2012/5/17 roob noob [email protected]:
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