第二部分:如何让 Ruby AES-256-CBC 和 PHP MCRYPT_RIJNDAEL_128 一起玩得很好

Posted

技术标签:

【中文标题】第二部分:如何让 Ruby AES-256-CBC 和 PHP MCRYPT_RIJNDAEL_128 一起玩得很好【英文标题】:Part II: How to make Ruby AES-256-CBC and PHP MCRYPT_RIJNDAEL_128 play well together 【发布时间】:2010-12-24 07:06:18 【问题描述】:

这个问题是我上一个问题的延续,关于How to make Ruby AES-256-CBC and php MCRYPT_RIJNDAEL_128 play well together。我现在已经开始工作了,但我仍在努力转向另一个方向。 PHP 生成的密码似乎包含所提供的所有信息,但我无法让 Ruby 代码无错误地对其进行解密。

这是我用来生成密码的 PHP 代码:

$cleartext = "Who's the clever boy?";
$key = base64_decode("6sEwMG/aKdBk5Fa2rR6vVw==\n");
$iv = base64_decode("vCkaypm5tPmtP3TF7aWrug==");
$cryptogram = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $cleartext, MCRYPT_MODE_CBC, $iv);
$result = base64_encode($cryptogram);
print "\n'$result'\n";

RESULT
'JM0OxMINPTnF1vwXdI3XdKI0KlVx210CvpJllFja+GM='

那么这是在 Ruby 中解密的尝试:

>> cipher = OpenSSL::Cipher::Cipher.new('aes-128-cbc')
>> cipher.key = Base64.decode64("6sEwMG/aKdBk5Fa2rR6vVw==\n")
>> cipher.iv = Base64.decode64("vCkaypm5tPmtP3TF7aWrug==")
>> cryptogram = Base64.decode64('JM0OxMINPTnF1vwXdI3XdKI0KlVx210CvpJllFja+GM=')
>> cleartext = cipher.update(cryptogram)
=> "Who's the clever"
>> cleartext << cipher.final
OpenSSL::Cipher::CipherError: bad decrypt
 from (irb):100:in `final'
 from (irb):100

真正令人沮丧的是,有可能从该加密字符串中获取整个明文。重复上述步骤,但在密码中添加一个无意义的填充:

  >> cleartext = cipher.update(cryptogram + 'pad')
  => "Who's the clever boy?\000\000\000\000\000\000\000\000\000\000\000"
  >> cleartext << cipher.final
  OpenSSL::Cipher::CipherError: bad decrypt
   from (irb):119:in `final'
   from (irb):119

在我的实际用例中,明文是结构化的(一个 JSON 字符串,因为你问了),所以我感觉很舒服,我可以告诉使用这个方案并检测加密不佳的输入而不执行 cipher.final。但是,我不能容忍我的代码中出现这种混乱,所以我想了解如何让 ruby​​ 代码优雅地处理最后一个块。

【问题讨论】:

【参考方案1】:

问题在于mcrypt 没有填充最后一个块,而 Ruby 的 OpenSSL 绑定使用默认的 OpenSSL 填充方法,即 PKCS 填充。我无法真正改进 OpenSSL 文档中的描述:

PKCS 填充通过添加 n 填充来工作 值 n 的字节构成总数 数据长度 a 块大小的倍数。填充是 总是添加所以如果数据已经 块大小 n 的倍数将 等于块大小。例如,如果 块大小为 8 和 11 个字节 要加密,然后是 5 个填充字节 值 5 将被添加。

在加密之前,您需要在 PHP 中的明文末尾手动添加适当的填充。为此,请在加密之前通过 PHP 端的 pkcs5_pad 函数传递您的 $cleartext(传递 16 作为块大小)。

function pkcs5_pad ($text, $blocksize)

    $pad = $blocksize - (strlen($text) % $blocksize);
    return $text . str_repeat(chr($pad), $pad);

如果您也采用其他方式(在 Ruby 中加密并使用 mcrypt 解密),则必须在解密后去除填充字节。

旁注: 即使明文已经是块大小的倍数(整个填充块),您也必须添加填充,这样当您解密时,您就知道最后一个块的最后一个字节总是是添加的填充量。否则,您将无法区分具有单个填充字节的明文和恰好以值 0x01 结尾的没有填充字节的明文。

【讨论】:

感谢您的想法,咖啡馆。将文本添加到明文只是创建一个不同的字符串来加密;它不会改变结果。我同意这个问题与两个实现如何处理加密流的最后一个块有关。两种算法的输出是相同的,直到最后一个 32 字节块,其中最后 16 个字节完全不同。我已经对这个问题失去了耐心,所以除非有一些撒玛利亚人过来为我解决这个问题,否则我会选择上面的问题。 添加到明文末尾的文本(填充)必须具有非常特定的形式,这是 Ruby 方面所期望的。我将调查它使用什么填充方法并更新答案。 ...PKCS 填充通过添加值 n 的 n 个填充字节来使 加密 数据的总长度成为块大小的倍数... 虽然经过反思,措辞有点令人困惑,但填充最肯定添加到未加密的数据中。 咖啡馆,我欠你的,首先是道歉,然后是你选择的虚拟美味饮料(如果你在西雅图附近的任何地方,这种饮料可以是非虚拟的)。对我来说令人难以置信的是,您发现并填充了源 - 即使它的长度正好是 0 mod 16 - 也是必需的。谢谢。【参考方案2】:

似乎 PHP \0 在加密之前填充了明文。您可以将 Ruby 设置为禁用填充。

http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/Cipher.html#method-i-padding-3D

这会起作用,但是您必须手动去除填充。

1.9.3p125 :008 > cipher = OpenSSL::Cipher::Cipher.new('aes-128-cbc')
 => #<OpenSSL::Cipher::Cipher:0x0000000561ee78>
1.9.3p125 :009 > cipher.decrypt
 => #<OpenSSL::Cipher::Cipher:0x0000000561ee78>
1.9.3p125 :010 > cipher.padding = 0
 => 0
1.9.3p125 :011 > cipher.key = Base64.decode64("6sEwMG/aKdBk5Fa2rR6vVw==\n")
 => "\xEA\xC100o\xDA)\xD0d\xE4V\xB6\xAD\x1E\xAFW"
1.9.3p125 :012 > cipher.iv = Base64.decode64("vCkaypm5tPmtP3TF7aWrug==")
 => "\xBC)\x1A\xCA\x99\xB9\xB4\xF9\xAD?t\xC5\xED\xA5\xAB\xBA"
1.9.3p125 :013 > cryptogram =  Base64.decode64('JM0OxMINPTnF1vwXdI3XdI2j8NJ8kr+Du0fnkxorNl0=')
 => "$\xCD\x0E\xC4\xC2\r=9\xC5\xD6\xFC\x17t\x8D\xD7t\x8D\xA3\xF0\xD2|\x92\xBF\x83\xBBG\xE7\x93\x1A+6]"
1.9.3p125 :014 > cleartext = cipher.update(cryptogram)
 => "Who's the clever girl?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
1.9.3p125 :015 > cleartext << cipher.final
 => "Who's the clever girl?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"



1.9.3p125 :042 > cleartext.strip
 => "Who's the clever girl?"

【讨论】:

以上是关于第二部分:如何让 Ruby AES-256-CBC 和 PHP MCRYPT_RIJNDAEL_128 一起玩得很好的主要内容,如果未能解决你的问题,请参考以下文章

无法使用来自 AES-256-CBC 的 pgcrypto 解密,但 AES-128-CBC 可以

text 唯一支持的密码是AES-128-CBC和AES-256-CBC,密钥长度正确。

使用 Java 解密 AES256 CBC

如何在iOS Swift 3中加密AES 256 CBC,如laravel encrypt()方法

加密空字符串

密码“aes256-cbc”是必需的,但它不可用