如何复制MySQL';s aes-256-cbc


How to replicate MySQL's aes-256-cbc in PHP

以本文为指导,我成功地在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_256替换MCRYPT_RIJNDAEL_128,用MCRYPT_MODE_CBC替换MCRYPT_MODE_ECB,并使用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

AES是Rijndael的子集,因此要将其用于AES,必须选择128位的块大小和128、192或256位的密钥大小。

MCRYPT_RIJNDAEL_256指定256位的块大小,而不是密钥大小,类似地,MCRYPT_ RIJNDAEL_128指定128位的块尺寸,而不是关键字大小。这是一个常见的困惑。

Rijndael的这种实现基于传递的密钥自动选择密钥大小,因此使用正确所需大小的密钥非常重要。

还应注意,iv长度与块大小相同,因此对于AES而言,其长度为128位。

这是一个很好的例子,说明糟糕的命名会带来不幸的后果。

由于eggyal和Artjom B.都没有选择用他们的解决方案提供答案,我将代表他们这样做。

第一个问题是AES-256仍然使用MCRYPT_RIJNDAEL_128而不是MCRYPT_RIJNDAEL_256,第二个问题是通过提供32字节密钥而不是16字节密钥,在AES-128上使用AES-256。

下面提供了一个正确的实现:

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'))