10

这个问题是我上一个问题的延续,关于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​​ 代码优雅地处理最后一个块。

4

2 回答 2

15

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

PKCS 填充通过添加 n 个值为 n 的填充字节来使数据的总长度成为块大小的倍数。始终添加填充,因此如果数据已经是块大小的倍数,则 n 将等于块大小。例如,如果块大小为 8,并且要加密 11 个字节,则将添加 5 个填充字节,值为 5。

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

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

如果您还采用另一种方式(在 Ruby 中加密并使用 mcrypt 解密),则必须在解密后剥离填充字节。

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

于 2009-12-09T00:02:02.543 回答
0

似乎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?"
于 2013-07-26T19:58:25.563 回答