PHP AES 加密/解密
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3422759/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
PHP AES encrypt / decrypt
提问by Andreas Prang
I found an example for en/decoding strings in PHP. At first it looks very good but it wont work :-(
我在 PHP 中找到了一个用于编码/解码字符串的示例。起初它看起来很好,但它不会工作:-(
Does anyone know what the problem is?
有谁知道问题是什么?
$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)));
}
The result is:
结果是:
Encrypted: boKRNTYYNp7AiOvY1CidqsAn9wX4ufz/D9XrpjAOPk8=
加密: boKRNTYYNp7AiOvY1CidqsAn9wX4ufz/D9XrpjAOPk8=
Decrypted: —?(?á ^ y?~F'??ó–í ?e2á_B‰?—
解密: —?(?á ^ y?~F'??ó–í ?e2á_B‰?—
回答by Scott Arciszewski
Please use an existing secure PHP encryption library
请使用现有的安全 PHP 加密库
It's generally a bad idea to write your own cryptography unless you have experience breaking other peoples' cryptography implementations.
除非您有破坏其他人的密码学实现的经验,否则编写自己的密码学通常是一个坏主意。
None of the examples here authenticate the ciphertext, which leaves them vulnerable to bit-rewriting attacks.
这里的示例都没有验证密文,这使它们容易受到位重写攻击。
If you can install PECL extensions, libsodiumis even better
如果你可以安装 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
);
}
Then to test it out:
然后测试一下:
<?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);
This can be used in any situation where you are passing data to the client (e.g. encrypted cookies for sessions without server-side storage, encrypted URL parameters, etc.) with a reasonably high degree of certainty that the end user cannot decipher or reliably tamper with it.
这可以在您将数据传递给客户端的任何情况下使用(例如,没有服务器端存储的会话的加密 cookie、加密的 URL 参数等),并且具有相当高的确定性,最终用户无法破译或可靠地篡改用它。
Since libsodium is cross-platform, this also makes it easier to communicate with PHP from, e.g. Java applets or native mobile apps.
由于libsodium 是跨平台的,这也使得与 PHP 通信变得更加容易,例如 Java 小程序或本机移动应用程序。
Note: If you specifically need to add encrypted cookies powered by libsodium to your app, my employer Paragon Initiative Enterprisesis developing a library called Halitethat does all of this for you.
注意:如果您特别需要向您的应用程序添加由 libsodium 提供支持的加密 cookie,我的雇主Paragon Initiative Enterprises正在开发一个名为Halite的库,可以为您完成所有这些工作。
回答by zz1433
$sDecrypted
and $sEncrypted
were undefined in your code. See a solution that works (but is not secure!):
$sDecrypted
并且$sEncrypted
在您的代码中未定义。查看有效的解决方案(但不安全!):
STOP!
停止!
This example is insecure!Do not use it!
这个例子是不安全的!不要使用它!
$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)
)
), "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 . $iv, $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_equals(hash_hmac('sha256', $ciphertext . $iv, $key, true), $hash)) return null;
return openssl_decrypt($ciphertext, $method, $key, OPENSSL_RAW_DATA, $iv);
}
"
);
}
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
)
), "$encrypted = encrypt('Plaintext string.', 'password'); // this yields a binary string
echo decrypt($encrypted, 'password');
// decrypt($encrypted, 'wrong password') === null
"
);
}
But there are other problems in this code which make it insecure, in particular the use of ECB (which is not an encryptionmode, only a building block on top of which encryption modes can be defined). See Fab Sa's answerfor a quick fix of the worst problems and Scott's answerfor how to do this right.
但是这段代码还有其他问题使其不安全,特别是使用了 ECB(这不是加密模式,只是可以定义加密模式的构建块)。请参阅Fab Sa 的答案以快速解决最严重的问题,以及Scott 的答案以了解如何正确地做到这一点。
回答by blade
If you don't want to use a heavy dependencyfor something solvable in 15 lines of code, use the built in OpenSSLfunctions. Most PHP installations come with OpenSSL, which provides fast, compatible and secure AES encryption in PHP. Well, it's secure as long as you're following the best practices.
如果您不想对可在 15 行代码中解决的问题使用重度依赖,请使用内置的OpenSSL函数。大多数 PHP 安装都带有 OpenSSL,它在 PHP 中提供快速、兼容和安全的 AES 加密。好吧,只要您遵循最佳实践,它就是安全的。
The following code:
以下代码:
- uses AES256 in CBC mode
- is compatible with other AES implementations, but not mcrypt, since mcrypt uses PKCS#5 instead of PKCS#7.
- generates a key from the provided password using SHA256
- generates a hmac hash of the encrypted data for integrity check
- generates a random IV for each message
- prepends the IV (16 bytes) and the hash (32 bytes) to the ciphertext
- should be pretty secure
- 在 CBC 模式下使用 AES256
- 与其他 AES 实现兼容,但与mcrypt 不兼容,因为 mcrypt 使用 PKCS#5 而不是 PKCS#7。
- 使用 SHA256 从提供的密码生成密钥
- 生成加密数据的 hmac 哈希值以进行完整性检查
- 为每条消息生成一个随机 IV
- 将 IV(16 个字节)和散列(32 个字节)添加到密文
- 应该很安全
IV is a public information and needs to be random for each message. The hash ensures that the data hasn't been tampered with.
IV是公共信息,需要对每条消息都是随机的。散列可确保数据未被篡改。
<?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)), " <?php
class AESEncryption {
protected $key;
protected $data;
protected $method;
protected $iv;
/**
* Available OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING
*
* @var type $options
*/
protected $options = 0;
/**
*
* @param type $data
* @param type $key
* @param type $iv
* @param type $blockSize
* @param type $mode
*/
public function __construct($data = null, $key = null, $iv = null, $blockSize = null, $mode = 'CBC') {
$this->setData($data);
$this->setKey($key);
$this->setInitializationVector($iv);
$this->setMethod($blockSize, $mode);
}
/**
*
* @param type $data
*/
public function setData($data) {
$this->data = $data;
}
/**
*
* @param type $key
*/
public function setKey($key) {
$this->key = $key;
}
/**
* CBC 128 192 256
CBC-HMAC-SHA1 128 256
CBC-HMAC-SHA256 128 256
CFB 128 192 256
CFB1 128 192 256
CFB8 128 192 256
CTR 128 192 256
ECB 128 192 256
OFB 128 192 256
XTS 128 256
* @param type $blockSize
* @param type $mode
*/
public function setMethod($blockSize, $mode = 'CBC') {
if($blockSize==192 && in_array('', array('CBC-HMAC-SHA1','CBC-HMAC-SHA256','XTS'))){
$this->method=null;
throw new Exception('Invalid block size and mode combination!');
}
$this->method = 'AES-' . $blockSize . '-' . $mode;
}
/**
*
* @param type $data
*/
public function setInitializationVector($iv) {
$this->iv = $iv;
}
/**
*
* @return boolean
*/
public function validateParams() {
if ($this->data != null &&
$this->method != null ) {
return true;
} else {
return FALSE;
}
}
//it must be the same when you encrypt and decrypt
protected function getIV() {
return $this->iv;
}
/**
* @return type
* @throws Exception
*/
public function encrypt() {
if ($this->validateParams()) {
return trim(openssl_encrypt($this->data, $this->method, $this->key, $this->options,$this->getIV()));
} else {
throw new Exception('Invalid params!');
}
}
/**
*
* @return type
* @throws Exception
*/
public function decrypt() {
if ($this->validateParams()) {
$ret=openssl_decrypt($this->data, $this->method, $this->key, $this->options,$this->getIV());
return trim($ret);
} else {
throw new Exception('Invalid params!');
}
}
}
");
}
function fnDecrypt($sValue, $sSecretKey) {
global $iv;
return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $sSecretKey, base64_decode($sValue), MCRYPT_MODE_CBC, $iv), "<?php
$data = json_encode(['first_name'=>'Dunsin','last_name'=>'Olubobokun','country'=>'Nigeria']);
$inputKey = "W92ZB837943A711B98D35E799DFE3Z18";
$iv = "tuqZQhKP48e8Piuc";
$blockSize = 256;
$aes = new AESEncryption($data, $inputKey, $iv, $blockSize);
$enc = $aes->encrypt();
$aes->setData($enc);
$dec=$aes->decrypt();
echo "After encryption: ".$enc."<br/>";
echo "After decryption: ".$dec."<br/>";
");
}
Usage:
用法:
$string = "TheString";
$decrypted_string = decrypt_function($stirng, $key);
echo bin2hex($decrypted_string)."=".bin2hex("TheString");
edit: Updated to use hash_equals
and added IV to the hash.
编辑:更新为使用hash_equals
并将 IV 添加到哈希中。
回答by Fabien Sa
For information MCRYPT_MODE_ECB
doesn't use the IV (initialization vector). ECB mode divide your message into blocks and each block is encrypted separately. I really don't recommended it.
有关信息MCRYPT_MODE_ECB
不使用 IV(初始化向量)。ECB 模式将您的消息分成块,每个块单独加密。我真的不推荐它。
CBC mode use the IV to make each message unique. CBC is recommended and should be used instead of ECB.
CBC 模式使用 IV 使每条消息都是唯一的。建议使用 CBC,并应使用 CBC 代替 ECB。
Example :
例子 :
class Crypto
{
/**
* Encrypt data using OpenSSL (AES-256-CBC)
* @param string $plaindata Data to be encrypted
* @param string $cryptokey key for encryption (with 256 bit of entropy)
* @param string $hashkey key for hashing (with 256 bit of entropy)
* @return string IV+Hash+Encrypted as raw binary string. The first 16
* bytes is IV, next 32 bytes is HMAC-SHA256 and the rest is
* $plaindata as encrypted.
* @throws Exception on internal error
*
* Based on code from: https://stackoverflow.com/a/46872528
*/
public static function encrypt($plaindata, $cryptokey, $hashkey)
{
$method = "AES-256-CBC";
$key = hash('sha256', $cryptokey, true);
$iv = openssl_random_pseudo_bytes(16);
$cipherdata = openssl_encrypt($plaindata, $method, $key, OPENSSL_RAW_DATA, $iv);
if ($cipherdata === false)
{
$cryptokey = "**REMOVED**";
$hashkey = "**REMOVED**";
throw new \Exception("Internal error: openssl_encrypt() failed:".openssl_error_string());
}
$hash = hash_hmac('sha256', $cipherdata.$iv, $hashkey, true);
if ($hash === false)
{
$cryptokey = "**REMOVED**";
$hashkey = "**REMOVED**";
throw new \Exception("Internal error: hash_hmac() failed");
}
return $iv.$hash.$cipherdata;
}
/**
* Decrypt data using OpenSSL (AES-256-CBC)
* @param string $encrypteddata IV+Hash+Encrypted as raw binary string
* where the first 16 bytes is IV, next 32 bytes is HMAC-SHA256 and
* the rest is encrypted payload.
* @param string $cryptokey key for decryption (with 256 bit of entropy)
* @param string $hashkey key for hashing (with 256 bit of entropy)
* @return string Decrypted data
* @throws Exception on internal error
*
* Based on code from: https://stackoverflow.com/a/46872528
*/
public static function decrypt($encrypteddata, $cryptokey, $hashkey)
{
$method = "AES-256-CBC";
$key = hash('sha256', $cryptokey, true);
$iv = substr($encrypteddata, 0, 16);
$hash = substr($encrypteddata, 16, 32);
$cipherdata = substr($encrypteddata, 48);
if (!hash_equals(hash_hmac('sha256', $cipherdata.$iv, $hashkey, true), $hash))
{
$cryptokey = "**REMOVED**";
$hashkey = "**REMOVED**";
throw new \Exception("Internal error: Hash verification failed");
}
$plaindata = openssl_decrypt($cipherdata, $method, $key, OPENSSL_RAW_DATA, $iv);
if ($plaindata === false)
{
$cryptokey = "**REMOVED**";
$hashkey = "**REMOVED**";
throw new \Exception("Internal error: openssl_decrypt() failed:".openssl_error_string());
}
return $plaindata;
}
}
You have to stock the IV to decode each message (IV are notsecret). Each message is unique because each message has an unique IV.
您必须储备 IV 以解码每条消息(IV不是秘密)。每条消息都是唯一的,因为每条消息都有唯一的 IV。
- More informations about mode of operation (wikipedia).
回答by Navneet Kumar
Few important things to note with AES encryption:
使用 AES 加密需要注意的几个重要事项:
- Never use plain text as encryption key. Always hash the plain text key and then use for encryption.
- Always use Random IV (initialization vector) for encryption and decryption. True randomizationis important.
- As mentioned above, don't use ecbmode, use
CBC
instead.
- 切勿使用纯文本作为加密密钥。始终散列纯文本密钥,然后用于加密。
- 始终使用随机 IV(初始化向量)进行加密和解密。真正的随机化很重要。
- 如上所述,不要使用ecb模式,
CBC
而是使用。
回答by Dunsin Olubobokun
This is a working solution of AES encryption
- implemented using openssl
. It uses the Cipher Block Chaining Mode (CBC-Mode). Thus, alongside data
and key
, you can specify iv
and block size
这是一个工作解决方案AES encryption
- 使用openssl
. 它使用密码块链接模式(CBC-Mode)。因此,在data
and 旁边 key
,您可以指定iv
和block size
Sample usage:
示例用法:
##代码##回答by Kamen
If you are using MCRYPT_RIJNDAEL_128, try rtrim($output, "\0\3")
. If the length of the string is less than 16, the decrypt function will return a string with length of 16 characters, adding 03 at the end.
如果您使用的是 MCRYPT_RIJNDAEL_128,请尝试rtrim($output, "\0\3")
. 如果字符串的长度小于 16,则解密函数将返回一个长度为 16 个字符的字符串,并在末尾添加 03。
You can easily check this, e.g. by trying:
您可以轻松地检查这一点,例如通过尝试:
##代码##回答by M_R_K
If you are using PHP >= 7.2 consider using inbuilt sodium core extension for encrption.
如果您使用 PHP >= 7.2,请考虑使用内置的sodium 核心扩展进行加密。
Find more information here - http://php.net/manual/en/intro.sodium.php
.
在此处查找更多信息 - http://php.net/manual/en/intro.sodium.php
。
回答by Mikko Rantalainen
Here's an improved version based on code written by blade
这是基于blade编写的代码的改进版本
- add comments
- overwrite arguments before throwing to avoid leaking secrets with the exception
- check return values from openssl and hmac functions
- 添加评论
- 在抛出之前覆盖参数以避免泄露秘密
- 检查 openssl 和 hmac 函数的返回值
The code:
编码:
##代码##