php - 如何加密和解密 PHP 字符串?

标签 php security encryption cryptography encryption-symmetric

我的意思是:

Original String + Salt or Key --> Encrypted String
Encrypted String + Salt or Key --> Decrypted (Original String)

也许是这样的:
"hello world!" + "ABCD1234" --> Encrypt --> "2a2ffa8f13220befbe30819047e23b2c" (may be, for e.g)
"2a2ffa8f13220befbe30819047e23b2c" --> Decrypt with "ABCD1234" --> "hello world!"
  • 在 PHP 中,你怎么能做到这一点?

  • 尝试使用 Crypt_Blowfish ,但它对我不起作用。

    最佳答案

    在您进一步操作之前,请先了解 the difference between encryption and authentication ,以及为什么您可能需要经过身份验证的加密而不仅仅是加密。
    要实现经过身份验证的加密,您需要先加密,然后再 MAC。 The order of encryption and authentication is very important!这个问题的现有答案之一犯了这个错误;许多用 PHP 编写的密码学库也是如此。
    您应该avoid implementing your own cryptography ,而是使用由密码学专家编写和审查的安全库。

    Update: PHP 7.2 now provides libsodium! For best security, update your systems to use PHP 7.2 or higher and only follow the libsodium advice in this answer.


    Use libsodium if you have PECL access (或 sodium_compat 如果您想要不含 PECL 的 libsodium);否则...
    Use defuse/php-encryption ;不要推出自己的密码学!
    上面链接的两个库都可以轻松轻松地在您自己的库中实现经过身份验证的加密。
    如果您仍然想编写和部署自己的密码学库,与 Internet 上每个密码学专家的传统智慧相反,这些是您必须采取的步骤。
    加密:
  • 在 CTR 模式下使用 AES 加密。您也可以使用 GCM(无需单独的 MAC)。此外,ChaCha20 和 Salsa20(由 libsodium 提供)是流密码,不需要特殊模式。
  • 除非您在上面选择了 GCM,否则您应该使用 HMAC-SHA-256(或者,对于流密码,Poly1305——大多数 libsodium API 为您执行此操作)验证密文。 MAC应该涵盖IV以及密文!

  • 解密:
  • 除非使用Poly1305或GCM,否则重新计算密文的MAC并将其与使用 hash_equals() 发送的MAC进行比较。 .如果失败,则中止。
  • 解密消息。

  • 其他设计注意事项:
  • 不要压缩任何东西。密文不可压缩;在加密之前压缩明文会导致信息泄漏(例如 TLS 上的 CRIME 和 BREACH)。
  • 确保您使用 mb_strlen()mb_substr() ,使用 '8bit'字符集模式防止mbstring.func_overload问题。
  • 应该使用 CSPRNG 生成 IV ;如果您使用的是 mcrypt_create_iv() , 请勿使用 MCRYPT_RAND !
  • 另请查看 random_compat .

  • 除非您使用 AEAD 结构,否则 ALWAYS encrypt then MAC!
  • bin2hex() , base64_encode()等可能会通过缓存计时泄露有关您的加密 key 的信息。如果可能,请避免使用它们。

  • 即使您遵循此处给出的建议,密码学也可能会出现很多问题。 始终让密码学专家审查您的实现。 如果您没有幸与本地大学的密码学学生成为私有(private) friend ,您可以随时尝试 Cryptography Stack Exchange论坛寻求建议。
    如果您需要对您的实现进行专业分析,您可以随时聘请 reputable team of security consultants to review your PHP cryptography code (披露:我的雇主)。
    重要提示:何时不使用加密
    Don't encrypt passwords .您想使用以下密码散列算法之一对它们进行散列:
  • Argon2
  • scrypt
  • bcrypt
  • PBKDF2-SHA256 with 86,000 iterations

  • 切勿使用通用哈希函数(MD5、SHA256)来存储密码。
    Don't encrypt URL Parameters . 这是工作的错误工具。
    使用 Libsodium 的 PHP 字符串加密示例
    如果你使用的是 PHP < 7.2 或者没有安装 libsodium,你可以使用 sodium_compat完成相同的结果(尽管速度较慢)。
    <?php
    declare(strict_types=1);
    
    /**
     * Encrypt a message
     * 
     * @param string $message - message to encrypt
     * @param string $key - encryption key
     * @return string
     * @throws RangeException
     */
    function safeEncrypt(string $message, string $key): string
    {
        if (mb_strlen($key, '8bit') !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
            throw new RangeException('Key is not the correct size (must be 32 bytes).');
        }
        $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
        
        $cipher = base64_encode(
            $nonce.
            sodium_crypto_secretbox(
                $message,
                $nonce,
                $key
            )
        );
        sodium_memzero($message);
        sodium_memzero($key);
        return $cipher;
    }
    
    /**
     * Decrypt a message
     * 
     * @param string $encrypted - message encrypted with safeEncrypt()
     * @param string $key - encryption key
     * @return string
     * @throws Exception
     */
    function safeDecrypt(string $encrypted, string $key): string
    {   
        $decoded = base64_decode($encrypted);
        $nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
        $ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
        
        $plain = sodium_crypto_secretbox_open(
            $ciphertext,
            $nonce,
            $key
        );
        if (!is_string($plain)) {
            throw new Exception('Invalid MAC');
        }
        sodium_memzero($ciphertext);
        sodium_memzero($key);
        return $plain;
    }
    
    然后测试一下:
    <?php
    // This refers to the previous code block.
    require "safeCrypto.php"; 
    
    // Do this once then store it somehow:
    $key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
    $message = 'We are all living in a yellow submarine';
    
    $ciphertext = safeEncrypt($message, $key);
    $plaintext = safeDecrypt($ciphertext, $key);
    
    var_dump($ciphertext);
    var_dump($plaintext);
    
    Halite - Libsodium 变得更容易
    我一直在从事的项目之一是名为 Halite 的加密库。 ,旨在使 libsodium 更简单、更直观。
    <?php
    use \ParagonIE\Halite\KeyFactory;
    use \ParagonIE\Halite\Symmetric\Crypto as SymmetricCrypto;
    
    // Generate a new random symmetric-key encryption key. You're going to want to store this:
    $key = new KeyFactory::generateEncryptionKey();
    // To save your encryption key:
    KeyFactory::save($key, '/path/to/secret.key');
    // To load it again:
    $loadedkey = KeyFactory::loadEncryptionKey('/path/to/secret.key');
    
    $message = 'We are all living in a yellow submarine';
    $ciphertext = SymmetricCrypto::encrypt($message, $key);
    $plaintext = SymmetricCrypto::decrypt($ciphertext, $key);
    
    var_dump($ciphertext);
    var_dump($plaintext);
    
    所有底层密码学都由 libsodium 处理。
    使用 defuse/php-encryption 的示例
    <?php
    /**
     * This requires https://github.com/defuse/php-encryption
     * php composer.phar require defuse/php-encryption
     */
    
    use Defuse\Crypto\Crypto;
    use Defuse\Crypto\Key;
    
    require "vendor/autoload.php";
    
    // Do this once then store it somehow:
    $key = Key::createNewRandomKey();
    
    $message = 'We are all living in a yellow submarine';
    
    $ciphertext = Crypto::encrypt($message, $key);
    $plaintext = Crypto::decrypt($ciphertext, $key);
    
    var_dump($ciphertext);
    var_dump($plaintext);
    
    备注 :Crypto::encrypt()返回十六进制编码的输出。
    加密 key 管理
    如果您想使用“密码”,请立即停止。您需要一个随机的 128 位加密 key ,而不是人类容易记住的密码。
    您可以存储加密 key 以供长期使用,如下所示:
    $storeMe = bin2hex($key);
    
    而且,根据需要,您可以像这样检索它:
    $key = hex2bin($storeMe);
    
    强烈建议只存储一个随机生成的 key 以供长期使用,而不是任何类型的密码作为 key (或派生 key )。
    如果您使用的是 Defuse 的库:
  • $string = $keyObject->saveToAsciiSafeString()
  • $loaded = Key::loadFromAsciiSafeString($string);

  • “但我真的很想使用密码。”
    这是一个坏主意,但好吧,这里是如何安全地做到这一点。
    首先,生成一个随 secret 钥并将其存储在一个常量中。
    /**
     * Replace this with your own salt! 
     * Use bin2hex() then add \x before every 2 hex characters, like so:
     */
    define('MY_PBKDF2_SALT', "\x2d\xb7\x68\x1a\x28\x15\xbe\x06\x33\xa0\x7e\x0e\x8f\x79\xd5\xdf");
    
    请注意,您正在添加额外的工作,并且可以仅使用此常量作为键并为自己省去很多心痛!
    然后使用 PBKDF2(像这样)从您的密码中派生出合适的加密 key ,而不是直接使用您的密码进行加密。
    /**
     * Get an AES key from a static password and a secret salt
     * 
     * @param string $password Your weak password here
     * @param int $keysize Number of bytes in encryption key
     */
    function getKeyFromPassword($password, $keysize = 16)
    {
        return hash_pbkdf2(
            'sha256',
            $password,
            MY_PBKDF2_SALT,
            100000, // Number of iterations
            $keysize,
            true
        );
    }
    
    不要只使用 16 个字符的密码。您的加密 key 将被可笑地破坏。

    关于php - 如何加密和解密 PHP 字符串?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16600708/

    相关文章:

    javascript - WebcryptoAPI - 安全与否?

    php - mysqli_connect() 不适用于 bluemix

    php - 在php html中显示从mysql到dropdownbox的具体信息。

    c# - ASP.NET Membership C# - 如何比较现有密码/哈希

    Java 存在于系统上但未安装。这是网络安全问题吗?

    sql-server - 在数据库中存储密码的首选方法

    swift - 如何在 macOS 上正确存储加密 key 以便只有我的可执行文件可以访问它们?

    java - 无法建立 SSL 通信黑白客户端进程和 MQ 系列

    javascript - 在同一页面上创建钻取融合图表

    php - 如何在 "woocommerce_thankyou"钩子(Hook)触发后将产品名称与其属性名称连接起来?