How can I decrypt data encrypted by Node's deprecated createCipher, in Ruby?

北城余情 提交于 2019-12-24 06:13:25

问题


I have some legacy data, encrypted in Node, which I need to decrypt in Ruby.

The problem is, the data was encrypted with a now deprecated method, createCipher. This method uses a password to perform encryption. It has been replaced by createCipheriv, which requires a 32 byte key and a 16 byte initialization vector.

In Node, I can decrypt the string with the similarly deprecated createDecipher, which also accepts a password. However, I'm not aware of any equivalent method in Ruby, which makes sense, since these methods are now known to be insecure and are deprecated.

Ruby's OpenSSL AES Cipher correctly requires a 32 byte key and 16 byte IV like the newer createCipheriv, but I don't have either of these, only the original password I used with createCipher.

How can I get createCipher/createDecipher-like behavior in Ruby?

Specifically, given the following 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

... how can I use the password 'secret password' to decrypt the string 'f3051259f83c7ca2ac012a396c4c0848' and arrive back at the input value 'dummy string' in Ruby?


回答1:


createCipher repeatedly performs MD5 hashing on the input password to generate a 32 byte key and a 16 byte initialization vector. It was deprecated because this is not a secure way of producing a random IV.

This happens down inside the C source of Node, in /deps/openssl/openssl/crypto/evp/evp_key.c.

This function has quite a few arguments, but when used to generate a key/iv for createCipher('aes-256-cbc', 'password'), this method is invoked here, with the following arguments:

  • const EVP_CIPHER *type // "aes-256-cbc"
  • const EVP_MD *md // EVP_md5()
  • const unsigned char *salt // null, unused
  • const unsigned char *data // "password"
  • int datal // 8, length of "password",
  • int count // 1, unused
  • unsigned char *key // output key buffer,
  • unsigned char *iv // output iv buffer

This method must produce 48 total bytes (32 for the key, and 16 for the IV) and it does so by first running hash = md5(password), producing 16 bytes.

Now, for each subsequent set of 16 bytes, it concatenates the previous 16 bytes with the password, and hashes it again: hash = md5(hash + password).

Effectively bytes...

  • 0 to 16 generated via md5(password)
  • 17 to 32 generated via md5(md5(password) + password)
  • 33 to 48 genearted via md5(md5(md5(password) + password) + password).

We can implement the same in Ruby to derive the correct key and IV to decrypt a given string via a password:

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'

For completeness, here is the equivalent encrypt method, which will allow data encrypted in Ruby to be decrypted in Node's deprecate createDecipher:

 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"


来源:https://stackoverflow.com/questions/57068729/how-can-i-decrypt-data-encrypted-by-nodes-deprecated-createcipher-in-ruby

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!