如何在 PHP 中复制 MySQL 的 aes-256-cbc
How to replicate MySQL's aes-256-cbc in PHP
使用 this 文章作为指南,我能够在 PHP 中成功复制 MySQL 的 aes-128-ecb:
final class Encryption
{
// The key
const KEY = '36F3D40A7A41A827968BE75A87D60950';
/**
* Encrypt a string
*
* @access public
* @static
* @param string $string
* @return string
*/
public static function encrypt($string)
{
return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, self::getMySQLKey(self::KEY), self::getPaddedString($string), MCRYPT_MODE_ECB);
}
/**
* Decrypt a string
*
* @access public
* @static
* @param string $string
* @return string
*/
public static function decrypt($string)
{
return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, self::getMySQLKey(self::KEY), $string, MCRYPT_MODE_ECB), "\x00..\x10");
}
/**
* Get MySQL key
*
* @access public
* @static
* @param string $key
* @return string
*/
public static function getMySQLKey($key)
{
// The new key
$new_key = str_repeat(chr(0), 16);
// Iterate over the key and XOR
for ($i = 0, $l = strlen($key); $i < $l; ++$i)
{
$new_key[$i % 16] = $new_key[$i % 16] ^ $key[$i];
}
// Return the new key
return $new_key;
}
/**
* Get padded string
*
* @access public
* @static
* @param string $string
* @return string
*/
public static function getPaddedString($string)
{
return str_pad($string, (16 * (floor(strlen($string) / 16) + 1)), chr(16 - (strlen($string) % 16)));
}
}
举个例子:
// PHP, gives CJI+zJyviQI7GgSCLGMNsqsXq2MDKC3a9FIG3wDrE8Y=
base64_encode(Encryption::encrypt('michael@example.com'))
// MySQL, gives CJI+zJyviQI7GgSCLGMNsqsXq2MDKC3a9FIG3wDrE8Y=
SELECT TO_BASE64(AES_ENCRYPT('michael@example.com', '36F3D40A7A41A827968BE75A87D60950'));
但是,我想更新为使用 aes-256-cbc,但遇到了困难。我首先将 MCRYPT_RIJNDAEL_128
替换为 MCRYPT_RIJNDAEL_256
,将 MCRYPT_MODE_ECB
替换为 MCRYPT_MODE_CBC
,并使用 KEY
常量作为初始化向量:
/**
* Encrypt a string
*
* @access public
* @static
* @param string $string
* @return string
*/
public static function encrypt($string)
{
return mcrypt_encrypt(MCRYPT_RIJNDAEL_256, self::getMySQLKey(self::KEY), self::getPaddedString($string), MCRYPT_MODE_CBC, self::KEY);
}
/**
* Decrypt a string
*
* @access public
* @static
* @param string $string
* @return string
*/
public static function decrypt($string)
{
return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, self::getMySQLKey(self::KEY), $string, MCRYPT_MODE_CBC, self::KEY), "\x00..\x10");
}
问题是我现在从 PHP 和 MySQL 得到不同的值:
// PHP, gives XSRfnrl05CE7JIHCvfhq6D67O0mAW2ayrFv2YkjFVYI=
base64_encode(Encryption::encrypt('michael@example.com'))
// MySQL, gives lTLT4MRXcHnOAsYjlwUX4WVPHgYvyi6nKC4/3us/VF4=
SELECT TO_BASE64(AES_ENCRYPT('michael@example.com', '36F3D40A7A41A827968BE75A87D60950', '36F3D40A7A41A827968BE75A87D60950'));
我不确定从哪里开始,所以任何帮助将不胜感激。
并确认 MySQL 确实使用了正确的加密方法:
// aes-256-cbc
SELECT @@session.block_encryption_mode
由于 eggyal nor Artjom B. 都没有选择通过他们的解决方案提供答案,我将代表他们这样做。
第一个问题是 AES-256 仍然使用 MCRYPT_RIJNDAEL_128
而不是 MCRYPT_RIJNDAEL_256
,第二个问题是 AES-256 通过提供 32 字节密钥而不是 AES-128 而被使用一个 16 字节的密钥。
下面提供了正确的实现:
final class AESEncrypter
{
// The key
const KEY = 'F40E2A9E22150793C6D0CA9E316FEA42';
// The IV
const IV = '5C354934224F698E';
/**
* Encrypt a string
*
* @access public
* @static
* @param string $string
* @return string
*/
public static function encrypt($string)
{
return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, self::getMySQLKey(self::KEY), self::getPaddedString($string), MCRYPT_MODE_CBC, self::IV);
}
/**
* Decrypt a string
*
* @access public
* @static
* @param string $string
* @return string
*/
public static function decrypt($string)
{
return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, self::getMySQLKey(self::KEY), $string, MCRYPT_MODE_CBC, self::IV), "\x00..\x10");
}
/**
* Get MySQL key
*
* @access public
* @static
* @param string $key
* @return string
*/
public static function getMySQLKey($key)
{
// The new key
$new_key = str_repeat(chr(0), 32);
// Iterate over the key and XOR
for ($i = 0, $l = strlen($key); $i < $l; ++$i)
{
$new_key[$i % 32] = $new_key[$i % 32] ^ $key[$i];
}
// Return the new key
return $new_key;
}
/**
* Get padded string
*
* @access public
* @static
* @param string $string
* @return string
*/
public static function getPaddedString($string)
{
return str_pad($string, (16 * (floor(strlen($string) / 16) + 1)), chr(16 - (strlen($string) % 16)));
}
}
举个例子:
// PHP, gives mwVraDh/7jG3BvPJyYqgxY6Ca8CTRN5JHvwPGeV8Vd0=
base64_encode(AESEncrypter::encrypt('michael@example.com'))
// MySQL, gives mwVraDh/7jG3BvPJyYqgxY6Ca8CTRN5JHvwPGeV8Vd0=
SELECT TO_BASE64(AES_ENCRYPT('michael@example.com', 'F40E2A9E22150793C6D0CA9E316FEA42', '5C354934224F698E'))
AES 是 Rijndael 的子集,因此要将其用于 AES,必须 select 128 位的块大小和 128、192 或 256 位的密钥大小。
MCRYPT_RIJNDAEL_256 指定块大小为 256 位,而不是密钥大小,类似地 MCRYPT_RIJNDAEL_128 指定块大小为 128 位,而不是密钥大小。这是一个常见的混淆。
此 Rijndael auto 实现 select 的密钥大小基于传递的密钥,因此使用所需大小完全正确的密钥非常重要。
另请注意,iv 长度与块大小相同,因此对于 AES,它的长度为 128 位。
这是一个很好的例子,说明命名不当会导致不幸的后果。
使用 this 文章作为指南,我能够在 PHP 中成功复制 MySQL 的 aes-128-ecb:
final class Encryption
{
// The key
const KEY = '36F3D40A7A41A827968BE75A87D60950';
/**
* Encrypt a string
*
* @access public
* @static
* @param string $string
* @return string
*/
public static function encrypt($string)
{
return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, self::getMySQLKey(self::KEY), self::getPaddedString($string), MCRYPT_MODE_ECB);
}
/**
* Decrypt a string
*
* @access public
* @static
* @param string $string
* @return string
*/
public static function decrypt($string)
{
return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, self::getMySQLKey(self::KEY), $string, MCRYPT_MODE_ECB), "\x00..\x10");
}
/**
* Get MySQL key
*
* @access public
* @static
* @param string $key
* @return string
*/
public static function getMySQLKey($key)
{
// The new key
$new_key = str_repeat(chr(0), 16);
// Iterate over the key and XOR
for ($i = 0, $l = strlen($key); $i < $l; ++$i)
{
$new_key[$i % 16] = $new_key[$i % 16] ^ $key[$i];
}
// Return the new key
return $new_key;
}
/**
* Get padded string
*
* @access public
* @static
* @param string $string
* @return string
*/
public static function getPaddedString($string)
{
return str_pad($string, (16 * (floor(strlen($string) / 16) + 1)), chr(16 - (strlen($string) % 16)));
}
}
举个例子:
// PHP, gives CJI+zJyviQI7GgSCLGMNsqsXq2MDKC3a9FIG3wDrE8Y=
base64_encode(Encryption::encrypt('michael@example.com'))
// MySQL, gives CJI+zJyviQI7GgSCLGMNsqsXq2MDKC3a9FIG3wDrE8Y=
SELECT TO_BASE64(AES_ENCRYPT('michael@example.com', '36F3D40A7A41A827968BE75A87D60950'));
但是,我想更新为使用 aes-256-cbc,但遇到了困难。我首先将 MCRYPT_RIJNDAEL_128
替换为 MCRYPT_RIJNDAEL_256
,将 MCRYPT_MODE_ECB
替换为 MCRYPT_MODE_CBC
,并使用 KEY
常量作为初始化向量:
/**
* Encrypt a string
*
* @access public
* @static
* @param string $string
* @return string
*/
public static function encrypt($string)
{
return mcrypt_encrypt(MCRYPT_RIJNDAEL_256, self::getMySQLKey(self::KEY), self::getPaddedString($string), MCRYPT_MODE_CBC, self::KEY);
}
/**
* Decrypt a string
*
* @access public
* @static
* @param string $string
* @return string
*/
public static function decrypt($string)
{
return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, self::getMySQLKey(self::KEY), $string, MCRYPT_MODE_CBC, self::KEY), "\x00..\x10");
}
问题是我现在从 PHP 和 MySQL 得到不同的值:
// PHP, gives XSRfnrl05CE7JIHCvfhq6D67O0mAW2ayrFv2YkjFVYI=
base64_encode(Encryption::encrypt('michael@example.com'))
// MySQL, gives lTLT4MRXcHnOAsYjlwUX4WVPHgYvyi6nKC4/3us/VF4=
SELECT TO_BASE64(AES_ENCRYPT('michael@example.com', '36F3D40A7A41A827968BE75A87D60950', '36F3D40A7A41A827968BE75A87D60950'));
我不确定从哪里开始,所以任何帮助将不胜感激。
并确认 MySQL 确实使用了正确的加密方法:
// aes-256-cbc
SELECT @@session.block_encryption_mode
由于 eggyal nor Artjom B. 都没有选择通过他们的解决方案提供答案,我将代表他们这样做。
第一个问题是 AES-256 仍然使用 MCRYPT_RIJNDAEL_128
而不是 MCRYPT_RIJNDAEL_256
,第二个问题是 AES-256 通过提供 32 字节密钥而不是 AES-128 而被使用一个 16 字节的密钥。
下面提供了正确的实现:
final class AESEncrypter
{
// The key
const KEY = 'F40E2A9E22150793C6D0CA9E316FEA42';
// The IV
const IV = '5C354934224F698E';
/**
* Encrypt a string
*
* @access public
* @static
* @param string $string
* @return string
*/
public static function encrypt($string)
{
return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, self::getMySQLKey(self::KEY), self::getPaddedString($string), MCRYPT_MODE_CBC, self::IV);
}
/**
* Decrypt a string
*
* @access public
* @static
* @param string $string
* @return string
*/
public static function decrypt($string)
{
return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, self::getMySQLKey(self::KEY), $string, MCRYPT_MODE_CBC, self::IV), "\x00..\x10");
}
/**
* Get MySQL key
*
* @access public
* @static
* @param string $key
* @return string
*/
public static function getMySQLKey($key)
{
// The new key
$new_key = str_repeat(chr(0), 32);
// Iterate over the key and XOR
for ($i = 0, $l = strlen($key); $i < $l; ++$i)
{
$new_key[$i % 32] = $new_key[$i % 32] ^ $key[$i];
}
// Return the new key
return $new_key;
}
/**
* Get padded string
*
* @access public
* @static
* @param string $string
* @return string
*/
public static function getPaddedString($string)
{
return str_pad($string, (16 * (floor(strlen($string) / 16) + 1)), chr(16 - (strlen($string) % 16)));
}
}
举个例子:
// PHP, gives mwVraDh/7jG3BvPJyYqgxY6Ca8CTRN5JHvwPGeV8Vd0=
base64_encode(AESEncrypter::encrypt('michael@example.com'))
// MySQL, gives mwVraDh/7jG3BvPJyYqgxY6Ca8CTRN5JHvwPGeV8Vd0=
SELECT TO_BASE64(AES_ENCRYPT('michael@example.com', 'F40E2A9E22150793C6D0CA9E316FEA42', '5C354934224F698E'))
AES 是 Rijndael 的子集,因此要将其用于 AES,必须 select 128 位的块大小和 128、192 或 256 位的密钥大小。
MCRYPT_RIJNDAEL_256 指定块大小为 256 位,而不是密钥大小,类似地 MCRYPT_RIJNDAEL_128 指定块大小为 128 位,而不是密钥大小。这是一个常见的混淆。
此 Rijndael auto 实现 select 的密钥大小基于传递的密钥,因此使用所需大小完全正确的密钥非常重要。
另请注意,iv 长度与块大小相同,因此对于 AES,它的长度为 128 位。
这是一个很好的例子,说明命名不当会导致不幸的后果。