Question

Dwolla permits an app to solicit and store a user's PIN as a form of pre-authorization, but requires that it be encrypted. From the TOS:

PIN(s) must be encrypted in transit and at rest (this includes any and all backup mediums) using FIPS 140-2 standards (at a minimum)

Normally, I'd use Bcrypt to encrypt (actually, make a secure hash. Neil Slater, thanks for the correction) something (using bcrypt-ruby gem), such as a password. But if I encrypt with Bcrypt, then I'd have to transmit the hash, and of course that won't match what Dwolla is expecting and the PIN will be rejected.

How do you encrypt the PIN and unencrypt it for secure transmittal?

UPDATE:

One of the answers in the question that Andrew links to below referenced OpenSSL:Cipher, and using that I can encrypt the PIN with the below code. But remaining questions then are:

  1. How should I store the key, iv (initialization vector), and cipher? Is it secure to save as environment variables, or would it be better to put in a database table in a secure hash?
  2. Does the below code make sense as a way to encrypt the PIN?
  3. Since I don't have a public key from Dwolla, what's the best way to transmit it?

pin = "1111" # this is what needs to be encrypted

#encryption:
cipher = OpenSSL::Cipher.new('AES-128-CBC') #=> #<OpenSSL::Cipher:0x00000100ef09d8>
cipher.encrypt
key = cipher.random_key #=> odd characters...
iv = cipher.random_iv #=> odd characters...
encrypted = cipher.update(pin) + cipher.final #=> odd characters...

#dcryption: 
decipher = OpenSSL::Cipher::AES.new(128, :CBC)
decipher.decrypt
decipher.key = key
decipher.iv = iv
plain = decipher.update(encrypted) + decipher.final

puts plain == pin #=> true
Was it helpful?

Solution

So this is what I've found out. In Rails, generate the key just once and store as an environment variable (and when you deploy encrypt it). Generate a new iv (initialization vector) for each pin. Store the iv and the encrypted pin in the database.

You may want to convert the encrypted PIN and the IV to UTF8 in order to successfully save without changing how you set up your database. (Be default, they'll be generated as ASCII 8-bit).

Here is one way to do it inside your User model, but you may want to refactor since these are large methods:

def dwolla_pin   # => this is to decrypt the PIN in order to use it
    unless encrypted_dwolla_pin.nil?
      decipher = OpenSSL::Cipher::AES.new(128, :CBC)
      decipher.decrypt
      decipher.key = ENV["ENCRYPT_KEY"]

      # Convert IV from UTF8 (as stored) back to ASCII-8bit (for OpenSSL)
      utf8_iv = self.iv_for_pin
      decipher.iv = Base64.decode64(utf8_iv.encode('ascii-8bit'))

      # Convert PIN from UTF8 (as stored) back to ASCII-8bit (for OpenSSL)
      utf8_pin = self.encrypted_dwolla_pin
      ascii_pin = Base64.decode64(utf8_pin.encode('ascii-8bit'))

      dwolla_pin ||= decipher.update(ascii_pin) + decipher.final
    end
  end

  def dwolla_pin=(new_pin)  # => this is to encrypt the PIN in order to store it
    return false unless valid_pin?(new_pin)
    cipher = OpenSSL::Cipher.new('AES-128-CBC')
    cipher.encrypt
    cipher.key = ENV["ENCRYPT_KEY"]

    # Create IV and convert to UTF-8 for storage in database
    iv = cipher.random_iv
    utf8_iv = Base64.encode64(iv).encode('utf-8')
    self.update_attribute(:iv_for_pin, utf8_iv)

    # Encrypt PIN and convert to UTF-8 for storage in database
    encrypted_pin = cipher.update(new_pin) + cipher.final
    utf8_pin = Base64.encode64(encrypted_pin).encode('utf-8')
    self.update_attribute(:encrypted_dwolla_pin, utf8_pin)
  end

  def valid_pin?(pin)  # => Here I'm just checking to make sure the PIN is basically in the right format
    pin.match(/^\d{4}/) && pin.length == 4
  end

"Secure transit" means SSL for usage and SSH for deployment. If deploying to Heroku then already using SSH, but for SSL you will need to buy from your DNS host wildcard cert and the ssl endpoint on Heroku.

Does anyone have anything to add to this?

OTHER TIPS

I'd use public/private key encryption in a case like this. Not an expert on Ruby, but this link might help:

Ruby: file encryption/decryption with private/public keys

If your pin is being sent externally then you'd need the end-users public key to encrypt. If this isn't possible then you could use a mixture of asynmmetric (public/private) and symmetric algorithms - basically what SSH does.

http://en.wikipedia.org/wiki/Secure_Shell

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top