首页 文章

你如何加密和解密PHP字符串?

提问于
浏览
184

我的意思是:

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 ,但它对我不起作用 .

6 回答

  • 11

    在您进一步做之前,请了解the difference between encryption and authentication,以及为什么您可能需要经过身份验证的加密而不仅仅是加密 .

    要实现经过身份验证的加密,您需要加密然后MAC . The order of encryption and authentication is very important!这个问题的现有答案之一造成了这个错误;就像许多用PHP编写的加密库一样 .

    您应该使用avoid implementing your own cryptography,而是使用由加密专家编写和审核的安全库 .

    更新:PHP 7.2现在提供libsodium!为了获得最佳安全性,请将系统更新为使用PHP 7.2或更高版本,并且只遵循本答案中的libsodium建议 .

    Use libsodium if you have PECL access (or sodium_compat if you want libsodium without PECL); otherwise...
    Use defuse/php-encryption; don't roll your own cryptography!

    上面链接的两个库使得在您自己的库中实现经过身份验证的加密变得轻松而轻松 .

    如果您仍然希望编写和部署自己的加密库,而不是互联网上每个加密专家的传统智慧,那么这些步骤就是您必须采取的步骤 .

    加密:

    • 在CTR模式下使用AES进行加密 . 您也可以使用GCM(不需要单独的MAC) . 此外,ChaCha20和Salsa20(由libsodium提供)是流密码,不需要特殊模式 .

    • 除非您选择上面的GCM,否则您应该使用HMAC-SHA-256对密文进行身份验证(或者,对于流密码,Poly1305 - 大多数libsodium API都会为您执行此操作) . The MAC should cover the IV as well as the ciphertext!

    解密:

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

    • 解密消息 .

    其他设计考虑因素:

    • 不要压缩任何东西 . 密文不可压缩;在加密之前压缩明文可能导致信息泄露(例如,TLS上的CRIME和BREACH) .

    • 确保使用 mb_strlen()mb_substr() ,使用 '8bit' 字符集模式来防止 mbstring.func_overload 问题 .

    • IVs应该使用CSPRNG生成;如果你正在使用 mcrypt_create_iv()DO NOT USE MCRYPT_RAND

    • 另请查看random_compat .

    • 除非您使用的是AEAD构造,否则ALWAYS encrypt then MAC!

    • bin2hex()base64_encode() 等可能会通过缓存时序泄漏有关加密密钥的信息 . 尽可能避免使用它们 .

    即使你遵循这里给出的建议,密码学也会出现很多问题 . Always have a cryptography expert review your implementation. 如果您不幸与当地大学的密码学生成为私人朋友,您可以随时到Cryptography Stack Exchange论坛寻求建议 .

    如果您需要对您的实施进行专业分析,您可以随时雇用reputable team of security consultants to review your PHP cryptography code(披露:我的雇主) .

    重要:何时不使用加密

    Don't encrypt passwords . 您希望使用以下密码散列算法之一来散列它们:

    切勿使用通用散列函数(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);
    

    NoteCrypto::encrypt() 返回十六进制编码输出 .

    加密密钥管理

    如果您想使用“密码”,请立即停止 . 您需要一个随机的128位加密密钥,而不是一个人类难忘的密码 .

    您可以存储加密密钥以供长期使用,如下所示:

    $storeMe = bin2hex($key);
    

    并且,根据需要,您可以像这样检索它:

    $key = hex2bin($storeMe);
    

    我建议只是存储随机生成的密钥以供长期使用,而不是任何类型的密码作为密钥(或导出密钥) .

    如果您正在使用Defuse的库:

    “但我真的想用密码 . ”

    这是一个坏主意,但没关系,这是如何安全地做到这一点 .

    首先,生成一个随机密钥并将其存储在常量中 .

    /**
     * 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(如此)从您的密码中导出合适的加密密钥,而不是直接使用您的密码加密 .

    /**
     * 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个字符的密码 . 您的加密密钥将被巧妙地破坏 .

  • 42

    What not to do

    警告:此答案使用ECB . ECB不是加密模式,它只是一个构建块 . 如本答案中所示,使用ECB实际上并不安全地加密字符串 . 不要在代码中使用ECB . 请参阅Scott的答案以获得一个好的解决方案 .

    我得到了自己 . 实际上我在谷歌找到了一些答案,只是修改了一些东西 . The result is completely insecure however.

    <?php
    define("ENCRYPTION_KEY", "!@#$%^&*");
    $string = "This is the original data string!";
    
    echo $encrypted = encrypt($string, ENCRYPTION_KEY);
    echo "
    "; echo $decrypted = decrypt($encrypted, ENCRYPTION_KEY); /** * Returns an encrypted & utf8-encoded */ function encrypt($pure_string, $encryption_key) { $iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB); $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); $encrypted_string = mcrypt_encrypt(MCRYPT_BLOWFISH, $encryption_key, utf8_encode($pure_string), MCRYPT_MODE_ECB, $iv); return $encrypted_string; } /** * Returns decrypted original string */ function decrypt($encrypted_string, $encryption_key) { $iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB); $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); $decrypted_string = mcrypt_decrypt(MCRYPT_BLOWFISH, $encryption_key, $encrypted_string, MCRYPT_MODE_ECB, $iv); return $decrypted_string; } ?>
  • 6

    我迟到了,但是找到了正确的方法,我遇到了这个页面,它是谷歌搜索的最高回报之一,所以我想分享一下我对这个问题的看法,我认为这个问题是撰写本文时(2017年初)的最新信息 . 从PHP 7.1.0开始, mcrypt_decryptmcrypt_encrypt 将被弃用,因此构建未来验证代码应该使用openssl_encryptopenssl_decrypt

    你可以这样做:

    $string_to_encrypt="Test";
    $password="password";
    $encrypted_string=openssl_encrypt($string_to_encrypt,"AES-128-ECB",$password);
    $decrypted_string=openssl_decrypt($encrypted_string,"AES-128-ECB",$password);
    

    重要说明:这使用的ECB模式不安全 . 如果您想要一个简单的解决方案而不参加加密工程中的速成课程,请不要自己编写,只需使用库 .

    您也可以使用任何其他削片机方法,具体取决于您的安全需求 . 要了解可用的削片机方法,请参阅openssl_get_cipher_methods功能 .

  • 344

    对于Laravel框架

    如果您使用的是Laravel框架,那么使用内部函数加密和解密会更容易 .

    $string = 'Some text to be encrypted';
    $encrypted = \Illuminate\Support\Facades\Crypt::encrypt($string);
    $decrypted_string = \Illuminate\Support\Facades\Crypt::decrypt($encrypted);
    
    var_dump($string);
    var_dump($encrypted);
    var_dump($decrypted_string);
    

    注意:确保在config / app.php文件的key选项中设置16个,24个或32个字符的随机字符串 . 否则,加密值将不安全 .

  • 36

    历史记录:这是在PHP4时编写的 . 这就是我们现在所说的“遗留代码” .

    我把这个答案留给了历史目的 - 但现在有些方法已被弃用,DES加密方法不是推荐的做法,等等 .

    我没有更新此代码有两个原因:1)我不再使用PHP手工加密方法,2)此代码仍然用于其目的:展示加密如何工作的最小,简单概念用PHP .

    如果您发现类似的简单,“PHP加密傻瓜”类型的源代码可以让人们开始使用10到20行或更少的代码,请在评论中告诉我 .

    除此之外,请享受早期PHP4简约加密答案的经典剧集 .


    理想情况下,您可以 - 或者可以获得 - 访问mcrypt PHP库,因为它肯定很受欢迎且非常有用 . 以下是不同种类的加密和一些示例代码:Encryption Techniques in PHP

    //Listing 3: Encrypting Data Using the mcrypt_ecb Function 
    
    <?php 
    echo("<h3> Symmetric Encryption </h3>"); 
    $key_value = "KEYVALUE"; 
    $plain_text = "PLAINTEXT"; 
    $encrypted_text = mcrypt_ecb(MCRYPT_DES, $key_value, $plain_text, MCRYPT_ENCRYPT); 
    echo ("<p><b> Text after encryption : </b>"); 
    echo ( $encrypted_text ); 
    $decrypted_text = mcrypt_ecb(MCRYPT_DES, $key_value, $encrypted_text, MCRYPT_DECRYPT); 
    echo ("<p><b> Text after decryption : </b>"); 
    echo ( $decrypted_text ); 
    ?>
    

    一些警告:

    1)当单向散列执行时,切勿使用可逆或“对称”加密 .

    2)如果数据真正敏感,如信用卡或社会安全号码,请停止;你需要的不仅仅是任何简单的代码块,而是需要一个专为此目的而设计的加密库,并且需要大量的时间来研究必要的方法 . 此外,软件加密可能是敏感数据安全性的10% . 这就像重新布线核电站 - 接受任务是危险和困难的,并且如果是这样的话,超出了你的知识范围 . 经济处罚可能是巨大的,因此最好使用服务并将责任运送给他们 .

    3)如此处所列,任何类型的易于实施的加密都可以合理地保护您想要避免的轻微重要信息,以防止在意外/故意泄漏的情况下窥探或限制暴露 . 但是看看密钥是如何以纯文本形式存储在Web服务器上的,如果他们可以获取数据,他们就可以获得解密密钥 .

    尽管如此,玩得开心:)

  • 2

    如果你不想使用库(你应该),那么使用这样的东西(PHP 7):

    function sign($message, $key) {
        return hash_hmac('sha256', $message, $key) . $message;
    }
    
    function verify($bundle, $key) {
        return hash_equals(
          hash_hmac('sha256', mb_substr($bundle, 64, null, '8bit'), $key),
          mb_substr($bundle, 0, 64, '8bit')
        );
    }
    
    function getKey($password, $keysize = 16) {
        return hash_pbkdf2('sha256',$password,'some_token',100000,$keysize,true);
    }
    
    function encrypt($message, $password) {
        $iv = random_bytes(16);
        $key = getKey($password);
        $result = sign(openssl_encrypt($message,'aes-256-ctr',$key,OPENSSL_RAW_DATA,$iv), $key);
        return bin2hex($iv).bin2hex($result);
    }
    
    function decrypt($hash, $password) {
        $iv = hex2bin(substr($hash, 0, 32));
        $data = hex2bin(substr($hash, 32));
        $key = getKey($password);
        if (!verify($data, $key)) {
          return null;
        }
        return openssl_decrypt(mb_substr($data, 64, null, '8bit'),'aes-256-ctr',$key,OPENSSL_RAW_DATA,$iv);
    }
    
    $string_to_encrypt='John Smith';
    $password='password';
    $encrypted_string=encrypt($string_to_encrypt, $password);
    $decrypted_string=decrypt($encrypted_string, $password);
    

相关问题