From: "Technorama Ltd." Date: 2007-03-26T04:20:20+09:00 Subject: Re: Ruby and Cryptography David x Callaway wrote: > >> Updated documentation is available at >> http://technorama.net/~oss/ruby/openssl/doc/ >> >> The documention isn't complete. >> > > but it is a *lot* better than the standard lib docs ;-). > > with aid from the documention above and looking at the c source for ruby > openssl, I wrote the code below (I left out error checking for brevity > here) which looks reasonable for encrypting/decrypting from streams. it > "works", meaning I can encrypt a stream and recover it, and it is quite > fast, ~1 sec to encrypt a 20MB file vs more than one hour for Crypt to > do the same. > > questions: > > 1) is pkcs5_keyivgen() the way to go rather than hashing (password > string + salt) and then using that as the key with > OpenSSL::Cipher::Cipher as EzCrypto does? Maybe. In most cases the pkcs5_keyivgen method is not pkcs5 compliant. You must be using MD2, MD5, or SHA1 with RC2 or DES. Using any other cipher (like AES) will generate your key/iv in an OpenSSL specific format. You probably don't want to use your own key generation method since you are fairly likely to make design mistakes. Ideally you would use PKCS5 v2. Unfortunately the ruby OpenSSL module doesn't have a hook into any of the PKCS5 v2 password generating functions. Below is a pure ruby PKCS5 v2 password -> key method. Usage: cipher = Cipher::Cipher.new('aes-128-cbc') cipher.encrypt cipher.key = pkcs5_pbkdf2_hmac_sha1(password, salt, iter, cipher.key_len) cipher.iv = generate_your_own_iv Iterations should probably be above 1000. The higher it is the longer it takes to generate the key. Salt should be unique for each unique piece of data. def pkcs5_pbkdf2_hmac_sha1(pass, salt, iter, len) ret = '' i = 0 digest = OpenSSL::Digest::Digest.new('sha1') while len > 0 i += 1 hmac = HMAC.new(pass, digest) hmac.update(salt) hmac.update([i].pack('N')) digtmp = hmac.digest cplen = len > digtmp.length ? digtmp.length : len tmp = digtmp.dup 1.upto(iter - 1) do |j| hmac = HMAC.new(pass, digest) hmac.update(digtmp) digtmp = hmac.digest 0.upto(cplen - 1) do |k| tmp[k] = (tmp[k] ^ digtmp[k]).chr end end tmp.slice!((cplen)..-1) if (tmp.length > cplen) ret << tmp len -= tmp.length end ret end Notes: The above method was written to answer your message and has not been thoroughly tested. The ruby version is 8-9 times slower than the c version. Patches for the c method will be sent in to ruby at some point when they have been properly tested. If you are designing your own encrypted storage format there are a number of things you should do (this is not an exhaustive list of everything you should do, just a few pointers). Include the following in the header: magic string version number (either a storage format version or a program version) key derivation method and parameters (if you have one) cipher id digest or hmac id Document the format. Write unit tests. Test, test, test, test and test. There's nothing worse than finding out that you used the wrong variable for the key and now everything is encrypted with the same key. Or finding out later than you didn't store the key properly and can't retrieve anything. > 2) how can I make the encrypted output compatible with what I get from > using openssl directly, e.g. > > openssl aes-128-cbc -e -in xxx.txt -out out.bin \ > -pass pass:aaaabbbbccccdddd -salt -S 6161616161616161 -p > > openssl includes the salt string in the encrypted output, so I tried > -nosalt and other things like providing the key binary directly, but no > matter what the encrypted file did not match either the output from the > code below or code using EzCrypto (EzCrypto didn't match openssl > either), so I could not decrypt openssl output nor could it decrypt > mine. since the purpose of encryption is to make the content secure I > would feel a lot warmer and fuzzier if I got interoperable output. > Here's a quick and dirty bit of code to encrypt/decrypt the openssl program output. require 'openssl' magic = 'Salted__' salt_len = 8 infile = "" outfile = "" password = "x" cipher = 'aes-128-cbc' # decrypt: compatible with openssl -e $CIPHER -k $PASS file = File.open(infile) if (buf = file.read(magic.length)) != magic raise "unknown file, read #{buf.inspect}" end salt = file.read(salt_len) c = OpenSSL::Cipher::Cipher.new(cipher) c.decrypt c.pkcs5_keyivgen(password, salt, 1) buf = c.update(file.read) + c.final # encrypt: compatible with openssl enc -d $CIPHER -k $PASS file = File.new(outfile, "w") salt ||= OpenSSL::Random::pseudo_rand_bytes(salt_len) c = OpenSSL::Cipher::Cipher.new(cipher) c.encrypt c.pkcs5_keyivgen(password, salt, 1) file.write(magic) file.write(salt) file.write(c.update(buf) + c.final) -- Posted via http://www.ruby-forum.com/.