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.
Related
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.
I'm working on a Ruby project that is interacting with a webservice that I'm exchanging some encrypted data with.
I am having a very hard time decrypting something I get back from the webservice in Ruby, although in the .NET side, it's working fine, and a number of other web-based or desktop-based tools can deal with this.
The encryption method was 3DES with ECB and no padding.
Below is a test script I have been working on. I've tried everything I can think of to get these strings unpacked correctly, but to no avail.
require 'openssl'
require 'base64'
def cipher(key, encrypted)
key = key.unpack('a2'*32).map{|x| x.hex}.pack('c'*32)
encrypted = encrypted.unpack('a2'*32).map{|x| x.hex}.pack('c'*32)
OpenSSL::Cipher::ciphers.select{|c| c.include? 'des3' }.map do |cipher_name|
begin
cipher = OpenSSL::Cipher.new(cipher_name)
cipher.padding = 0
cipher.decrypt
cipher.key=key
plain = cipher.update(encrypted) + cipher.final
p "Cipher #{cipher_name} success: #{plain} #{plain.class} #{plain.length} #{plain.encoding.to_s}"
plain
rescue => e
p "Cipher #{cipher_name} failed #{e}"
nil
end
end
end
key = '202FA9B21843D7022B6466DB68327E1F'
encrypted = 'ff6f07e270ebd5c0878c67c999d87ebf'
res1 = cipher key, encrypted
key = '49CE85147B24123718AB3F4539AB1A21'
encrypted = '995604ed8016da8897f1875ebd725529'
res2 = cipher key, encrypted
p res1 == res2 ? "SUCCESS" : "FAIL"
# In both cases, the correct output should be '25588015543912470222703296730936'
A 3DES key is 24-bytes, use a full length key.
3DES uses triple encryption with essentially a 24-byte key. 202FA9B21843D7022B6466DB68327E1F is hex encoded 16-byte key.
Try repeating the first 8-bytes of the key:
202FA9B21843D7022B6466DB68327E1F202FA9B21843D702
Some 3DES implementations will repeat 8-bytes of a 16-byte key but relying on such implementation details is not a good idea.
Note: 3DES actually uses a 168-bit key because the LSb of each byte is not used. Further because there are actually three DES calls the security is only 112-bits. Additionally DES has some weak keys. There are two common modes, ede and ded, in an effort to facilitate moving from DES to 3DES thus adding more confusion.
Finally: Move from 3DES to AES in CBC mode with a random IV. Please don't continue poor security practices.
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()]));
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
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