PHP的AESencryption/解密
我find了一个在PHP中使用/解码string的例子。 起初它看起来非常好,但它不会工作:-(
有谁知道问题是什么?
$Pass = "Passwort"; $Clear = "Klartext"; $crypted = fnEncrypt($Clear, $Pass); echo "Encrypted: ".$crypted."</br>"; $newClear = fnDecrypt($crypted, $Pass); echo "Decrypted: ".$newClear."</br>"; function fnEncrypt($sValue, $sSecretKey) { return trim(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $sSecretKey, $sDecrypted, MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND)))); } function fnDecrypt($sValue, $sSecretKey) { return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $sSecretKey, base64_decode($sEncrypted), MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND))); }
结果是:
encryption: boKRNTYYNp7AiOvY1CidqsAn9wX4ufz/D9XrpjAOPk8=
解密: —‚(ÑÁ ^ yË~F'¸®Ó–í œð2Á_B‰Â—
$sDecrypted
和$sEncrypted
在您的代码中未定义。 看到有效的解决scheme( 但不安全! ):
停!
这个例子是不安全的! 不要使用它!
$Pass = "Passwort"; $Clear = "Klartext"; $crypted = fnEncrypt($Clear, $Pass); echo "Encrypred: ".$crypted."</br>"; $newClear = fnDecrypt($crypted, $Pass); echo "Decrypred: ".$newClear."</br>"; function fnEncrypt($sValue, $sSecretKey) { return rtrim( base64_encode( mcrypt_encrypt( MCRYPT_RIJNDAEL_256, $sSecretKey, $sValue, MCRYPT_MODE_ECB, mcrypt_create_iv( mcrypt_get_iv_size( MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB ), MCRYPT_RAND) ) ), "\0" ); } function fnDecrypt($sValue, $sSecretKey) { return rtrim( mcrypt_decrypt( MCRYPT_RIJNDAEL_256, $sSecretKey, base64_decode($sValue), MCRYPT_MODE_ECB, mcrypt_create_iv( mcrypt_get_iv_size( MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB ), MCRYPT_RAND ) ), "\0" ); }
但是在这个代码中还有其他的问题使得它不安全,尤其是使用ECB(这不是一种encryption模式,只能在其上定义encryption模式的构build块)。 请参阅Fab Sa的答案,以便及时解决最糟糕的问题,以及Scott如何做到这一点的答案 。
请使用现有的安全PHPencryption库
编写自己的密码通常是一个糟糕的主意,除非你有打破其他人的密码实现的经验。
这里没有一个例子validation密文 ,这使得它们容易受到位重写攻击。
如果你可以安装PECL扩展, libsodium甚至更好
<?php // PECL libsodium 0.2.1 and newer /** * Encrypt a message * * @param string $message - message to encrypt * @param string $key - encryption key * @return string */ function safeEncrypt($message, $key) { $nonce = \Sodium\randombytes_buf( \Sodium\CRYPTO_SECRETBOX_NONCEBYTES ); return base64_encode( $nonce. \Sodium\crypto_secretbox( $message, $nonce, $key ) ); } /** * Decrypt a message * * @param string $encrypted - message encrypted with safeEncrypt() * @param string $key - encryption key * @return string */ function safeDecrypt($encrypted, $key) { $decoded = base64_decode($encrypted); $nonce = mb_substr($decoded, 0, \Sodium\CRYPTO_SECRETBOX_NONCEBYTES, '8bit'); $ciphertext = mb_substr($decoded, \Sodium\CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit'); return \Sodium\crypto_secretbox_open( $ciphertext, $nonce, $key ); }
然后testing一下:
<?php // This refers to the previous code block. require "safeCrypto.php"; // Do this once then store it somehow: $key = \Sodium\randombytes_buf(\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);
这可以在任何情况下将数据传递给客户端(例如,没有服务器端存储的会话的encryptioncookie,encryption的URL参数等),并具有相当高的确定性,以致最终用户无法破译或可靠地篡改用它。
由于libsodium是跨平台的 ,这也使得与例如Java小程序或本地移动应用程序的PHP进行通信变得更容易。
注意:如果您特别需要在您的应用程序中添加由libsodium驱动的encryptioncookie,我的雇主Paragon Initiative Enterprises正在开发名为Halite的图书馆,为您完成所有这些工作。
有关信息MCRYPT_MODE_ECB
不使用IV(初始化向量)。 ECB模式将消息分成块,每个块分别encryption。 我真的不推荐它 。
CBC模式使用IV来使每个消息都是唯一的。 build议使用CBC,而不是ECB。
例如:
<?php $password = "myPassword_!"; $messageClear = "Secret message"; // 32 byte binary blob $aes256Key = hash("SHA256", $password, true); // for good entropy (for MCRYPT_RAND) srand((double) microtime() * 1000000); // generate random iv $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC), MCRYPT_RAND); $crypted = fnEncrypt($messageClear, $aes256Key); $newClear = fnDecrypt($crypted, $aes256Key); echo "IV: <code>".$iv."</code><br/>". "Encrypred: <code>".$crypted."</code><br/>". "Decrypred: <code>".$newClear."</code><br/>"; function fnEncrypt($sValue, $sSecretKey) { global $iv; return rtrim(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $sSecretKey, $sValue, MCRYPT_MODE_CBC, $iv)), "\0\3"); } function fnDecrypt($sValue, $sSecretKey) { global $iv; return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $sSecretKey, base64_decode($sValue), MCRYPT_MODE_CBC, $iv), "\0\3"); }
你必须存储IV来解码每个消息(IV 不是秘密)。 每条消息都是独一无二的,因为每条消息都有独特的IV
- 有关操作模式的更多信息(维基百科) 。
AESencryption很less有重要的注意事项:
- 切勿使用纯文本作为encryption密钥。 始终散列纯文本密钥,然后用于encryption。
- 始终使用随机IV(初始化vector)进行encryption和解密。 真正的随机化很重要。
- 如上所述,不要使用ecb模式,而应使用
CBC
。
如果您正在使用MCRYPT_RIJNDAEL_128,请尝试使用rtrim rtrim($output, "\0\3")
。 如果string的长度小于16,解密函数将返回长度为16个字符的string,最后加上03。
你可以很容易地检查这个,例如通过尝试:
$string = "TheString"; $decrypted_string = decrypt_function($stirng, $key); echo bin2hex($decrypted_string)."=".bin2hex("TheString");
您可以使用OpenSSL在PHP中进行简单且安全的AESencryption。 OpenSSL通常内置于PHP中,所以不需要外部依赖。 下面的例子:
- 在CBC模式下使用AES256
- 使用SHA256从提供的密码生成一个密钥
- 生成encryption数据的hmac散列以进行完整性检查
- 为每条消息生成一个随机IV
- 将IV(16字节)和散列(32字节)加到密文上
IV是一个公共信息,需要随机为每个消息。 散列确保数据没有被篡改。
function encrypt($plaintext, $password) { $method = "AES-256-CBC"; $key = hash('sha256', $password, true); $iv = openssl_random_pseudo_bytes(16); $ciphertext = openssl_encrypt($plaintext, $method, $key, OPENSSL_RAW_DATA, $iv); $hash = hash_hmac('sha256', $ciphertext, $key, true); return $iv . $hash . $ciphertext; } function decrypt($ivHashCiphertext, $password) { $method = "AES-256-CBC"; $iv = substr($ivHashCiphertext, 0, 16); $hash = substr($ivHashCiphertext, 16, 32); $ciphertext = substr($ivHashCiphertext, 48); $key = hash('sha256', $password, true); if (hash_hmac('sha256', $ciphertext, $key, true) !== $hash) return null; return openssl_decrypt($ciphertext, $method, $key, OPENSSL_RAW_DATA, $iv); }
用法:
$encrypted = encrypt('Plaintext string.', 'password'); // this yields a binary string echo decrypt($encrypted, 'password'); // decrypt($encrypted, 'wrong password') === null
这应该与其他AES实现兼容,但不是mcrypt ,因为mcrypt使用PKCS#5而不是PKCS#7。