PHP 中的 2 向字符串加密 - 其中哪个更安全?

2-way string encryption in PHP - which of these is more secure?

我需要通过查询字符串传递一个值以显示在我的页面上。该值必须加密,因为它包含一些供查看页面的用户使用的 PII。并且为了让用户可读,它需要在显示时能够被解密。

我正在使用 PHP,到目前为止的研究使我找到了 openssl_encryptopenssl_decrypt 以及这两个代码资源:

  1. https://bhoover.com/using-php-openssl_encrypt-openssl_decrypt-encrypt-decrypt-data/
  2. https://gist.github.com/joashp/a1ae9cb30fa533f4ad94

我非常喜欢#1,因为 iv 实际上附加到返回的加密字符串的方式。这使我们 NOT 不必将 iv 存储为常量,并且能够在每次调用函数时生成一个新常量。这对我来说似乎比每次都使用相同的 keyiv 更安全。 是真的吗?如果是这样,除了明显的痛苦之外,我是否应该了解任何原因?。我不喜欢的是我 认为 ivkey 与 character/string 连接起来(在这种情况下 :: ) 可以发现在其他潜在的密文中自然发生或 iv 成为问题。使用这种方法,在尝试加密 7000 多个电子邮件地址时,其中略多于一半的解码字符串中出现了这些奇怪的字符,���6CTΣ等等)从而破坏了它。

#2 很棒,因为它有效!!我还没有找到可以破坏它的字符串……尤其是在我的电子邮件列表中。 BUT 如上所述,这需要。 ivkey 始终是相同的值并存储在某处的变量中。这似乎是 1 微小的 维护问题,但更多的是安全问题。

所以我做了更多 reading/thinking 并得出 with this working example - 这是代码:

<?php

// generate key with base64_encode(openssl_random_pseudo_bytes(32);
// and save it in a constant.
define('ENCRYPT_KEY_1', 'CuH8WPfXzMj0xRWybHjssWJ+IhTDqL5w0OD9+zXFloA=');

function encrypt_decrypt($action, $string) {
    $output = false;

    $encrypt_method = "AES-256-CBC";
    $key = ENCRYPT_KEY_1;
    
    $ivLen = openssl_cipher_iv_length($encrypt_method);
    

    /**
     * the key was generated with 32 pseudo-bytes and then base64Encod()-ed.
     * Not sure of the reason for encoding - just decoding in case it's necessary.
     * 
     * Thoughts?
     * **/
    $key = base64_decode($key);

    if ( $action == 'encrypt' ) {
        /**
        * "AES-256-CBC" expects a 16-byte string - create an 8-byte string to be 
        * converted to a 16-byte hex before being used as the initialization vector
        * TLDR" in order to end up with 16-bytes to feed to openssl_random_pseudo_bytes,
        * divide initial length in half as the hex value will double it
        * */
        $iv = openssl_random_pseudo_bytes($ivLen/2);
        $iv = bin2hex($iv);
        $tmp_data_str_in = openssl_encrypt($string, $encrypt_method, $key, 0, $iv);
        $output = base64_encode($tmp_data_str_in . $iv);
    } else if( $action == 'decrypt' ) {
        $data_str_in = base64_decode($string);
        
        // This time, rather than generating one, we get the iv (converted to hex)
        // by grabbing the last 16 characters of the concatenation of the 2 that happened
        // during encryption.
        $iv = substr($data_str_in, -$ivLen);
        
        // And now we just grab everything BUT the last 16 characters. We'll
        // run openssl_decrypt and return a copy of the original input
        $encrypted_txt_str = substr($data_str_in, 0, strlen($data_str_in)-$ivLen); 

        // Notice we are returning the encrypted value of a string consisting of
        // encoded versions of the input text and a random `IV` - we'll grab the `IV`
        // from it later in order to decrypt later. 
        $output = openssl_decrypt($encrypted_txt_str, $encrypt_method, $key, 0, $iv);
    }

    return $output;
}

$plain_txt = "memyselfandi@i.me";
echo "Plain Text = " .$plain_txt. "\n";  

$encrypted_txt = encrypt_decrypt('encrypt', $plain_txt);
echo "Encrypted Text = " .$encrypted_txt. "\n";

$decrypted_txt = encrypt_decrypt('decrypt', $encrypted_txt);
echo "Decrypted Text = " .$decrypted_txt. "\n";

if ( $plain_txt === $decrypted_txt ) echo "SUCCESS";
else echo "FAILED";

echo "\n";
?>

所以我想我的主要问题是:

  1. 我认为使用在执行函数时生成的动态 iv 的解决方案比提前定义静态 iv 并用于每个加密?如果没有,我错过了什么?
  2. (可能成功的)攻击有哪些机会,所以 any/all 这些方法暴露了?如何修复或修改它们以降低风险
  3. 这些方法中的任何一种(希望是我放在一起的方法)是否可以在显示用户 PII 的站点上的生产环境中使用——本质上没有银行或超级机密——并允许他进行更新?它被用在 PHP 中,看起来有点像:print "<li>Email: " . encrypt_decrypt('decrypt', my_sanitize_fxn($_GET['ue']) . "</li">;

关于 #3 的快速说明:

我猜 FAR 加密一些不是 PII 的东西(例如数据库中用户的唯一 ID)以通过查询字符串发送会更好,然后解密该值并使用它通过数据库查询查找他的电子邮件地址。尽管我最终可能会以这种方式结束所有这一切,但我们只想说目前有一些因素在起作用(为此解释会把这个问题拖到离主题太远的地方),这甚至阻止了它成为一个远程可行的选择。

我认为了解我在这里得到的东西将很好地延续到最终解决方案中。除了对我一直提出的一些正式问题的回答之外,我很想听听任何做得特别差或做得特别好的事情,或者只是一般性的评论。

提前感谢您愿意分享的任何智慧。

抱歉懒得将我的例子应用到你的代码中,但它应该不会那么复杂,因为下面的代码是一个完整的示例 使用随机 IV 的 AES GCM 256 字符串加密。 IV 和标签被添加到密文之前,然后进行 Base64 编码。

请注意,该代码没有任何错误处理,仅用于教育目的!不要使用静态密钥进行加密。

输出:

Sample AES GCM 256 string encryption
Please note that this code does not have any error handling and is for educational purpose only
Do NOT use static keys for encryption !

plaintext: The quick brown fox jumps over the lazy dog
encrypt: jemvFuwhIaUYx49d1nap6uKz8wMIorvQuRD/PGt+SYhFt8iaK1fiqAf8CjWtVNYqFZATStgq2XQuUAhbnhMtpzHDPN7oUFo=
decrypt: The quick brown fox jumps over the lazy dog

代码:

<?php
function encrypt($encryptionKey, $data) {
    $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-gcm'));
    $encrypted = openssl_encrypt($data, 'aes-256-gcm', $encryptionKey, OPENSSL_RAW_DATA, $iv, $tag);
    return base64_encode($iv . $tag . $encrypted);
}

function decrypt($encryptionKey, $data) {
    $c = base64_decode($data);
    $ivlen = openssl_cipher_iv_length($cipher="AES-256-GCM");
    $iv = substr($c, 0, $ivlen);
    $tag = substr($c, $ivlen, $taglen=16);
    $ciphertext_raw = substr($c, $ivlen+$taglen);
    return openssl_decrypt($ciphertext_raw, 'aes-256-gcm', $encryptionKey, OPENSSL_RAW_DATA, $iv, $tag);
}

echo 'Sample AES GCM 256 string encryption' . PHP_EOL;
echo 'Please note that this code does not have any error handling and is for educational purpose only' . PHP_EOL;
echo 'Do NOT use static keys for encryption !'. PHP_EOL . PHP_EOL;

$plaintext = 'The quick brown fox jumps over the lazy dog';
$key = '12345678901234567890123456789012'; // 32 bytes = 256 bit key
echo 'plaintext: ' . $plaintext .PHP_EOL;
$encrypt = encrypt($key, $plaintext);
echo 'encrypt: ' . $encrypt . PHP_EOL;
$decrypt = decrypt($key, $encrypt);
echo 'decrypt: ' . $decrypt . PHP_EOL;
?>