如何让 Ruby AES-256-CBC 和 PHP MCRYPT_RIJNDAEL_128 一起玩得很好

Posted

技术标签:

【中文标题】如何让 Ruby AES-256-CBC 和 PHP MCRYPT_RIJNDAEL_128 一起玩得很好【英文标题】:How to make Ruby AES-256-CBC and PHP MCRYPT_RIJNDAEL_128 play well together 【发布时间】:2010-12-24 04:17:09 【问题描述】:

我正在生成要从 Ruby 堆栈发送到 php 堆栈的数据。我在 Ruby 端使用 OpenSSL::Cipher 库和 PHP 中的 'mcrypt' 库。当我在 Ruby 中使用“aes-256-cbc”(256 位块大小)加密时,我需要在 PHP 中使用 MCRYPT_RIJNDAEL_128(128 位块大小)来解密它。我怀疑 Ruby 代码被破坏了,因为 cipher.iv_len 是 16;我认为应该是 32:

>> cipher = OpenSSL::Cipher::Cipher.new('aes-128-cbc')
=> #<OpenSSL::Cipher::Cipher:0x3067c5c>
>> cipher.key_len
=> 16
>> cipher.iv_len
=> 16
>> cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
=> #<OpenSSL::Cipher::Cipher:0x306de18>
>> cipher.key_len
=> 32
>> cipher.iv_len
=> 16

所以这是我的测试。在 Ruby 方面,首先我生成密钥和 iv:

>> cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
>> cipher.encrypt
>> iv = cipher.random_iv
>> iv64 = [iv].pack("m").strip
=> "vCkaypm5tPmtP3TF7aWrug=="
>> key = cipher.random_key
>> key64 = [key].pack("m").strip
=> "RIvFgoi9xZaHS/0Bp0J9WDRyND6Z7jrd3btiAfcQ8Y0="

然后我使用这些密钥进行加密:

>> plain_data = "Hi, Don, this is a string."
>> cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
>> cipher.encrypt
>> cipher.key = Base64.decode64(key64)
>> cipher.iv = Base64.decode64(iv64)
>> encrypted_data = cipher.update(plain_data)
>> encrypted_data << cipher.final
>> crypt64 = [encrypted_data].pack("m").strip
=> "5gfC/kJcnAV2fJI0haxnLcdraIKWgtu54UoznVxf8K0="

这里是PHP解密:

$ruby_crypt = "5gfC/kJcnAV2fJI0haxnLcdraIKWgtu54UoznVxf8K0=";
$encrypted_data = base64_decode($ruby_crypt);
$key = base64_decode("RIvFgoi9xZaHS/0Bp0J9WDRyND6Z7jrd3btiAfcQ8Y0=");
$iv = base64_decode("vCkaypm5tPmtP3TF7aWrug==");
$result = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $encrypted_data, MCRYPT_MODE_CBC, $iv);
$unencrypt = rtrim($result, "\x00..\x1F");
print "\nUnencrypted token:\n'$unencrypt'\n";

RESULT:
Unencrypted token:
'Hi, Don, this is a string.'

我更喜欢使用较长的块大小。显然我误解了 API。帮忙?

【问题讨论】:

与您的问题无关,但请注意 AES-256:schneier.com/blog/archives/2009/07/another_new_aes.html 谢谢,亚当。也许它是切线的,但你心照不宣的观点(“只要使用 AES-128,反正它更强大”)并非不重要。 【参考方案1】:

我写了一个例子,其他人可能会发现上面讨论的解释:

$ cat publisher.rb

#!/usr/bin/env ruby

require 'openssl'
require 'base64'

key = '7fc4d85e2e4193b842bb0541de51a497'

cipher = OpenSSL::Cipher::Cipher.new('aes-128-cbc')
cipher.encrypt()
iv = cipher.random_iv

cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
cipher.encrypt()
cipher.key = key
cipher.iv = iv
crypt = cipher.update('This is my text')
crypt << cipher.final()

puts [Base64.encode64(crypt).strip(), Base64.encode64(iv).strip()].join('|')

$ cat consumer.php

$key256 = '7fc4d85e2e4193b842bb0541de51a497';

$fd = fopen("php://stdin", "r");
$tokens = '';
while (!feof($fd))
  $tokens .= fread($fd, 1024);
fclose($fd);

$tokens = explode('|', trim($tokens));
$crypt = $tokens[0];
$iv = $tokens[1];

$crypttext = base64_decode($crypt);
$iv = base64_decode($iv);
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key256, $crypttext, MCRYPT_MODE_CBC, $iv);

print $decrypted ."\n";

要测试它,从命令行尝试:

$ ruby​​ 发布者.rb | php消费者.php

这是我的文字

【讨论】:

【参考方案2】:

我不懂 PHP,但是阅读侧边栏上的相关问题,我看到了Converting Ruby AES256 decrypt function to PHP。这包括对this page 的引用,指出MCRYPT_RIJNDAEL_128 中的128 是指加密的块大小,而不是密钥大小。您会注意到您在 ruby​​ 和 PHP 之间传递的密钥大小在这两种情况下都是 256 位。换句话说,这似乎是预期的行为,并且您已经在使用较大的密钥。

#!/usr/bin/ruby
require 'base64'

puts((Base64.decode64("RIvFgoi9xZaHS/0Bp0J9WDRyND6Z7jrd3btiAfcQ8Y0=").length * 8).to_s)

HTH

【讨论】:

啊哈!谢谢,艾登。我已经阅读了上面的“Converting Ruby ... to PHP”文章,但是错过了第二篇文章的链接,这正是我所需要的。阅读这表明我实际上误解了 API; AES 块大小始终为 16 字节。那么“aes-128-cbc”和“aes-256-cbc”中的“128”和“256”指的是密钥大小,块大小总是128(16字节),这完全解释了这个行为.再次感谢。 ...对不起,我还应该注意到,相反,PHP MCRYPT_RIJNDAEL_128 和 MCRYPT_RIJNDAEL_256 中的“128”和“256”是指块大小,密钥大小是根据密钥计算的已提供。 总结一下,对于那些在家玩的人来说——Rijndael 既有可变的块大小,也有独立可变的密钥大小。 AES 是 Rijndael 的子集,具有固定的块大小(128 位)和两种密钥大小(128 位和 256 位)可供选择。【参考方案3】:

我遇到了麻烦,因为 PHP 使用的密码小于 8 个字符。在这种情况下,需要添加 0,以使其与 PHP 兼容:

mcrypt-encrypt 手册页 "钥匙

用于加密数据的密钥。如果它小于所需的密钥大小,则用 '\0' 填充。最好不要将 ASCII 字符串用作键。 http://php.net/manual/en/function.mcrypt-encrypt.php 建议使用 mhash 函数从字符串创建密钥。"

require 'openssl'
cipher = OpenSSL::Cipher.new('DES-ECB')
cipher.encrypt
key =  'passwrd'[0...7].ljust(8, 0.chr)  #Pad the key smaller than 8 chars
cipher.key = key
encrypted = cipher.update('33')
encrypted << cipher.final
dec = Base64.encode64(encrypted).strip()

【讨论】:

【参考方案4】:

让我给你看一些代码。

PHP 代码:

$privateKey = "1234567890123456"; # the size is 16.
$data = "hello";
$iv = "0123456789012345";

$result = mcrypt_encrypt(
  MCRYPT_RIJNDAEL_128, $privateKey, $data, MCRYPT_MODE_CBC, $iv
)

$base64str = base64_encode($result);
$base64str = str_replace("+", "-",  $base64str);
$base64str = str_replace("/","_",  $base64str);

# => f-WffBXnf122NcVBUZ6Rlg==

Ruby 代码:

require 'base64'
require 'openssl'

private_key = "1234567890123456"
data = "hello"
iv = "0123456789012345"

cipher = OpenSSL::Cipher::AES.new(128, :CBC) 
cipher.encrypt

cipher.padding = 0 # we must disable padding in ruby.
cipher.key = private_key
cipher.iv = iv
block_size = cipher.block_size

# Add padding by yourself.
data = data + "\0" * (block_size - data.bytesize % block_size)
result = cipher.update(data) + cipher.final

Base64.urlsafe_encode64(result)
# ==> f-WffBXnf122NcVBUZ6Rlg==

如您所见,我在 ruby​​ 中使用 AES-128,因为 private_key 的大小为 16。 因此,如果private_key 的大小为 32,则必须使用 AES-256。

公式:size_of_private_key * 8.

【讨论】:

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

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

使用 Java 解密 AES256 CBC

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

如何在 PHP 中进行 AES256 解密?

php openssl aes-256-cbc key长度自动匹配了128的长度,为啥

错误的加密(QT c++ OpenSSL AES 256 CBC)