I have a rails application that encrypts (with attr_encrypted) 2 fields in one of the models.
Another part of my process, which is not the web-application needs to perform some tasks using this data (plaintext).
I'm trying to read the stored values from the DB and decrypt them but just can't..
my model looks like this:
class SecretData < ActiveRecord::Base
mysecret = "mylittlesecret"
attr_encrypted :data1, :key=>mysecret, :algorithm => "aes-256-cbc"
attr_encrypted :data2, :key=>mysecret, :algorithm => "aes-256-cbc"
...
end
The DB fields (encrypted_data1 and encrypted_data2) are filled with data but when I try to decode the base64 (attr_encrypted does that by default) and decrypt (I tried with openssl from commandline and using Java) I get "bad magic number" (openssl) or various errors about key length (in Java). I spent a lot of time trying to decrypt those strings but just couldn't find the way.
Here is all the data I have:
encrypted + base64 strings (for data1 and data2) are:
cyE3jDkKc99GVB8TiUlBxQ==
sqcbOnBTl6yy3wwjkl0qhA==
I can decode base64 from both of them and get some byte array. When I try:
echo cyE3jDkKc99GVB8TiUlBxQ== | openssl aes-256-cbc -a -d (and type "mylittlesecret" as the password)
I get: "bad magic number"
When I try the following Java code:
Key key = generateKey();
Cipher c = Cipher.getInstance(ALGO);
c.init(Cipher.DECRYPT_MODE, key);
byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedData);
byte[] decValue = c.doFinal(decordedValue);
String decryptedValue = new String(decValue);
I get "java.security.InvalidKeyException: Invalid AES key length: 14 bytes"
I've tried many variations for the Java code, so it might be that this particular one is a complete mistake..
When I try in ruby:
irb(main):069:0> Encryptor.decrypt(Base64.decode64("cyE3jDkKc99GVB8TiUlBxQ=="), ,key=>'mylittlesecret')
=> "data1-value"
I get the correct value decrypted (as you can see).
I've also noticed that when I try to encrypt the same string in Java and encode in Base64 I get a longer string (after base64). Don't know why but it's probably related..
I thought I should also have a salt/iv with the encrypted value, but I don't see it stored anywhere.. I tried to encrypt the same value twice and got the same output string so it's not a random one.
Does anyone know how does attr_encrypted (it's using ruby's Encryptor) encrypts data and how I should decrypt it externally?
Well, thanks to owlstead I was able to solve this. I'm posting the code in ruby and Java in case someone needs it in the future:
The problem, as owlstead mentioned, is indeed in the EVP_BytesToKey (key generation from a password and salt). Ruby from some reason doesn't use the standard one and therefore Java (or openssl) can't decode.
Here is a ruby implementation that uses a standard method:
def self.encrypt(options)
plaintext = options[:value]
return true if plaintext.blank?
cipher = OpenSSL::Cipher::Cipher.new(@@cipher_type)
cipher.encrypt
iv = cipher.random_iv
salt = (0 ... @@salt_length).map{65.+(rand(25)).chr}.join # random salt
key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(@@password, salt, @@pkbdf_num_iters, cipher.key_len)
cipher.key = key
cipher.iv = iv
enc_data = cipher.update(plaintext)
enc_data << cipher.final
final_data = salt << iv << enc_data
Base64.strict_encode64(final_data)
end
def self.decrypt(options)
ciphertext = options[:value]
return true if ciphertext.blank?
cipher = OpenSSL::Cipher::Cipher.new(@@cipher_type)
cipher.decrypt
cipher_data = Base64.decode64(ciphertext)
salt = cipher_data[0 .. @@salt_length-1]
iv = cipher_data[@@salt_length .. @@salt_length+cipher.iv_len]
enc_data = cipher_data[@@salt_length+cipher.iv_len .. -1] # the rest
key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(@@password, salt, @@pkbdf_num_iters, cipher.key_len)
cipher.key = key
cipher.iv = iv
plaintext = cipher.update(enc_data)
plaintext << cipher.final
plaintext
end
I've set the following parameters: - cipher_type = aes-128-cbc (Java supports only 128 but out of the box. For more than that you need to install some additional packages) - salt_length = 8 - pkbdf_num_iters = 1024
This is the Java method for decoding:
public String decrypt(String ciphertext) throws Exception {
byte[] crypt = Base64.decodeBase64(ciphertext);
// parse the encrypted data and get salt and IV
byte[] salt = Arrays.copyOfRange(crypt, 0, saltLength);
byte[] iv = Arrays.copyOfRange(crypt, saltLength, saltLength + ivLength);
byte[] encryptedData = Arrays.copyOfRange(crypt, saltLength + ivLength, crypt.length);
// generate key from salt and password
SecretKeyFactory f = SecretKeyFactory.getInstance(secretKeyName);
KeySpec ks = new PBEKeySpec(password.toCharArray(), salt, pbkdfNumIters, keyLength);
SecretKey s = f.generateSecret(ks);
Key keySpec = new SecretKeySpec(s.getEncoded(),"AES");
// initialize the cipher object with the key and IV
Cipher cipher = Cipher.getInstance(cipherAlgo);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
// decrypt
byte[] decBytes = cipher.doFinal(encryptedData);
return new String(decBytes);
}
Worked for me.
Hope it helps (or will, to someone..)
Zach