From: "Martin Boßlet" Date: 2012-05-18T02:54:35+09:00 Subject: Re: ruby openssl bug, reset cipher fails 2012/5/17 roob noob : > I kind of understand. So where is it getting the IV value from if I > don't explicitly tell it to use an IV? > > -- > Posted via http://www.ruby-forum.com/. > Good question :) As I wrote in the documentation for Cipher, if you do not specify any IV, an implicit IV of all zeroes ("\0") of the correct length is assumed by OpenSSL. To see this, try the following code example: require 'openssl' message = "whatever" iv = "\0" * 32 key = "k" * 32 c1 = OpenSSL::Cipher::Cipher.new("AES-256-CTR") c1.encrypt c1.key = key ct1 = c1.update(message) + c1.final c2 = OpenSSL::Cipher::Cipher.new("AES-256-CTR") c2.encrypt c2.key = key c2.iv = iv ct2 = c2.update(message) + c2.final puts ct1 == ct2 # => true, proves that iv for c1 and c2 were the same Now, if you look into the OpenSSL sources for what happens when you reset a Cipher (EVP_CipherInitEx is called), then the behavior for CTR is different than that for other modes (that's also why it works with e.g. OFB): the internal counter 'num' is reset, but not the IV itself. But the IV is used for the "running counter" of CTR mode. This means if you used the Cipher before and reset it after that, the running counter derived from the IV will not be reset. Thus, no reproducible ciphertext in your case, because the internal state is not entirely reset. It will only be entirely reset if you *additionally* provide an IV. As to why CTR is implemented this way, I can only imagine it's to prevent you from unintentionally reusing the same key / IV combination for different messages. This is a huge security concern when using a streaming Cipher mode (or stream cipher in general): NEVER reuse the same key and IV. Since you probably don't want to throw away your key each time you encrypt, what this means is that you should generate a new IV each time you encrypt a message, using a non-predictable IV (for example generated by Cipher#random_iv). I should probably add this to the documentation, since it really is such an important aspect. To see why this is such a huge problem, consider this piece of code: require 'openssl' iv = "\0" * 32 key = "k" * 32 class String def ^(other) "".tap do |result| each_byte.each_with_index { |b,i| result << ( b ^ other[i].ord) } end end end m1 = "whatever" m2 = "lesecret" c1 = OpenSSL::Cipher::Cipher.new("AES-256-CTR") c1.encrypt c1.key = key c1.iv = iv ct1 = c1.update(m1) + c1.final c2 = OpenSSL::Cipher::Cipher.new("AES-256-CTR") c2.encrypt c2.key = key c2.iv = iv ct2 = c2.update(m2) + c2.final plain_xored = m1 ^ m2 ct_xored = ct1 ^ ct2 puts plain_xored == ct_xored # => true As you can see, the XOR of the ciphertexts is now equal to the XOR of the plaintext messages. It's not too hard for anyone to recover the original m1 and m2 from this, so this would completely break the security of your encryption. -Martin