6
votes

Hi so lets assume that client-side has a key that is not transfered via the same channel as the encrypted data.

What I am trying to accomplish is to decrypt the result of Stanford Javascript Crypto Library (sjcl) in ruby. or for a generalisation in any other language that has a crypto library that has support for AES.

Here is what I am doing in javascript:

sjcl.encrypt('stack-password', 'overflow-secret')

And this is what I get in return:

{
  "iv": "Tbn0mZxQcroWnq4g/Pm+Gg",
  "v": 1,
  "iter": 1000,
  "ks": 128,
  "ts": 64,
  "mode": "ccm",
  "adata": "",
  "cipher": "aes",
  "salt": "pMQh7m9Scds",
  "ct": "H6JRpgSdEzKUw2qEO1+HwIzAdxGTgh0"
}

So what I'm actually asking is, which of these parameters I need(assuming the server already has the "stack-password" key) in order to decrypt the secret server-side, and which library should I use? May be having AES decryption library is not enough?

3

3 Answers

3
votes

For those coming here from Google, I managed to use the sjcl library to encrypt on Appcelerator's Titanium and decrypt on Ruby/Rails.

Encrypt (javascript):

var data = SJCL.encrypt('your key here', 
                        'plain text', 
                        { mode: 'gcm', 
                          iv: SJCL.random.randomWords(3, 0) });

The important bit is using a smaller IV.

Decrypt (ruby):

def self.decrypt(h)
  h = HashWithIndifferentAccess.new(JSON.parse(h))

  key = OpenSSL::PKCS5.pbkdf2_hmac('your key here', Base64.decode64(h[:salt]), 1000, h[:ks]/8, 'SHA256')

  puts "Key: #{key.unpack('H*')}"

  puts "Salt: #{Base64.decode64(h[:salt]).unpack('H*')}"

  c = OpenSSL::Cipher.new("#{h[:cipher]}-#{h[:ks]}-#{h[:mode]}")

  c.decrypt

  c.key = key

  c.iv = Base64.decode64(h[:iv])

  puts "IV: #{Base64.decode64(h[:iv]).unpack('H*')}"

  c.auth_data = ""

  c.update(Base64.decode64(h[:ct]))
end
9
votes

The following can not be negotiated a head of time (or hard coded).

  • ct: cipher-text your encrypted data, obviously
  • iv: initialization vector, should be unique and not be reused with the same key for AES-CCM
  • salt: random, and is used to create the key from password with Pbkdf2
  • adata: additional authenticated data, is plain text data that you want to include but ensure that it has not been tampered when using AES-CCM. If you aren't ever going to include any data then you can ignore it (you would have had to pass it with the plaintext in sjcl).

The following you can negotiated a head of time (or hard coded), in fact you shouldn't plug these values in on the server encryption api, when transmitted unauthenticated and expect security. adata wouldn't be a bad place for v, iter, or ks if you wanted it to be changeable based on the client

  • iter: iterations for Pbkdf2, this just needs to be high enough to slow down bruteforce on your password needs to change with the speed of hardware in the future.
  • ks: keysize to know what size key to generate with Pbkdf2, needs to change with the amount of security in future
  • ts: tagsize to know what size authentication tag is part of your cipher text
  • cipher: If you will only support AES, then you can just assume.
  • mode: if you will only support AES-CCM than you can just assume.
  • v: If you will only support one version of sjcl in the future than you can just assume.

With ruby using the OpenSSL library seems like it could work as long as your OpenSSL supports AES-128-CCM puts OpenSSL::Cipher.ciphers to check. And it does support generating keys using pbkdf2, but you do need to use Sha256 digest to be compatible with sjcl, which is again dependent on your version of openssl.

1
votes

Alternatively, I wrote up a translation of SJCL into pure Ruby (at least for the CCM parts). This doesn't require updating OpenSSL, but it's a bit slower.

You can find it at https://github.com/mdp/sjcl_rb

Hope this helps