Tuesday, May 14, 2013

Encrypting Database Passwords In Ruby

Most web applications connect to a database, which means that they need to store the database password in a file. While that file should only be readable by the user running the application server, security can be further improved by encrypting the password in the file. The encryption passphrase is then stored in the code of your application. This means that an attacker would not only have to get access to the password file, but also to the application code to decrypt the password. Here's a simple implementation of AES 256 encryption/decryption in Ruby. I've run it in JRuby using Java 1.7, and it requires you to install "Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files" for your JRE. On Mac OS, after extracting the zip file I had to copy US_export_policy.jar and local_policy.jar to /Library/Java/JavaVirtualMachines/jdk1.7.0_21.jdk/Contents/Home/jre/lib/security/.

# file_crypto.rb

require 'openssl'

class FileCrypto
  def initialize(passphrase, iv_passphrase, salt)
    @key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(passphrase, salt, 2000, 32)
    @iv = OpenSSL::PKCS5.pbkdf2_hmac_sha1(iv_passphrase, salt, 2000, 16)
  end

  def read(filepath)
    decrypt(IO.read(filepath))
  end

  def write(filepath, plaintext)
    IO.write(filepath, encrypt(plaintext))
  end

  def encrypt(data)
    cipher = OpenSSL::Cipher::AES256.new(:CBC)
    cipher.encrypt
    cipher.key = @key
    cipher.iv = @iv
    cipher.update(data) + cipher.final
  end

  def decrypt(data)
    cipher = OpenSSL::Cipher::AES256.new(:CBC)
    cipher.decrypt
    cipher.key = @key
    cipher.iv = @iv
    cipher.update(data) + cipher.final
  end
end

Then, you can encrypt test password like this:
require_relative 'file_crypto'

fc = FileCrypto.new(
  'j7U2 k92M%qc mw}f;T@6cAlH',
  'cZd !W7Xf2PMQY#Xprt)n',
  '28Ca kDl^vYuO'
)

fc.write('/tmp/encpassword', 'test password')

And decrypt it back like this:
require_relative 'file_crypto'

fc = FileCrypto.new(
  'j7U2 k92M%qc mw}f;T@6cAlH',
  'cZd !W7Xf2PMQY#Xprt)n',
  '28Ca kDl^vYuO'
)

puts fc.read('/tmp/encpassword')

No comments: