如何在Ruby中解密由Node弃用的createCipher加密的数据?

时间:2019-07-17 04:54:26

标签: node.js ruby encryption cryptography

我有一些用Node加密的旧数据,需要在Ruby中解密。

问题是,数据已使用现已弃用的方法createCipher加密。此方法使用密码来执行加密。它已被createCipheriv取代,它需要一个32字节的密钥和一个16字节的初始化向量。

在Node中,我可以使用同样不推荐使用的createDecipher解密字符串,该字符串也接受密码。但是,我不知道Ruby中有任何等效的方法,这很有意义,因为现在已知这些方法是不安全的并且已弃用。

Ruby的OpenSSL AES密码正确地需要32字节的密钥和16字节的IV,就像较新的createCipheriv一样,但是我没有这两个,只有createCipher使用的原始密码。 / p>

如何在Ruby中获得类似createCipher / createDecipher的行为?

具体来说,鉴于以下JavaScript ...

const crypto = require('crypto');

const cipher = crypto.createCipher("aes-256-cbc", 'secret password');
cipherText = cipher.update('dummy string', "utf8", "hex") + cipher.final("hex");

console.log(cipherText); // f3051259f83c7ca2ac012a396c4c0848

...如何使用密码'secret password'解密字符串'f3051259f83c7ca2ac012a396c4c0848'并返回Ruby中的输入值'dummy string'

1 个答案:

答案 0 :(得分:3)

createCipher对输入的密码重复执行MD5哈希,以生成32字节的密钥和16字节的初始化向量。之所以弃用它,是因为这不是产生随机IV的安全方法。

这发生在/deps/openssl/openssl/crypto/evp/evp_key.c中Node的C源代码内部。

此函数有很多参数,但是当用于为createCipher('aes-256-cbc', 'password')生成密钥/ iv时,此方法将使用以下参数调用here

  • const EVP_CIPHER *type //“ aes-256-cbc”
  • const EVP_MD *md // EVP_md5()
  • const unsigned char *salt //空,未使用
  • const unsigned char *data //“密码”
  • int datal // 8,“密码”的长度,
  • int count // 1,未使用
  • unsigned char *key //输出密钥缓冲区,
  • unsigned char *iv //输出iv缓冲区

此方法必须产生48个总字节(密钥为32个字节,IV为16个字节),并且首先运行hash = md5(password),产生16个字节。

现在,对于每个后续的16个字节组,它将前16个字节与password连接起来,并再次对其进行哈希处理:hash = md5(hash + password)

有效字节...

    通过md5(password)产生的
  • 0到16
  • 通过md5(md5(password) + password)生成的
  • 17到32
  • 33到48个通过md5(md5(md5(password) + password) + password)生成。

我们可以在Ruby中实现相同的功能以得出正确的密钥,并可以通过密码IV来解密给定的字符串:

require 'digest'
require 'openssl'

def decrypt(cipher_text, password)
  bytes = [ Digest::MD5.digest(password) ]
  bytes << Digest::MD5.digest(bytes.last + password)
  bytes << Digest::MD5.digest(bytes.last + password)

  bytes = bytes.join

  cipher = OpenSSL::Cipher.new('aes-256-cbc')
  cipher.decrypt

  cipher.key = bytes[0...32]
  cipher.iv = bytes[32...48]

  # OpenSSL deals in raw bytes, not the hex-encoded representation
  # 'f3051259f83c7ca2ac012a396c4c0848' => "\xF3\x05\x12Y\xF8<|\xA2\xAC\x01*9lL\bH"
  cipher_text = cipher_text.unpack('a2' * (cipher_text.length / 2)).map(&:hex).pack('c*')

  cipher.update(cipher_text) + cipher.final
end

decrypt('f3051259f83c7ca2ac012a396c4c0848', 'secret password') # => 'dummy string'

为完整起见,这里是等效的encrypt方法,该方法将允许在Node弃用的createDecipher中解密用Ruby加密的数据:

 def encrypt(plain_text, password)
  cipher = OpenSSL::Cipher.new('aes-256-cbc')
  cipher.encrypt

  bytes = [ Digest::MD5.digest(password) ]
  bytes << Digest::MD5.digest(bytes.last + password)
  bytes << Digest::MD5.digest(bytes.last + password)

  bytes = bytes.join

  cipher.key = bytes[0...32]
  cipher.iv = bytes[32...48]

  cipher_text = cipher.update(plain_text) + cipher.final

  # Produce a hex representation
  # "\xF3\x05\x12Y\xF8<|\xA2\xAC\x01*9lL\bH" => 'f3051259f83c7ca2ac012a396c4c0848'
  cipher_text.unpack('C*').map { |byte| byte.to_s(16) }.map { |str| str.rjust(2, "0") }.join
end

encrypt('dummy string', 'secret password') # => "f3051259f83c7ca2ac012a396c4c0848"