Decrypting salted AES file generated on command line with Ruby - ruby

I would like to decrypt a text file within a ruby 2.1 script which was previously encrypted using OpenSSL's commandline tools:
openssl enc -aes-256-cbc -a -salt -in my_file
As seen in the command, the file is AES-256-CBC encrypted, salted and base64 encoded.
The password is known, but not the IV nor the key, which are required to follow this code snippet, taken from the ruby documentation:
decipher = OpenSSL::Cipher::AES.new 256, :CBC
decipher.decrypt
decipher.key = key
decipher.iv = iv
plain = decipher.update(encrypted_text) + decipher.final
While trying to find an answer, I found the gem AESCrypt gem which supposedly simplifies en- and decrypting, yet the currently released version is not compatible with ruby 2.1.
Looking at it's source code, I found that the key was retrieved by digesting the password, and the IV is just left as nil.
So I tried to get the following running:
encoded_and_encrypted_text = File.read my_file_path
encrypted_text = Base64.decode64 encoded_and_encrypted_text.to_s.strip
decipher = OpenSSL::Cipher::AES.new 256, :CBC
decipher.decrypt
decipher.key = OpenSSL::Digest::SHA256.new(my_password).digest
plain_text = decipher.update(encrypted_text) + decipher.final
But this results in OpenSSL::Cipher::CipherError: bad decrypt.
Do I need to somehow specifically handle that the file is salted? I have read in the OpenSSL documentation for the enc function that the IV, if not specified while encrypting the file, is generated from the password. Do I need to manually reconstruct the IV somehow?
Any advice would be highly appreciated :)

OpenSSL uses a custom header and key derivation routine. Security.SE has a good description of the header and the docs for EVP_BytesToKey describe the key derivation.
We can modify your code to use this weird and somewhat broken key derivation as follows:
encoded_and_encrypted_text = File.read my_file_path
encrypted_text = Base64.decode64 encoded_and_encrypted_text.to_s.strip
header = encrypted_text[0,8]
salt = encrypted_text[8,8]
payload = encrypted_text[16..-1]
decipher = OpenSSL::Cipher::AES.new 256, :CBC
decipher.decrypt
D_1 = OpenSSL::Digest::MD5.new(my_password + salt).digest
D_2 = OpenSSL::Digest::MD5.new(D_1 + my_password + salt).digest
D_3 = OpenSSL::Digest::MD5.new(D_2 + my_password + salt).digest
decipher.key = (D_1 + D_2)
decipher.iv = D_3
plain_text = decipher.update(payload) + decipher.final

Related

In Ruby, how can I use OpenSSL::PKCS7's encrypt + decrypt functions?

Can anyone advise how to encrypt + decrypt a message in Ruby using OpenSSL::PKCS7? (I'm not trying to sign + verify. That much I can do. I need to write tests for some existing code which calls OpenSSL::PKCS7#decrypt.)
I've pored over what little documentation I can find online, but to no effect. When I call decrypt, I get the following on stderr:
example.rb:13:in `decrypt': decrypt error (OpenSSL::PKCS7::PKCS7Error)
require 'openssl'
# Create a key & self-signed certificate
key = OpenSSL::PKey::RSA.new 1024
cert = OpenSSL::X509::Certificate.new
cert.public_key = key.public_key
cert.not_before = Time.now
cert.not_after = Time.now + 2**12
cert.sign key, OpenSSL::Digest.new('SHA256')
# Encrypt & decrypt
ciphertext = OpenSSL::PKCS7.encrypt([cert], "plaintext").to_pem
decrypted = OpenSSL::PKCS7.new(ciphertext).decrypt(key, cert) # => error
I've furthermore experimented with specifying a cipher and flags for the OpenSSL::PKCS7::encrypt call, but to no effect.
I'm using Ruby 2.6.9.

AES-128-GCM does not seem to check for authentication

If I understand GCM mode correctly, it is supposed to provide not only encryption, but also authentication of the ciphertext. However when I use Ruby's OpenSSL implementation to encrypt data with AES-256-GCM mode, it will happily decrypt the data even if I tamper with the auth_tag. Am I missing something here or is the implementation indeed broken?
require 'openssl'
# ALICE encrypts some secret data
data = 'secret'
cipher = OpenSSL::Cipher.new('aes-128-gcm')
cipher.encrypt
key = cipher.random_key
iv = cipher.random_iv
cipher.auth_data = 'auth_data'
ciphertext = cipher.update(data) + cipher.final
auth_tag = cipher.auth_tag
# EVE tampers with the auth tag, e.g. dropping the last 10 bytes
auth_tag = auth_tag[0..-11]
# BOB decrypts the ciphertext
cipher = OpenSSL::Cipher.new('aes-128-gcm')
cipher.decrypt
cipher.key = key
cipher.iv = iv
cipher.auth_tag = auth_tag
cipher.auth_data = 'auth_data'
data = cipher.update(ciphertext) + cipher.final
# BOB is very sad because no error is raised!
I am using OS X built in OpenSSL version:
% openssl version
OpenSSL 0.9.8zg 14 July 2015
GCM supports multiple sizes for the authentication tag. The authentication tag is shortened in these versions by removing bytes from the right. That's exactly what your attacker seems to be doing.
Now the API is dangerously unstable. First of all, (16 - 10) * 8 = 48, which is not a valid size for AES-GCM (according to NIST SP 800-38D. Furthermore, the authentication tag size should be an input or configuration parameter of the cipher. At the very least the API should warn users to check the authentication tag size themselves instead of allowing just any authentication tag input.
So yes, you are missing something and yes, I would say the implementation - or at the very least the documentation - is broken; good catch.

As a NodeJS user, how do I use the ruby OpenSSL library for crypto?

I'm Node developer, but every once in awhile I get to play around with ERB templates. I really love pulling out as much ruby as I can in these templates when I can and this idea caught my eye especially.
I have this configuration value, which should be encrypted, but is coming in plain text. The program would decrypt it like so:
var crypto = require('crypto');
var decipher = crypto.createDecipher('aes256', 'e20jhciwjf90u2r9u9ujj');
var decrypted = crypto.update('4ufujj90u19ru90u109u') + crypto.final();
I was wondering how I might go about creating an encrypted string for the above to decrypt using ruby?
So far I have:
require 'openssl'
cipher = OpenSSL::Cipher::Cipher.new('aes256');
cipher.key= 'e20jhciwjf90u2r9u9ujj'
encrypted = cipher.update('my cat is yellow and very pretty.') + cipher.final
Two problems I have:
I often get a Key length not long enough error on the ruby side.
Ruby outputs a bunch of crazy hex, whereas node seems to always take/want utf8.
Am I encrypting/decrypting safely?
Is there a way to universally translate/work laterally with these two APIs?
AES-256 uses a key of 256bits, and by default ruby uses utf-8 encoding, so each ansi character is 8bit long. So the key string must be 32 bytes.
Explicitly use an aes mode (e.g. aes-256-cbc).
Set the same iv(initial vector) on both sides
I finally succeeded with the above methods.
Here is my code:
Ruby side:
require 'openssl'
require 'base64'
cipher = OpenSSL::Cipher.new('aes-256-cbc')
cipher.encrypt
cipher.iv = 'a'*16;
cipher.key = '01234567890123456789012345678901' # should be 32 characters, 32*8=256 bits
enc = Base64.strict_encode64(cipher.update('01234567890123456789012345678901') + cipher.final)
puts enc
Javascript side:
var encrypted = new Buffer(base64Data, 'base64');
var crypto = require('crypto');
var decipher = crypto.createDecipheriv('aes-256-cbc', '01234567890123456789012345678901', 'aaaaaaaaaaaaaaaa');
var dec = decipher.update(encrypted);
console.log(Buffer.concat([dec, decipher.final()]));

Coldfusion "AES/CBC/PKCS5Padding" decryption in Ruby

I need to decrypt text encrypted using AES/CBC/PKCS5Padding scheme. The encrypted text I got was generated using Coldfusion.
CFML example below:
<table border="1" cellpadding="5" cellspacing="0">
<tr bgcolor="c0c0c0">
<th>Decrypted string</th>
<th>3DESKey</th>
</tr>
<cfset variables.algorithm ="AES/CBC/PKCS5Padding">
<cfset variables.seed ="C610297CE8570750">
<cfset variables.password = "Vza0O49SHpIe/mR4+4jHXhApmKhEyl5O2nzzDxVNQbo=">
<cfset variables.decryptedString = Decrypt(variables.password, generate3DesKey("#variables.seed#"), "#variables.algorithm#", "Base64")>
<cfoutput>
<tr>
<td>#variables.decryptedString#</td>
<td><cfoutput>#generate3DesKey("variables.seed")#</cfoutput></td>
</tr>
</cfoutput>
</table>
Output is:
Decrypted String: Name322big563
3DESKey: QzYxMDI5N0NFODU3MDc1MA==
I tried with ruby:
require 'openssl'
require 'base64'
string = "Vza0O49SHpIe/mR4+4jHXhApmKhEyl5O2nzzDxVNQbo="
def decrypt(cpass)
des = OpenSSL::Cipher::Cipher.new('AES-256-CBC')
des.decrypt
des.key = 'C610297CE8570750'
return des.update(Base64.decode64(cpass)) + des.final
end
decrypted = decrypt(string)
puts "decrypted string: #{decrypted}"
I get key length too short (OpenSSL::Cipher::CipherError)
The problem is I don't know the key but only the seed used C610297CE8570750, because the key returned by the CFML script is base64 but I need a hex key.
I tried also with OpenSSL::Cipher::AES256.new(:CBC) same error.
require 'openssl'
require 'base64'
# decryption
aes = OpenSSL::Cipher::AES256.new(:CBC)
aes.decrypt
aes.padding = 1 # actually it's on by default
aes.key = "QzYxMDI5N0NFODU3MDc1MA=="
aes.iv = "C610297CE8570750"
aes.update(Base64::decode64("Vza0O49SHpIe/mR4+4jHXhApmKhEyl5O2nzzDxVNQbo="))+aes.final
Any idea?
EDIT:
As hinted by #Leigh, need to use AES-128-CBC, so I did this:
require 'openssl'
require 'base64'
string = "Vza0O49SHpIe/mR4+4jHXhApmKhEyl5O2nzzDxVNQbo="
def decrypt(cpass)
des = OpenSSL::Cipher::Cipher.new('AES-128-CBC')
des.decrypt
des.key = 'C610297CE8570750'
return des.update(Base64.decode64(cpass)) + des.final
end
decrypted = decrypt(string)
puts "decrypted string: #{decrypted}"
actually seems to kinda work (...ish).
decrypted string: ▒▒.ϥD▒▒ ▒▒▒▒▒Name322big563
any idea what's still wrong?
(Expanded from comments)
but I need a hex key
Then convert it from base64 to hex. In CF, you can use the BinaryEncode() and BinaryDecode functions:
binaryEncode(binaryDecode("QzYxMDI5N0NFODU3MDc1MA==", "base64"), "hex")
Looks like there are a few other problems:
The CF code generates a 128 bit key, but the ruby code is using AES 256. It needs to use AES 128.
The CF code is generating a random IV. The Ruby code is using a totally different iv. With CBC mode, both sides must use the same iv to get the expected results. "Decrypting with the incorrect IV causes the first block of plaintext to be corrupt ...", which is why your decrypted value is off. To resolve it, the Ruby code should use the same iv that was used to encrypt.
Update:
When CF generates the IV automatically (as it does here), it prepends that IV to the encrypted value:
When ColdFusion creates an IV automatically, it generates a secure,
random IV and prepends this to the encrypted data. When ColdFusion
decrypts the data, this IV is recovered and used. It is
cryptologically important that the IV varies between encryptions. This
is why the encrypted value changes when you repeatedly encrypt the
same string with an algorithm that uses an IV, like
DES/CBC/PKCS5Padding. Unlike the encryption key, it is not necessary
for the IV to be kept secret.
So the IV value can be extracted by removing the first "block" of the encrypted binary. The block size depends on the algorithm. For AES, it is 16. I do not know the exact Ruby code, but in CF you could extract the IV like so:
blockSize = 16;
rawBinary = binaryDecode(encryptedString, "base64");
// IV is always the first block
ivBytes = arraySlice(rawBinary, 1, blockSize);
// Remaining bytes are the encrypted value
dataBytes = arraySlice(rawBinary, blockSize+1, arrayLen(rawBinary)-blockSize);
Unless I'm very much mistaken, this is a problem I encountered years ago.
PHP Encryption Code Converted to ColdFusion

Why can't I make "OpenSSL with Ruby" and "Command line OpenSSL" interoperable?

While trying to setup an interoperable encryption system, I met a weird situation during a light "proof-of-concept".
I wrote the following code in Ruby to:
create an encrypted file from a dummy text file on my file system
decrypt the encrypted file
compare with the original file and check if they are the same
Here is the code:
require 'openssl'
require 'base64'
# Read the dummy file
data = File.read("test.txt")
# Create an encrypter
cipher = OpenSSL::Cipher::AES.new(256, :CBC)
cipher.encrypt
key = "somethingreallyreallycomplicated"
cipher.key = key
# Encrypt and save to a file
encrypted = cipher.update(data) + cipher.final
open "encrypted.txt", "w" do |io| io.write Base64.encode64(encrypted) end
# Create a decrypter
decipher = OpenSSL::Cipher::AES.new(256, :CBC)
decipher.decrypt
decipher.key = key
# Decrypt and save to a file
encrypted_data = Base64.decode64(File.read("encrypted.txt"))
plain = decipher.update(encrypted_data) + decipher.final
open "decrypted.txt", "w" do |io| io.write plain end
# Compare original message and decrypted message
puts data == plain #=> true
Everything works fine, this script outputs "true"
Then I tried to use the openssl command-line to decrypt my file with the following command:
openssl aes-256-cbc -d -a -in encrypted.txt -k somethingreallyreallycomplicated
But I got: bad magic number
How come?
You need to use the -K (upper case) and -iv options on the command line to specify key and IV explicitly as a string of hex digits. If you use -k (lower case), OpenSSL will derive key and IV from the password using a key derivation function. When OpenSSL derives a key, it will also use a "salted" ciphertext format which is incompatible with the plain blockwise CBC you are expecting.
Note that in your Ruby code, you are using the first 256 bits (32 bytes) of an ASCII string directly as a key, which is almost certainly not what you want for a real world application where security is an issue. You should use a (randomly generated) binary key, or derive a key from a password using a key derivation function such as PBKDF2, bcrypt or scrypt.

Resources