问题
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, unusedconst unsigned char *data// "password"int datal// 8, length of "password",int count// 1, unusedunsigned 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