Ruby openssl bug, reset cipher fails

This is mentioned in this thread
http://www.ruby-forum.com/topic/4293246#1061067 but I thought it was a
different problem then so the thread isn't named correctly. Hopefully
this will get the attention of people interested more in OpenSSL than in
helping noobs understand initialize.

ruby 1.9.3p125 [x86_64-linux]

irb(main):001:0> require 'openssl'
=> true
irb(main):002:0> message = "whatever"
=> "whatever"
irb(main):003:0> @sha256 = OpenSSL::Digest::SHA256.new
=> #<OpenSSL::Digest::SHA256:
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855>
irb(main):004:0> @cipher = OpenSSL::Cipher::Cipher.new("AES-256-CTR")
=> #<OpenSSL::Cipher::Cipher:0x00000002467830>
irb(main):005:0> 2.times do
irb(main):006:1* key = @sha256.digest("whatever")
irb(main):007:1> @sha256.reset
irb(main):008:1> @cipher.encrypt
irb(main):009:1> @cipher.key = key
irb(main):010:1> ciphertext = @cipher.update(message)
irb(main):011:1> ciphertext << @cipher.final
irb(main):012:1> @cipher.reset
irb(main):013:1> puts ciphertext
irb(main):014:1> end
�a�xtT��
9]K���/
=> 2

···

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

Hi,

this is not a bug and Cipher#reset is working fine. What's "going
wrong" here is that you also have to take the IV into account when
trying to reproduce a certain ciphertext. In fact, you don't even need
to call #reset explicitly, #encrypt implies the same functionality
already. Let me explain:

require 'openssl'

message = "whatever"
sha256 = OpenSSL::Digest::SHA256.new
cipher = OpenSSL::Cipher::Cipher.new("AES-256-CTR")
iv = "0" * 32 # you shouldn't do this of course, see my remarks below

2.times do
  key = sha256.digest("whatever")
  sha256.reset
  cipher.encrypt
  cipher.key = key
  cipher.iv = iv
  ciphertext = cipher.update(message)
  ciphertext << cipher.final
  #cipher.reset
  puts ciphertext #will reproduce the same ciphertext twice
end

Of course, it's bad practice to choose a deterministic IV like that,
it was just for demonstration. Generally, you should prefer to use
#random_iv and #random_key in production code as outlined in
http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/Cipher.html\.
Hope that clarifies the issue?

-Martin

···

2012/5/17 roob noob <lists@ruby-forum.com>:

This is mentioned in this thread
What is going wrong here? The case of the noob not understanding initialize - Ruby - Ruby-Forum but I thought it was a
different problem then so the thread isn't named correctly. Hopefully
this will get the attention of people interested more in OpenSSL than in
helping noobs understand initialize.

ruby 1.9.3p125 [x86_64-linux]

irb(main):001:0> require 'openssl'
=> true
irb(main):002:0> message = "whatever"
=> "whatever"
irb(main):003:0> @sha256 = OpenSSL::Digest::SHA256.new
=> #<OpenSSL::Digest::SHA256:
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855>
irb(main):004:0> @cipher = OpenSSL::Cipher::Cipher.new("AES-256-CTR")
=> #<OpenSSL::Cipher::Cipher:0x00000002467830>
irb(main):005:0> 2.times do
irb(main):006:1* key = @sha256.digest("whatever")
irb(main):007:1> @sha256.reset
irb(main):008:1> @cipher.encrypt
irb(main):009:1> @cipher.key = key
irb(main):010:1> ciphertext = @cipher.update(message)
irb(main):011:1> ciphertext << @cipher.final
irb(main):012:1> @cipher.reset
irb(main):013:1> puts ciphertext
irb(main):014:1> end
�a�xtT��
9]K���/
=> 2

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

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

It seems to be an issue only with CTR mode. When I use CTR mode the
output is this:

�a�xtT��
9]K���/

when I use OFB mode the output is this:

�a�xtT��
�a�xtT��

···

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

Good question :slight_smile: 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

···

2012/5/17 roob noob <lists@ruby-forum.com>:

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/\.