DZone Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

Snippets has posted 5883 posts at DZone. View Full User Profile

Encrypting Data With Ruby And OpenSSL

08.11.2005
| 56079 views |
  • submit to reddit
        
require 'openssl'
require 'digest/sha1'
c = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
c.encrypt
# your pass is what is used to encrypt/decrypt
c.key = key = Digest::SHA1.hexdigest("yourpass")
c.iv = iv = c.random_iv
e = c.update("crypt this")
e << c.final
puts "encrypted: #{e}\n"
c = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
c.decrypt
c.key = key
c.iv = iv
d = c.update(e)
d << c.final
puts "decrypted: #{d}\n"

provided by menik from #rubyonrails    

Comments

Snippets Manager replied on Fri, 2010/06/11 - 4:39pm

This is out of date. Ruby has had better openssl support for a while now. For a longer password hash consider using OpenSSL::Digest::SHA512.new(@password).digest

Snippets Manager replied on Tue, 2011/04/05 - 10:44am

Alternatively, if you're just looking for simple and secure encryption in Ruby, check out my gem which abstracts away the complexity of OpenSSL - It's a whole lot better than relying on snippets of code strewn about the web. Gibberish gem - https://github.com/mdp/gibberish

Snippets Manager replied on Mon, 2007/12/10 - 2:14pm

how does one install openssl and net/https for rails?

Snippets Manager replied on Sat, 2006/08/26 - 8:33pm

In Ruby, converting a string of hex digits to a packed set of bytes is covered by the first comment above, specifically: binary_data = unpack('a2'*32).map{|x| x.hex}.pack('c'*32) The "unpack" call assumes you have 32 pairs of hex digits (the 'a2' bit will match a two digit hex number). Change the '32' to match however many hex numbers are in your string. The "map" call creates an array from the unpacked string, with the 32 two-digit hex numbers converted into 32 decimal numbers. The "pack" call takes this array of 32 integers and converts it into a packed stream of 32 bytes.

Snippets Manager replied on Mon, 2006/09/11 - 12:26pm

Hi all, I'm trying to work on a encryp/verification system for Javascript (client) and Ruby (server) I got it to make it work in the client (www.ohdave.com/rsa),encrypting and verifying correctly. Same in the serve side (Openssl:RSA). However, I'm having problem to pass the password encrypted from Javascript to Ruby. As I've being told and I'm reading here, Openssl expect binary form (or that's how it gives the password encrypted when you show it in the screen (see screenshot) ... but Javascript givesme hex encoded data. Here an screenshot of the outputs in client and server: http://i108.photobucket.com/albums/n27/jverger/rsaRuby.png I would appreciate any help on this issue ... : Should I convert from hex to byte or bits ? how? Any example of RSA and client/server architecture with Ruby? thanks so muchs,

Snippets Manager replied on Sat, 2006/08/26 - 8:33pm

Correction! My documentation was out of date, but my local Ruby library (Ruby 1.8.4) does include SHA2. Therefore: require 'digest/sha2' c.key = key = Digest::SHA256.digest("yourpass") ...does the trick, with decent security. The initialization vector still needs the same careful treatment.

Snippets Manager replied on Sat, 2006/08/26 - 8:33pm

To explain further, the AES-256-CBC cipher requires a key that is 256 bits, or 32 bytes long. The SHA1 digest function is one way of taking an arbitrary passphrase and transforming it into a fixed length binary string that can be used as a key; indeed, the binary data can be generated directly: Digest::SHA1.digest("yourpass") The trouble is, the SHA1 digest function doesn't generate data of the length that the cipher expects. It returns 160 bits, or 20 bytes. OpenSSL raises OpenSSL::CipherError if you try and use it. By unpacking the hex string version of the digest as 32 pairs of hex digits, the length becomes fixed; since the input data is too short, the rest of the unpacked digest will contain empty strings which turn into zero (''.hex => 0). This is then packed back down to a 32-byte piece of data to use as a key. The original code would have appeared to work as the cipher would have treated the hex string as a series of 40 arbitrary independent bytes (20 two-digit hex numbers in a string = 40 bytes) and used the first 32 as the key. This isn't secure because each byte in the key actually contains only 16 possible values ("0-9", "a-f") instead of 256, making it much easier to crack by brute force (16^20 - 16 raised to the power of 20 - possible key values rather than 256^20). In this specific example, the AES-256-CBC cipher expects a much longer key than the binary data returned from the SHA1 digest function. They aren't a good match [1], since part of the resulting key will always be zero which weakens the encryption (256^20 possible values rather than 256^32). Unfortunately the Ruby OpenSSL library's strongest digest algorithms only output 160 bits; there's no SHA-256, for example. If your code is open source, it is unlikely that anyone would try to crack the data on the wire based on zero knowledge - they'd already know the cipher and digest function and that the last 12 bytes of the cipher key are always zero. You may as well drop down to AES-192-CBC and generate a 24-byte key. This still requires a longer key than the digest function returns (the last four bytes would always be zero) so you're not losing much/any security, but you conserve resources on the application host. Alternatively, you might wish to pull in some data from some private, hidden source to make the remaining 12 bytes of key data unknown to the source code reader. Where possible, those bytes should vary with each piece of encrypted data - e.g. use some transformation of the passphrase to generate an offset into a file of pseudo-random data and read from there, creating a crude extension of the hash output that depends upon the input data and generates the same result for the same input data. The above example uses a random initialization vector ("c.iv = c.random_iv") which means that in order to decrypt the data, both the passphrase and the "random_iv" value must have been stored. If you need to operate on passphrase alone, you will have to generate an initialization vector (IV) that is *not* known to the public and ideally differs for each piece of data that is encrypted. If a given piece of data is encrypted with the same IV and passphrase, then the encrypted result will be identical. This can give away the use of a fixed IV and weakens the security of the system. The IV should never be made public as part of the message - it enables man-in-the-middle attacks. For more information see [2]. [1] http://www-128.ibm.com/developerworks/java/library/s-hashing/ "...SHA-1 is not sufficient if a hash of length longer than 160 bits is required." [2] http://www.ciphersbyritter.com/GLOSSARY.HTM#CipherBlockChaining

Snippets Manager replied on Thu, 2006/04/27 - 3:10pm

The key is in the wrong format... Do this: c.key = key = Digest::SHA1.hexdigest("yourpass").unpack('a2'*32).map{|x| x.hex}.pack('c'*32) OpenSSL expects the key in binary format. If you use the command line to encrypt something with the same key, you will notice different results unless you do the above. If you are using 128 bit, change the 32 to 16. If you are using 196 bit, try 24. (This is the number of bytes to convert from hex to binary. 128/8 = 16, 256/8=32, 196/8=24) I'm willing to bet that the initialization vector will need the same kind of tweak if you set your own iv.