Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Encrypting data with ruby decrypting with node

I want to encrypt some data in a ruby app and then decode it in a nodejs app. I have been trying to get this to work and now I am just trying to encrypt the same piece of data in both languages to get the same result but I can't seem to do it.

//js
var crypto = require('crypto');

var key = crypto.createHash('sha1').update('key').digest('hex');
console.log(key); // a62f2225bf70bfaccbc7f1ef2a397836717377de

var encrypted = "";
var cipher = crypto.createCipher('bf-cbc', key);

encrypted += cipher.update('text');
encrypted += cipher.final('hex');

console.log(encrypted); //outputs 4eafd5542875bd3c

So it looks like I get a hexadecimal string from the encoding.

#ruby
require 'openssl'
require 'digest/sha1'
c = OpenSSL::Cipher::Cipher.new("bf-cbc")
c.encrypt
# your pass is what is used to encrypt/decrypt
c.key = key = Digest::SHA1.hexdigest("key")
p key # a62f2225bf70bfaccbc7f1ef2a397836717377de
e = c.update("text")
e << c.final
p e # 皋?;??

Is there some sort of encoding issue that I am missing. I tried to base64 decode e but that didn't produce the same result as the node app. Any pointers?

UPDATE: So this is as close as a friend and I can get: https://gist.github.com/a880ea13d3b65a21a99d. Sheesh, I just want to encrypt something in ruby and decrypt it in node.

UPDATE2: Alright, the code in this issue gets me a lot of the way there: https://github.com/joyent/node/issues/1395

like image 844
Ryan S Avatar asked Aug 25 '11 14:08

Ryan S


1 Answers

There are several subtle things that make this fail. The most important one - you are not specifying an IV in your code, so a random value will be generated for you. You would notice that you couldn't even decrypt your ciphertext within the same programming language this way.

So you need to provide an explicit IV to both implementations. But before I show you the code, some advice:

Key generation:

Blowfish operates on 64 bit blocks, its key size varies, but OpenSSL (which currently powers both Ruby's and node.js' cipher implementation) uses 128 bit by default, that is 16 bytes.

So your key violates two principles - the first: it's simply too long. It's the hex representation of a SHA-1 hash, which is 20 bytes * 2 = 40 bytes instead of 16. Most of the time this is fine, because the implementation truncates the values appropriately, but that is something you should not depend on.

The second mistake, much more severe, is that you use the hex representation instead of the raw bytes: big security issue! Hex characters are not random at all, so in effect you reduce the entropy of your input to half the length (because the underlying bytes were random).

A secure way to generate random keys is using OpenSSL::Random

key = OpenSSL::Random.random_bytes(cipher_key_len)

A third mistake is to keep your key hard-coded in the sources. It's a bad idea. The least you should do is to store it elsewhere on the file system, where access is tightly restricted. See also my answer to another question. The key should be stored out-of-band and only loaded dynamically within the application.

Cipher:

Blowfish grows old. It's still considered unbroken in the sense that brute-forcing it is the only way to break it. But a search space of 2^64 is not out of reach for resourceful attackers. So you should indeed move on to AES.

Padding:

OpenSSL pads using PKCS5Padding (also known as PKCS7Padding) by default. Ruby profits from this and my bet is node.js utilizes this, too - so you should be safe on this.

Now to the working solution. We need to generate an IV, Blowfish requires it to be 64 bit - 8 bytes. You will need rbytes to get secure random numbers in node. The IV may be hardcoded in your sources (it's public information, no security impact) - but it must be the same on both sides. You should pregenerate a value and use it for both node.js and Ruby.

/*node.js*/

var rbytes = require('rbytes');
var iv = rbytes.randomBytes(8);

/*see advice above - this should be out-of-band*/
var key = rbytes.randomBytes(16);
var encrypted = "";
var cipher = crypto.createCipheriv('bf-cbc', key, iv);

encrypted += cipher.update('text');
encrypted += cipher.final('hex');

Now the Ruby part:

require 'openssl'

c = OpenSSL::Cipher::Cipher.new("bf-cbc")
c.encrypt
# should be out-of-band again
c.key = OpenSSL::Random.random_bytes(16)
# may be public but has to be the same for Ruby and node
iv = OpenSSL::Random.random_bytes(8)
c.iv = iv 
e = c.update("text")
e << c.final
puts e.unpack('H*')[0]
like image 135
emboss Avatar answered Sep 18 '22 14:09

emboss