Ruby and Cryptography

Hello everyone,
Is there any Ruby packages that provides cryptography “either symmetric
or asymmetric cryptography”? If so can any one tell me and recommend me
what to use?
Thanks in advance.

Hi,

Yes, you have the openssl in the standard ruby library which is a port
of the great C library of the same name. But it’s is poorly
documented. http://www.nongnu.org/rubypki/README.

So you have an easier library which is based on openssl : ezcrypto.
http://ezcrypto.rubyforge.org/.

On 3/20/07, Amr K. [email protected] wrote:

Hello everyone,
Is there any Ruby packages that provides cryptography “either symmetric
or asymmetric cryptography”? If so can any one tell me and recommend me
what to use?
Thanks in advance.

Hi Amr,
There are lots of good libraries for doing crypto with Ruby. Here is a
google search on the subject of your message:
http://www.google.com/search?q=Ruby+and+Cryptography

And here is the first result:
http://rubyforge.org/projects/crypt/

Though, this forum/mailing-list/usenet group was not set up so people
can do your googling for you. I think here we’d rather discuss the
relative merits of different crypto libraries, or possible defects in
them. Even a question like “I tried thing x with the crypt library and
y didn’t happen. Can anyone help?” would be better.

Highest regards,
-Harold


Posted via http://www.ruby-forum.com/.

And to the world in general,

The warning on the ruby-forum posting page which mentions that this
every message on this mailing list is delivered to thousands of people
is awesome! I actually visited the page with the intention of telling
the forum maintainer to add such a warning. I was very pleased to see
that it’s already there.

Now, how to make people notice it? Perhaps a tag is in order. :wink:

I like the crypt library that Harold mentioned and have used it
successfully in several applications. Good luck.

_studlee2

On Tue, Mar 20, 2007 at 04:22:01PM +0900, Amr K. wrote:

Hello everyone,
Is there any Ruby packages that provides cryptography “either symmetric
or asymmetric cryptography”? If so can any one tell me and recommend me
what to use?
Thanks in advance.

Ruby ships with an OpenSSL interface so you can use the encryption
available in openssl in ruby.

One example of using OpenSSL and Ruby for encryption is :

http://snippets.dzone.com/posts/show/576

It is true that the documentation on using OpenSSL from Ruby is lacking.

There are also the pure ruby encryption cyphers avaialble in
http://crypt.rubyforge.org. Those can be installed with:

gem insall crypt

The crypt website has good usage examples.

enjoy,

-jeremy

Amr K. wrote:

Thanks alot everyone I really your help and patience with me :).
Thanks again.

sorry for typo error “I really appreciate your help …” :slight_smile:

Thanks alot everyone I really your help and patience with me :).
Thanks again.

Ruby ships with an OpenSSL interface so you can use the encryption
available in openssl in ruby.

One example of using OpenSSL and Ruby for encryption is :

http://snippets.dzone.com/posts/show/576

It is true that the documentation on using OpenSSL from Ruby is lacking.

Updated documentation is available at
http://technorama.net/~oss/ruby/openssl/doc/

The documention isn’t complete.

Not all classes or methods are documented. Callbacks aren’t documented
at all.
Few classes or methods have examples on their usage beyond a simple
method call.

Here’s a summary of what classes are or aren’t documented.
Note: “complete” means the method shows up in rdoc, not that there is
useful documentation on how it works.

90% complete
OpenSSL::Cipher::Cipher
OpenSSL::Digest::Digest
OpenSSL::HMAC
OpenSSL::SSL::Session (doesn’t exist in 1.8 or 1.9 yet)

75% complete
OpenSSL::PKey::DH
OpenSSL::PKey::DSA
OpenSSL::PKey::RSA

No documentation
ASN1
BN
PKCS*
Random
X509*
SSL*

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?

  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.

-------- code follows -----------

def process_stream(action,src,target)
  begin
    cipher = Cipher.new(@cipher_name)
    if action == :encrypt
      cipher.encrypt.pkcs5_keyivgen(@password,@salt)
    elsif action == :decrypt
      cipher.decrypt.pkcs5_keyivgen(@password,@salt)
    else
      raise "programming error: unsupported action \"#{action}\""
    end
    loop do
      buffer = src.read(@block_size) or break
      target << cipher.update(buffer)
    end
    target << cipher.final
  rescue Exception => e
    @error = e.to_s
    return false
  end
  return true
end

On Wed, Mar 21, 2007 at 04:44:14AM +0900, Technorama Ltd. wrote:

Updated documentation is available at
http://technorama.net/~oss/ruby/openssl/doc/

This is great. I was looking at the documentation at the ruby-doc.org
site when I said OpenSSL documentation was lacking. I assume this is
being commited to the trunk ?

enjoy,

-jeremy

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.

  1. 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)

thanks, that was very illuminating, see comments below.

… snip …

  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.

I’m running ruby 1.8.5 and comments pertain to that source.

pkcs5_keyivgen eventually calls down to EVP_BytesToKey, passing the
passphrase, salt (must be exactly 8 bytes if given), iteration count
(defaults to 2048) and digest; it returns the key and IV.

the man page for EVP_BytesToKey says:

If the total key and IV length is less than the digest length and
MD5 is used then the derivation algorithm is compatible with
PKCS#5 v1.5 otherwise a non standard extension is used to derive
the extra data.

Newer applications should use more standard algorithms such as
PKCS#5 v2.0 for key derivation.

however there is no suggestion about how to get v2.0 output.

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.

I definitely don’t want to do this, hence I was trying to figure out
how to match “enc”.

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.

right, see comment above.

Below is a pure ruby PKCS5 v2 password -> key method.

thanks, but I don’t want to get ahead of the ruby openssl library. of
course this does raise the question of how it will eventually be
incorporated. it wouldn’t be a nice thing if the addition caused all
my encrypted files to be inaccessible.

… snip …

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).

right, but I don’t want to do this at all ;-).

… snip …

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.

right, that’s precisely why I was attempting to test against openssl
enc.

… snip …

Here’s a quick and dirty bit of code to encrypt/decrypt the openssl
program output.

the key in the code you provided, besides the obvious biz with the
header, is the iteration count is 1, something I wouldn’t have
guessed. inspecting the openssl src for “enc” shows the same (I
should have gone there first).

the other thing I didn’t figure out by messing around with “enc” is if
you don’t provide it a salt, and if you don’t specify -nosalt, then it
will do as you did, provide a random salt string when encrypting. I
understand the idea of salt, but specifying it on the command line
each time didn’t make sense to me, and now I see that’s because it
doesn’t make sense ;-).

… snip …

one more thing, at least on my system:

the call OpenSSL::Random::pseudo_rand_bytes(salt_len)
should be OpenSSL::Random::pseudo_bytes(salt_len), the other doesn’t
exist.