Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 939
Wayne Conrad Avatar asked Jan 28 '16 18:01

Wayne Conrad


2 Answers

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(""))       # ""
like image 190
Wayne Conrad Avatar answered Nov 14 '22 13:11

Wayne Conrad


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.

like image 32
Wand Maker Avatar answered Nov 14 '22 12:11

Wand Maker