How do I (really) encrypt a string in ruby?

Hello

I find the ruby crypto very confusing. It looks like the methods are
dynamically generated, and carry no rdoc documentation that would
appear on ruby-doc.

I tried to look at the openssl samples supplied with ruby.
But they only show how to use a high level method which
- does not allow specifying ivec
- makes the encrypted string longer than plain string (even for
lenghts like 4K I would consider very well rounded)

So the question is: how to make the equivalent of the following in ruby?
I am not sure how the length is supposed to be rounded and how long
the ivec should be - there is no documentation for aes in the openssl
man pages.

The following program is supposed to read a file from stdin, encrypt
each BUFLEN block with key specified in file testkey and ivec
specified in testiv, and output file of the same length (rounded to
KLEN). Decrypt if an argument is specified.
In ruby I could not specify the ivec, and the blocks would inflate slightly.

#include <stdio.h>
#include <string.h>
#include <openssl/aes.h>
#include <assert.h>
#define BUFLEN 256
#define KLEN 32
#define KBITS 256

void main(int argc, char** argv) {
        AES_KEY ks;
        FILE * keyf = fopen("testkey","rb");
        FILE * ivf = fopen("testiv","rb");
        assert(keyf && ivf);
        char buf[BUFLEN];
        char key [KLEN];
        char iv [KLEN];
        char civ [KLEN];
        int res;
        int len;
        int way;
        res=fread(key, KLEN, 1, keyf); assert(res);
        res=fread(iv, KLEN, 1, ivf); assert(res);
        memcpy(civ, iv, KLEN);

        if(argc>1){
                fprintf(stderr, "DECRYPT");
                AES_set_decrypt_key(key, KBITS, &ks);
                way=AES_DECRYPT;
        }else{
                fprintf(stderr, "ENCRYPT");
                AES_set_encrypt_key(key, KBITS, &ks);
                way=AES_ENCRYPT;
        }

        while(len = fread(buf, 1, BUFLEN, stdin)){
                if(len<BUFLEN && (len%KLEN)) {
                        res=((len / KLEN) +1)*KLEN;
                        fprintf(stderr, "%i, %i\n",len,res);
                        memset(buf+len,0,res-len);
                        len=res;
                }
                memcpy(civ, iv, KLEN);
                AES_cbc_encrypt(buf,buf,len,&ks,civ,way);
                res=fwrite(buf, 1, len, stdout);
                assert(res==len);

        }
}

Thanks

Michal Suchanek

···

--
Support the freedom of music! Maybe it's a weird genre you would not like
if you heared it .. but weird is *not* illegal.
Maybe next time they will send a special forces commando to your picnic ..
.. because they think you are weird.
   http://www.music-versus-guns.org http://en.policejnistat.cz

Hello,

Von: ruby-talk-admin@ruby-lang.org
[mailto:ruby-talk-admin@ruby-lang.org] Im Auftrag von Michal Suchanek
Gesendet: Dienstag, 27. September 2005 13:09
An: ruby-talk ML
Betreff: How do I (really) encrypt a string in ruby?

Hello

I find the ruby crypto very confusing. It looks like the
methods are dynamically generated, and carry no rdoc
documentation that would appear on ruby-doc.

Yes, it is a sad, sad story...

I tried to look at the openssl samples supplied with ruby.
But they only show how to use a high level method which

...

Example:

require "openssl"
include OpenSSL
include Cipher

cipher = Cipher.new("AES-256-CBC")

key = cipher.random_key()
iv = cipher.random_iv()

#---- Encrypt
text = "Hello, World!"
cipher.encrypt(key,iv)
cipher.key=key
cipher.iv = iv
e = cipher.update(text)
e << cipher.final()

puts("Encrypted text: " + e.to_s())

#---- Decrypt
cipher = Cipher.new("AES-256-CBC")
cipher.decrypt(key,iv)
cipher.key = key
cipher.iv = iv
d = cipher.update(e)
d << cipher.final()

puts("Plain text: " + d.to_s())

Hope it helps,
Roland

···

-----Ursprüngliche Nachricht-----

Hello

Thanks for the example. Although ruby complains all the time about key
derivation with encrypt being deprecated I get at least to specify the
iv.

However, encrypting 2.0M file gives 2.1M file.
Since doing encryption in C makes 2.0M ciphertext from 2.0M plaintext,
and it is also possible to decrypt the 2.0M ciphertext to the original
plaintext, something is rotten here.

Am I missing something?

require "openssl"

CHLEN = 256
KBITS = 256
KLEN = 32
IVLEN = 16 # God knows why

f=File.open("testkey");
key=f.read(KLEN)
f.close
f=File.open("testiv")
iv=f.read(IVLEN)
f.close
while (chunk=STDIN.read(CHLEN))
  cipher=OpenSSL::Cipher::Cipher.new("AES-256-CBC")
  cipher.encrypt(key,iv)
  cipher.key=key
  cipher.iv=iv
  STDOUT << cipher.update(chunk)
  STDOUT << cipher.final()
end

···

--
Support the freedom of music! Maybe it's a weird genre you would not like
if you heared it .. but weird is *not* illegal.
Maybe next time they will send a special forces commando to your picnic ..
.. because they think you are weird.
   http://www.music-versus-guns.org http://en.policejnistat.cz

..

Example:

require "openssl"
include OpenSSL
include Cipher

cipher = Cipher.new("AES-256-CBC")

key = cipher.random_key()
iv = cipher.random_iv()

#---- Encrypt
text = "Hello, World!"
cipher.encrypt(key,iv)

Is the above the preferred way to set key and iv, or the below?
Is it possible to set new iv (or key) without creating a new cipher instance?

cipher.key=key
cipher.iv = iv
e = cipher.update(text)
e << cipher.final()

Is it possible to add more text after final()? I guess not.

But as I understand it in C the encryption is done in chunks (probably
16 or 32 bytes long). If I encrypt part of data (rounded to chunks),
save the iv, and later encrypt some more data with the saved iv and
the same key, I can concatenate the results.

And decrypting the concatenated results should give concatenation of
the encrypted pieces of data. I haven't tried. But that's how I would
expect the iv to work.

Now in ruby one gets final() which looks like it finishes off the
instance, and it is not apparent if the iv before final() or after
final() (or neither) could be used for continuing the encryption.

Thanks

Michal Suchanek

···

On 9/27/05, Roland Schmitt <Roland.Schmitt@web.de> wrote:

--
Support the freedom of music! Maybe it's a weird genre you would not like
if you heared it .. but weird is *not* illegal.
Maybe next time they will send a special forces commando to your picnic ..
.. because they think you are weird.
   http://www.music-versus-guns.org http://en.policejnistat.cz

However, encrypting 2.0M file gives 2.1M file.
Since doing encryption in C makes 2.0M ciphertext from 2.0M plaintext,
and it is also possible to decrypt the 2.0M ciphertext to the original
plaintext, something is rotten here.

Any chance you're using Windows? Maybe it's a "text mode" vs. "binary mode"
problem? (LF -> CRLF in the output)

If you're on Windows, try:

STDIN.binmode
STDOUT.binmode

at the top of your script?

HTH,

Bill

···

From: "Michal Suchanek" <hramrach@gmail.com>

Hi,

Michal Suchanek wrote:

However, encrypting 2.0M file gives 2.1M file.

It may be caused by PKCS#5 padding.

Since doing encryption in C makes 2.0M ciphertext from 2.0M plaintext,
and it is also possible to decrypt the 2.0M ciphertext to the original
plaintext, something is rotten here.

Am I missing something?

require "openssl"

CHLEN = 256
KBITS = 256
KLEN = 32
IVLEN = 16 # God knows why

AES is a 128 bits (16 bytes) block cipher.

f=File.open("testkey");
key=f.read(KLEN)
f.close
f=File.open("testiv")
iv=f.read(IVLEN)
f.close

while (chunk=STDIN.read(CHLEN))
  cipher=OpenSSL::Cipher::Cipher.new("AES-256-CBC")
  cipher.encrypt(key,iv)
  cipher.key=key
  cipher.iv=iv
  STDOUT << cipher.update(chunk)
  STDOUT << cipher.final()
end

This block should be:

cipher=OpenSSL::Cipher::Cipher.new("AES-256-CBC")
cipher.encrypt(key,iv)
cipher.padding=0 # avoid PKCS#5 padding
cipher.key=key
cipher.iv=iv
ciphertext =
while (chunk=STDIN.read(CHLEN))
  ciphertext << cipher.update(chunk)
end
ciphertext << cipher.final() rescue nil # no need to call final if
padding = 0
print ciphertext.join

Regards,
// NaHi

From: "Michal Suchanek" <hramrach@gmail.com>

> However, encrypting 2.0M file gives 2.1M file.
> Since doing encryption in C makes 2.0M ciphertext from 2.0M plaintext,
> and it is also possible to decrypt the 2.0M ciphertext to the original
> plaintext, something is rotten here.

Any chance you're using Windows? Maybe it's a "text mode" vs. "binary mode"

I am on OS X right now.

problem? (LF -> CRLF in the output)

If you're on Windows, try:

STDIN.binmode
STDOUT.binmode

That does not help (not surprising).

irb(main):010:0> c=OpenSSL::Cipher::Cipher.new("AES-256-CBC")
=> #<OpenSSL::Cipher::Cipher:0x789a08>
irb(main):011:0> c.encrypt(c.random_key(),c.random_iv())
=> #<OpenSSL::Cipher::Cipher:0x789a08>
irb(main):012:0> str='0123456789ABCDEF'
=> "0123456789ABCDEF"
irb(main):013:0> e=c.update(str+str)
=> "\021\203\262\321\232@;Z-\257\020\240\343\350@s\236\322d\312U\246ce\221\331\034\2538@h\303"
irb(main):014:0> e+=c.final()
=> "\021\203\262\321\232@;Z-\257\020\240\343\350@s\236\322d\312U\246ce\221\331\034\2538@h\303\213L\036\2510b\006\353\307b\261#\022\257B\255"
irb(main):015:0> e.length
=> 48

Thanks

Michal Suchanek

···

On 9/27/05, Bill Kelly <billk@cts.com> wrote:

--
Support the freedom of music! Maybe it's a weird genre you would not like
if you heared it .. but weird is *not* illegal.
Maybe next time they will send a special forces commando to your picnic ..
.. because they think you are weird.
   http://www.music-versus-guns.org http://en.policejnistat.cz

:- )

Absolutely Excellent!!

You sir, are the man!

···

On 9/27/05, NAKAMURA, Hiroshi <nakahiro@sarion.co.jp> wrote:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi,

Michal Suchanek wrote:
> However, encrypting 2.0M file gives 2.1M file.

It may be caused by PKCS#5 padding.

> Since doing encryption in C makes 2.0M ciphertext from 2.0M plaintext,
> and it is also possible to decrypt the 2.0M ciphertext to the original
> plaintext, something is rotten here.
>
> Am I missing something?
>
> require "openssl"
>
> CHLEN = 256
> KBITS = 256
> KLEN = 32
> IVLEN = 16 # God knows why

AES is a 128 bits (16 bytes) block cipher.

> f=File.open("testkey");
> key=f.read(KLEN)
> f.close
> f=File.open("testiv")
> iv=f.read(IVLEN)
> f.close

> while (chunk=STDIN.read(CHLEN))
> cipher=OpenSSL::Cipher::Cipher.new("AES-256-CBC")
> cipher.encrypt(key,iv)
> cipher.key=key
> cipher.iv=iv
> STDOUT << cipher.update(chunk)
> STDOUT << cipher.final()
> end

This block should be:

cipher=OpenSSL::Cipher::Cipher.new("AES-256-CBC")
cipher.encrypt(key,iv)
cipher.padding=0 # avoid PKCS#5 padding
cipher.key=key
cipher.iv=iv
ciphertext =
while (chunk=STDIN.read(CHLEN))
  ciphertext << cipher.update(chunk)
end
ciphertext << cipher.final() rescue nil # no need to call final if
padding = 0
print ciphertext.join

Regards,
// NaHi
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.1 (Cygwin)

iD8DBQFDOegcf6b33ts2dPkRAul3AKCZStaQkGlmrPmWdNtDtlz5hTWr+wCgkxTg
YNdIRFCn/OZ7KB4zFcBYMRA=
=OSin
-----END PGP SIGNATURE-----

...

> while (chunk=STDIN.read(CHLEN))
> cipher=OpenSSL::Cipher::Cipher.new("AES-256-CBC")
> cipher.encrypt(key,iv)
> cipher.key=key
> cipher.iv=iv
> STDOUT << cipher.update(chunk)
> STDOUT << cipher.final()
> end

This block should be:

cipher=OpenSSL::Cipher::Cipher.new("AES-256-CBC")
cipher.encrypt(key,iv)
cipher.padding=0 # avoid PKCS#5 padding

yes, this is what I wanted

cipher.key=key
cipher.iv=iv
ciphertext =
while (chunk=STDIN.read(CHLEN))
  ciphertext << cipher.update(chunk)
end
ciphertext << cipher.final() rescue nil # no need to call final if
padding = 0
print ciphertext.join

But I really want to encrypt (and decrypt) the blocks separately.

Now I get identical results with C and ruby. Great !

Thanks

Michal Suchanek

···

On 9/28/05, NAKAMURA, Hiroshi <nakahiro@sarion.co.jp> wrote:

--
Support the freedom of music! Maybe it's a weird genre you would not like
if you heared it .. but weird is *not* illegal.
Maybe next time they will send a special forces commando to your picnic ..
.. because they think you are weird.
   http://www.music-versus-guns.org http://en.policejnistat.cz

Hi,

Michal Suchanek wrote:

But I really want to encrypt (and decrypt) the blocks separately.

Oops.

  cipher = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
  cipher.padding = 0
  cipher.key = key
  cipher.iv = iv
  while (chunk = STDIN.read(CHLEN))
    cipher.encrypt
    STDOUT << cipher.update(chunk)
  end

Regards,
// NaHi