How to encrypt files with Ruby? - ruby

I need to write a simple tool which encrypts/decrypts files.
I guess the best way is to use OpenSSL:
Generate a key:
openssl rand -base64 2048 > secret_key
Encrypt a file:
openssl aes-256-cbc -a -e -in file -out file.enc -k secret_key
Decrypt a file:
openssl aes-256-cbc -d -in file.enc -out file -k secret_key
Is there an easy way to implement this in Ruby? Is there a better way to do that? Using PGP maybe?

Ruby's OpenSSL is a thin wrapper around OpenSSL itself and provides almost all the functionality that OpenSSL itself does, so yes, there's a one-to-one mapping for all your examples:
openssl rand -base64 2048 > secret_key
That's actually exaggerated, you are using AES-256, so you only need a 256 bit key, you are not using RSA here. Ruby OpenSSL takes this decision off your shoulders, it will automatically determine the correct key size given the algorithm you want to use.
You are also making the mistake of using a deterministic IV during your encryption. Why? Because you don't specify an IV at all, OpenSSL itself will default to an IV of all zero bytes. That is not a good thing, so I'll show you the correct way to do it, for more information have a look at the Cipher documentation.
require 'openssl'
# encryption
cipher = OpenSSL::Cipher.new('aes-256-cbc')
cipher.encrypt
key = cipher.random_key
iv = cipher.random_iv
buf = ""
File.open("file.enc", "wb") do |outf|
File.open("file", "rb") do |inf|
while inf.read(4096, buf)
outf << cipher.update(buf)
end
outf << cipher.final
end
end
# decryption
cipher = OpenSSL::Cipher.new('aes-256-cbc')
cipher.decrypt
cipher.key = key
cipher.iv = iv # key and iv are the ones from above
buf = ""
File.open("file.dec", "wb") do |outf|
File.open("file.enc", "rb") do |inf|
while inf.read(4096, buf)
outf << cipher.update(buf)
end
outf << cipher.final
end
end
As you can see, encryption and decryption are fairly similar, so you can probably combine the streaming reading/writing into one shared method and just pass it a properly configured Cipher plus the corresponding file names, I just stated them explicitly for the sake of clarity.
If you'd like to Base64-encode the key (and probably the IV, too), you can use the Base64 module:
base64_key = Base64.encode64(key)

Ruby has an OpenSSL library that should take care of the heavy lifting.

Related

openSSL encrypt with CLI, decrypt with ruby not working

I'm using the openSSL CLI tool to encrypt data, and I want to decrypt that data on another machine in my ruby application. I'm unable to decrypt the data and I'm not sure what isn't lining up between the two. I'd really appreciate any help, below is what I'm running:
CLI Tool
$ export PASS=EncryptDecryptSuperSecretKeyNoOne123456789123456789
$ echo 'someTextIWantToEncrypt' | openssl enc -aes-256-ecb -base64 -e -k $PASS
:> U2FsdGVkX18Gboawkim6n6Ps/yssGaOZkdb1e6I4VyOZDUcqEh2uYdT8jxUydplX
Ruby Application
require 'openssl'
require 'base64'
module Aes
KEY = "EncryptDecryptSuperSecretKeyNoOne123456789123456789"
ALGORITHM = 'AES-256-ECB'
def self.decrypt(msg)
begin
cipher = OpenSSL::Cipher.new(ALGORITHM)
cipher.decrypt
cipher.key = KEY
tempkey = Base64.decode64(msg)
crypt = cipher.update(tempkey)
crypt << cipher.final
return crypt
rescue Exception => exc
puts ("Message for the decryption log file for message #{msg} = #{exc.message}")
end
end
end
When I call Aes.decrypt("U2FsdGVkX18Gboawkim6n6Ps/yssGaOZkdb1e6I4VyOZDUcqEh2uYdT8jxUydplX") it raises an error bad decrypt
What am I missing or not including? ECB does not require an IV. Is there something else?

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.

Encrypt file with Ruby OpenSSL and decrypt with command line

I am automating an OpenSSL file encryption process using Ruby. The files encrypted this way need to be able to be decrypted using OpenSSL on linux command line.
I can encrypt the file using the following Ruby method:
def encrypt_file
cipher = OpenSSL::Cipher.new('aes-256-cbc')
cipher.encrypt
cipher.key = "somelongkeystring"
buf = ""
File.open("file.enc", "wb") do |outf|
File.open("file.zip", "rb") do |inf|
while inf.read(4096, buf)
outf << cipher.update(buf)
end
outf << cipher.final
end
end
end
I need to be able to decrypt the file (file.enc) using the following command:
$ openssl aes-256-cbc -d -in file.enc -out file.zip
However, when I run this I get an error for bad magic number after I type the key from above.
Since I cannot change the decrypt approach (meaning it uses only a password and is entered on linux command line) how can I change my Ruby method to encrypt the file so it can be decrypted this way?
With the help of a similar question in stackoverflow, Im able to achieve it in ruby.
First u've to add an iv thing into ur cipher object, like this
cipher = OpenSSL::Cipher.new('aes-256-cbc')
cipher.encrypt
cipher.iv = "0"*16
cipher.key = "somelongkeystring"
While decryption u've to pass -K and -iv values
openssl aes-256-cbc -d -in file.enc -out file.zip -K <key_hex_code> -iv <iv_hex_code>
U can generate hex codes in ruby like this "string".unpack('H*'). There must be some way to generate hex codes in cli as well.
A solution based on Sumit's answer:
require 'openssl'
class Crypto
def initialize(key, iv, data, cipher='aes-256-cbc')
#key = key
#iv = iv
#cipher = cipher
#data = data
end
def encrypt
c = OpenSSL::Cipher.new(#cipher).encrypt
c.iv = #iv
c.key = #key
c.update(#data) + c.final
end
def decrypt
c = OpenSSL::Cipher.new(#cipher).decrypt
c.iv = #iv
c.key = #key
c.update(#data) + c.final
end
end
iv = '0'*16
key = '1'*32
message = 'My Message'
encrypted = Crypto.new(key,iv,message).encrypt
puts Crypto.new(key,iv,encrypted).decrypt
puts `echo -n '#{encrypted}' | openssl aes-256-cbc -d -K #{key.unpack('H*').first} -iv #{iv.unpack('H*').first}`
# My Message
# My Message
This solutions works for strings, can easily be adapted to files.
I got this to work though not using the Ruby OpenSSL implementation (which I would prefer). If you use the -k flag you can specify a password as the next argument then you don't need to pass anything and can use system to call it.
system("openssl aes-256-cbc -k '#{key}' -in file.zip -out file.enc")
If anyone has an approach using the Ruby OpenSSL implementation I would appreciate it.

openssl equivalent command in ruby

I have to convert certificate file(pem format) into pfx using private key. The command perfectly works in linux is
openssl pkcs12 -export -out certificate1.pfx -inkey myPrivateKey.key -in myCert.pem
Can anyone help me to write equivalent code in ruby using ruby-openssl.
Should be easy:
#!/usr/bin/env ruby
# export-der.rb
require 'openssl'
def export_der(pass, key, cert, out)
key = OpenSSL::PKey.read File.read(key)
cert = OpenSSL::X509::Certificate.new File.read(cert)
name = nil # not sure whether this is allowed
pkcs12 = OpenSSL::PKCS12.create(pass, name, key, cert)
File.open(out, 'w'){|f| f << pkcs12.to_der }
end
puts 'Password:'
export_der($stdin.read, *ARGV)
And call it this way (untested ;-)):
$ ruby export-der.rb myPrivateKey.key myCert.pem certificate1.pfx

Difference in blowfish encryption between perl and ruby

Why is there a difference in blowfish encryption between Crypt::CBC (perl) and OpenSSL (ruby)?
Perl
use Crypt::CBC;
my $cipher = Crypt::CBC->new( -key => 'length32length32length32length32', -cipher => 'Blowfish' );
my $ciphertext = $cipher->encrypt_hex('test');
# ciphertext is 53616c7465645f5f409c8b8eb353823c06d9b50537c92e19
Ruby
require "rubygems"
require "openssl"
cipher = OpenSSL::Cipher::Cipher.new("bf-cbc")
cipher.encrypt
cipher.key = "length32length32length32length32"
result = cipher.update("test") << cipher.final
ciphertext = result.unpack("H*").first
# ciphertext is 16f99115a09e0464
Crypt::CBC seems to be prepending Salted__ to the output by default. Can you explain what is going on that is so different between these? Is there a way to make OpenSSL behave in a similar way to Crypt::CBC?
Crypt::CBC (perl) uses its own method to randomize the salt and initialization vector. Plus in the case of Blowfish it uses a key length of 56 as mentioned above.
Using the perl code from your example:
Perl
use Crypt::CBC;
my $cipher = Crypt::CBC->new( -key => 'length32length32length32length32', -cipher => 'Blowfish' );
my $ciphertext = $cipher->encrypt_hex('test');
# 53616c7465645f5f409c8b8eb353823c06d9b50537c92e19
To decode this using ruby (OpenSSL) requires a little tweaking to extract the key and initialization vector:
Ruby
require 'openssl'
# Hex string to decode(from above)
string = '53616c7465645f5f409c8b8eb353823c06d9b50537c92e19'
# Pack Hex
string = [string].pack('H*')
# Some Config
pass = 'length32length32length32length32'
key_len = 56;
iv_len = 8;
desired_len = key_len + iv_len;
salt_re = /^Salted__(.{8})/
#Extract salt
salt = salt_re.match(string)
salt = salt.captures[0]
data = '';
d = '';
while (data.length < desired_len)
d = Digest::MD5::digest("#{d}#{pass}#{salt}");
data << d;
end
#Now you have extracted your key and initialization vector
key = data.slice(0..key_len-1)
iv = data.slice(key_len .. -1)
# Trim string of salt
string = string[16..-1]
cipher = OpenSSL::Cipher::Cipher.new "bf-cbc"
cipher.decrypt
cipher.key_len = key_len
cipher.key = key
cipher.iv = iv
puts cipher.update(string) << cipher.final
# test
Turns out the blowfish key size defaults are different between these two. OpenSSL defaults to 16, Crypt::Blowfish defaults to 56.
You can override key size in Crypt::CBC by specifying -keysize => n, but unfortunately there does not appear to be a way to override the key size in OpenSSL.
The Openssl library defaults to 16 byte Blowfish key sizes, so for
compatibility with Openssl you may wish to set -keysize=>16
http://metacpan.org/pod/Crypt::CBC
Perl (specify keysize)
my $cipher = Crypt::CBC->new(
-key => 'length32length32length32length32',
-keysize => 16,
-cipher => 'Blowfish'
);

Resources