AES-256-CFB cuts off message when using `random_iv` - ruby

I have two scripts for encrypting and decrypting messages using AES.
Here's encrypt.rb:
require 'openssl'
require 'base64'
require 'digest'
KEY = 'sekrit_key'
MESSAGE = "My Name is Rabbit and I don't know anything!"
cipher = OpenSSL::Cipher::AES256.new(:CFB)
cipher.encrypt
cipher.key = KEY
# hexdigest the IV to make sure encode64 doesn't fuck up
iv = Digest::MD5.hexdigest(cipher.random_iv)
cipher.iv = iv
encrypted = cipher.update(secret_message)+cipher.final
puts Base64.urlsafe_encode64(iv+'|'+encrypted.encode)
... and decrypt.rb:
require 'openssl'
require 'base64'
KEY = 'sekrit_key'
encrypted_message = STDIN.read.strip
parts = Base64.urlsafe_decode64(encrypted_message ).split('|')
iv = parts[0]
encrypted = parts[1]
decipher = OpenSSL::Cipher::AES256.new(:CFB)
decipher.decrypt
decipher.key = KEY
decipher.iv = iv
message = decipher.update(encrypted)+decipher.final
if message.eql?("My Name is Rabbit and I don't know anything!")
print '.'
else
puts
puts encrypted_message
puts message
end
When I now continuosly run both scripts, My output is cut off quite often!
$ while true; do ruby encrypt.rb | ruby decrypt.rb; done
....
YmZhNDg2ODJjNGZiOGIzZTcyMzAwYzMxZWUwNWI0Y2V8w2aJk930EL3gh3rfQsd2B3xZKy5wjoCzlZoYHBgmv6m51ZwAWQHGtCJoNRg=
My Name is Rabbi
..
YjMwNDQxOGRjMjg4NGEzOThmM2IwNGFiZDBiZTQxZGZ8OfLyjGQGKV3PPUpCvfL08IDuk7M7d3w7fj6F5Rql94jkRdwaCuuMfedqtFk=
My Name is Rabbit and
..
OWUxYzFlZWU5MTc4NGZjYWYxYzZiOGEwOTBjOGMxYzJ8g7I4X_Dt6K9ufByMhLBGlpoYCv8vlR0lTBqP-zS647tmmFh81rXdR8T-UkM=
My Name i
....
# and so on
Why are so many of my messages cut off?
Update: When using a fixed IV in encrypt.rb (eg. substitute cipher.random_iv with KEY) instead of a randomly generated, the problem does not occur.

The problem stems from the fact that you're treating binary data as strings. iv as well as encrypted.encode are binary and you're concatenating them with "|" (a string). Both iv as well as the message may contain the pipe character which will cause problems when splitting. In general, it's best to base64 both parts separately.
Here's working code:
encrypt.rb
require 'openssl'
require 'base64'
require 'digest'
KEY = 'sekrit_key123456' * 2
MESSAGE = "My Name is Rabbit and I don't know anything!"
cipher = OpenSSL::Cipher::AES256.new(:CFB)
cipher.encrypt
cipher.key = KEY
iv = cipher.random_iv
encrypted = cipher.update(MESSAGE)+cipher.final
puts Base64.strict_encode64(iv)+'|'+Base64.strict_encode64(encrypted.encode)
decrypt.rb
require 'openssl'
require 'base64'
KEY = 'sekrit_key123456' * 2 # key needs to be the right length
encrypted_message = STDIN.read.strip
parts = encrypted_message.split('|')
iv = Base64.strict_decode64(parts[0])
encrypted = Base64.strict_decode64(parts[1])
decipher = OpenSSL::Cipher::AES256.new(:CFB)
decipher.decrypt
decipher.key = KEY
decipher.iv = iv
message = decipher.update(encrypted)+decipher.final
if message.eql?("My Name is Rabbit and I don't know anything!")
print '.'
else
puts
puts encrypted_message
puts message
end
Also notice that random_iv already assigns iv to cipher, so you don't need to (see source under http://apidock.com/ruby/v1_9_3_125/OpenSSL/Cipher/random_iv ).

Related

Encrypt empty string

I am using Ruby's Open SSL bindings to do AES-256 encryption. I can encrypt a non-empty string. However, when attempting to encrypt an empty string, Ruby raises an exception complaining that the data must not be empty. How can I encrypt an empty string using Ruby's OpenSSL bindings?
Code to reproduce the problem
require "openssl"
KEY = OpenSSL::Cipher::Cipher.new("aes-256-cbc").random_key
def encrypt(plaintext)
cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
cipher.encrypt
iv = cipher.random_iv
cipher.iv = iv
cipher.key = KEY
ciphertext = cipher.update(plaintext) # <- ArgumentError here
ciphertext << cipher.final
[iv, ciphertext]
end
def decrypt(iv, ciphertext)
cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
cipher.decrypt
cipher.iv = iv
cipher.key = KEY
plaintext = cipher.update(ciphertext)
plaintext << cipher.final
plaintext
end
p decrypt(*encrypt("foo")) # "foo"
p decrypt(*encrypt(""))
# /tmp/foo.rb:11:in `update': data must not be empty (ArgumentError)
# from /tmp/foo.rb:11:in `encrypt'
# from /tmp/foo.rb:27:in `<main>'
Versions
ruby-2.2.2p95
OpenSSL::VERSION is "1.1.0"
Microsoft SQL Server 2014 (12.0.2000.8)
Why do I want to encrypt empty strings?
I am writing an ETL program to migrate data from one database to a SqlServer database. Certain columns from the source database must be encrypted before writing them to the destination database. The source columns may contain any data, including empty strings. The destination columns are usually non-nullable. The destination columns will be decrypted by .net code.
Goal #1: No information about the encrypted field, including whether or not it even exists, should be recoverable without properly decrypting it. An encrypted empty string should be indistinguishable from any other encrypted data.
Goal #2: The .net code that will decrypt these values should not need to handle empty strings specially.
If I can get openssl to encrypt empty strings, I will achieve both of these goals.
Workaround - Don't encrypt empty strings
I could just not encrypt empty strings, passing them through.
def encrypt(plaintext)
return plaintext if plaintext.empty?
...
end
def decrypt(iv, ciphertext)
return ciphertext if ciphertext.empty?
...
end
This has the disadvantages of exposing information, and also of requiring cooperating code to be written on the .net side.
Workaround - Add some constant to the plaintext
I could add some constant string to the plaintext before encryption, and remove it after decryption:
PLAINTEXT_SUFFIX = " "
def encrypt(plaintext)
plaintext += PLAINTEXT_SUFFIX
...
end
def decrypt(iv, ciphertext)
...
plaintext.chomp(PLAINTEXT_SUFFIX)
end
This hides whether the data exists or not, but still requires cooperating .net code.
As suggested by #ArtjomB, it's as simple as not calling Cipher#update with the empty string. The value returned by Cipher#final then properly encrypts an empty string.
require "openssl"
KEY = OpenSSL::Cipher::Cipher.new("aes-256-cbc").random_key
def encrypt(plaintext)
cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
cipher.encrypt
iv = cipher.random_iv
cipher.iv = iv
cipher.key = KEY
ciphertext = ""
ciphertext << cipher.update(plaintext) unless plaintext.empty?
ciphertext << cipher.final
[iv, ciphertext]
end
def decrypt(iv, ciphertext)
cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
cipher.decrypt
cipher.iv = iv
cipher.key = KEY
plaintext = cipher.update(ciphertext)
plaintext << cipher.final
end
p decrypt(*encrypt("foo")) # "foo"
p decrypt(*encrypt("")) # ""
If you can use DBMS provided encryption functions, then, MySQL AES_ENCRYPT, seems to be able to encrypt blank string.
For example:
UPDATE some_table
SET some_column = AES_ENCRYPT('',UNHEX('F3229A0B371ED2D9441B830D21A390C3'));
It's AES-128 by default, I am guessing that will be a problem as you need AES-256. Also, not sure which DBMS you are using and whether that DBMS has encryption functions.

Ruby to decrypt PHP encrypted text

I'd like to use Ruby to decrypt a string that's encrypted with PHP. The same decryption logic works fine with PHP, but the Ruby code returns garbage.
The working PHP example:
<?php
$_RIJNDAEL_KEY_ = "uUxJIpSKMbOQQdtm6Y4rPEXeE9TAKUns";
$_RIJNDAEL_IV_ = "PiToVoRjwlg8UwxUxQKI4w==";
$ciphertext = 'T353/s48iKzAf61b2dCOnqUApYa4xxjye8he4oAtJHyyCKl8sCbI33hfP6IqOsQZEIWeQBCsvy97xwJMPD8RwLG4J0wgX9Ihlti1vMar+5nrLrCR4lAfZcoJopoBt1JVnDAojLW+y0S0y5c4GCdB8YrHzj4jv70dg3yX8DxlAWE=';
$content = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $_RIJNDAEL_KEY_, base64_decode($ciphertext), MCRYPT_MODE_ECB, $_RIJNDAEL_IV_);
echo $content . "\n";
?>
The not-working Ruby example:
require "openssl"
require "digest"
require "uri"
require "base64"
data = Base64.decode64("T353/s48iKzAf61b2dCOnqUApYa4xxjye8he4oAtJHyyCKl8sCbI33hfP6IqOsQZEIWeQBCsvy97xwJMPD8RwLG4J0wgX9Ihlti1vMar+5nrLrCR4lAfZcoJopoBt1JVnDAojLW+y0S0y5c4GCdB8YrHzj4jv70dg3yX8DxlAWE=")
key = Base64.decode64('uUxJIpSKMbOQQdtm6Y4rPEXeE9TAKUns')
iv = Base64.decode64('PiToVoRjwlg8UwxUxQKI4w==')
aes = OpenSSL::Cipher.new('AES-128-ECB')
aes.decrypt
aes.padding = 0
aes.key = key
aes.iv = iv
plain = aes.update(data) + aes.final
puts plain
Could anyone shed some light ? Thanks.
Everything here is correct. But.
It is necessary to use AES-256-ECB in your example.
Here the code which is runnable:
require "openssl"
require "digest"
require "uri"
require "base64"
data = 'T353/s48iKzAf61b2dCOnqUApYa4xxjye8he4oAtJHyyCKl8sCbI33hfP6IqOsQZEIWeQBCsvy97xwJMPD8RwLG4J0wgX9Ihlti1vMar+5nrLrCR4lAfZcoJopoBt1JVnDAojLW+y0S0y5c4GCdB8YrHzj4jv70dg3yX8DxlAWE='
key = "uUxJIpSKMbOQQdtm6Y4rPEXeE9TAKUns"
#iv = 'PiToVoRjwlg8UwxUxQKI4w=='
aes = OpenSSL::Cipher::AES.new(256, :ECB)
# Without padding checksum|4033315172 will be truncated!
aes.padding = 0
aes.decrypt
aes.key = key
#aes.iv = iv
plain = aes.update(data.unpack('m')[0]) + aes.final
After decrypt I've got the following:
=> "date_add|2015-01-28 01:36:17\xC2\xA4id_lang|1\xC2\xA4id_currency|1\xC2\xA4id_guest|12165\xC2\xA4id_connections|10668\xC2\xA4checksum|4033315172\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
Next. Without zero-padding in this example, last character of checksum will be truncated. If possible, don't use EBC mode. Here is why.
And finally, you don't need IV in this example.
Good luck!

Perl AES vs Ruby AES

I cannot get Perl and Ruby to agree on CBC AES:
Perl
use Crypt::CBC;
use MIME::Base64::Perl;
my $cipher = Crypt::CBC->new(
-key => 'd2cb415e067c7b13',
-iv => 'e36dc751d0433f05', #random 16chars!!!!!! shold NOT repeat between requests
-cipher => 'OpenSSL::AES', #this is same as Rijndael
-literal_key => 1,
-header => "none",
-keysize => 16
);
$encypted = $cipher->encrypt("a really really long long text has differrent results???");
$base64 = encode_base64($encypted);
print("Ciphertext(b64): $base64");
$de_base64 = decode_base64($base64);
$decrypted = $cipher->decrypt($de_base64);
$c = $cipher->finish;
Ciphertext(b64): qz4eSQaFkQUkDOyJSbZf5W03HoldwtgvTLq0yJFRViKJnytf3PVSCGW2CYDjO+tRqV20oxeB2VPa
7NqN1TDSNQ==
there's a newline after the 2VPa section and another at the end
Ruby
require 'openssl'
require 'digest/sha2'
require 'base64'
message = "a really really long long text has differrent results???"
cipher = OpenSSL::Cipher.new('aes-128-cbc')
# digest the key, iv and hmac_key so we have 16-byte length
# also, it looks more of a funky password
# prepare cipher
cipher.encrypt
cipher.key = aes_key = "d2cb415e067c7b13"
cipher.iv = aes_iv = "e36dc751d0433f05"
encrypted = cipher.update(message)
encrypted << cipher.final()
b64_encoded = Base64.encode64(encrypted).encode('utf-8') #strict_encode64 guarantees no newlines, encode64 is default
puts "AES Key : '#{aes_key}'"
puts "AES IV : '#{aes_iv}'"
puts "Ciphertext(b64): '#{b64_encoded}'"
Ciphertext(b64): 'qz4eSQaFkQUkDOyJSbZf5W03HoldwtgvTLq0yJFRViKJnytf3PVSCGW2CYDj
O+tRqV20oxeB2VPa7NqN1TDSNQ==
'
Note the newlines chars after CYDj and after ==
I've seen the following: Perl & Ruby exchange AES encrypted information, but I'm not using padding=0
Newlines are not significant in Base64. You got exactly the same result from both languages.
While there should be absolutely no reason to do so, you could make the Perl version return the same string as the Ruby version as follows:
$base64 = encode_base64($encypted, '');
$base64 =~ s/\G.{60}\K/\n/sg;
The encode_base64 function takes a second parameter, called "eol" (end of line) which, by default, is '\n'.
The returned encoded string is broken into lines of no more than 76
characters each and it will end with $eol unless it is empty
Try:
$base64 = encode_base64($encypted, '');
instead.

bad decrypt error in ruby

While executing the cipher.final, It says bad decrypt error. I tried to find out the problem.But,I can't find . Can you tell what is wrong with my code?
Here is my code:
require 'openssl'
require 'base64'
require 'hex_string'
result_h ="4fcd6b1ac843a2f8bf13f2e53dd5c1544fcd6b1ac843a2f8"
key = result_h.to_byte_string
encrypt_str="79994A6EF73DA76C";
cipher = OpenSSL::Cipher.new("DES-EDE3-CBC")
cipher.decrypt
cipher.key = key
data = encrypt_str.to_byte_string
res = cipher.update( data )
res << cipher.final
result_h= res.unpack("H*")[0]
puts result_h.inspect;
Error is:
in `final': bad decrypt (OpenSSL::Cipher::CipherError)
I had a similar problem. In the specific example:
require 'openssl'
require 'base64'
require 'hex_string'
result_h ="4fcd6b1ac843a2f8bf13f2e53dd5c1544fcd6b1ac843a2f8"
key = result_h.to_byte_string
encrypt_str="79994A6EF73DA76C";
cipher = OpenSSL::Cipher.new("DES-EDE3-CBC")
cipher.decrypt
cipher.padding = 0
cipher.key = key
data = encrypt_str.to_byte_string
res = cipher.update( data )
res << cipher.final
result_h= res.unpack("H*")[0]
puts result_h.inspect;
=> "0befe932733d76e6"
If you add cipher.padding = 0 it gives you a result.
I was using a different decryption key as compared to the one I provided for encryption. This was when this error hit me.

Ruby blowfish difference in the last digits

I have some testdata key/text/encrypted from an API provider and am now trying to yield the same encrypted result with the function below, but my result diverts from the provided one in the last 16 of 241 digits. Do you have an idea, what the reason may be?
I ensured, that 'bf-ecb' is the right mode, and experimented with url-encoding, but so far without success.
require 'openssl'
def encrypt(key, data)
cipher = OpenSSL::Cipher::Cipher.new('bf-ecb').send(:encrypt)
cipher.key = key
result = cipher.update(data) << cipher.final
hexed = ''
result.each_byte { |c| hexed << '%02x' % c }
hexed.upcase
end
UPDATE
Also trying to decrypt the example result results in an OpenSSL::Cipher::CipherError "bad decrypt"
If your last 16 digits (=128 bits) are incorrect then it is likely there is a problem with the last block. Probably this is a problem with padding, your encryption is using one form of padding while your decryption is expecting a different padding. I suggest that you explicitly specify the padding at both sides. PKCS5 or PKCS7 are the usual choice. Faulty padding will also explain the "bad decrypt" error message.
It was indeed a problem with the padding. I worked around it with deactivating it and implementing it by myself.
So far it works.
This is how it looks like:
require 'openssl'
def encrypt(key,data)
cipher = OpenSSL::Cipher::Cipher.new "bf-ecb"
cipher.padding = 0
cipher.key = key
cipher.encrypt
enhex(cipher.update padd data)
end
def decrypt(key,data,len)
cipher = OpenSSL::Cipher::Cipher.new "bf-ecb"
cipher.padding = 0
cipher.key = key
cipher.decrypt
(cipher.update dehex(data)).slice(0,len)
end
def enhex(data)
hexed = ''
data.each_byte { |c| hexed << '%02x' % c }
hexed.upcase
end
def dehex(data)
data.scan(/../).map{ |b| b.to_i(16) }.pack('C*')
end
def padd(data)
data + " "*(8 - (data.length % 8))
end
You can simply do the blowfish encryption with ecb cipher mode just like this:
def blowfish_encrypt(key,data)
cipher = OpenSSL::Cipher::Cipher.new("bf-ecb").send :encrypt
cipher.key = key
cipher.update(data) << cipher.final
end
And you don't need to care about padding in this case.

Resources