OpenSSL: Determining initialization vector (IV) created using OpenSSL::Cipher::Cipher#pkcs5_keyivgen

Hi,

I am attempting to understand some legacy Ruby code that I must write a
service to interact on the backend (my service will be in Scala, but I
am
very handy with Ruby generally). The Ruby code in question uses the
encrypted_strings gem which was used to populate a large database of
encrypted fields in a database (running in production). My goal is to
send
the encrypted values to the new Scala/JVM-based service so that it can
establish connections after decrypting the fields to another service.

Under the covers encrypted_strings Ruby gem calls
OpenSSL::Cipher::Cipher#pkcs5_keyivgen with just the password. This is a
Ruby/C function[1] that is essentially called.

----- start source

static VALUEossl_cipher_pkcs5_keyivgen(int argc, VALUE *argv, VALUE
self){
EVP_CIPHER_CTX *ctx;
const EVP_MD *digest;
VALUE vpass, vsalt, viter, vdigest;
unsigned char key[EVP_MAX_KEY_LENGTH], iv[EVP_MAX_IV_LENGTH], *salt
= NULL;
int iter;

rb_scan_args(argc, argv, "13", &vpass, &vsalt, &viter, &vdigest);
StringValue(vpass);
if(!NIL_P(vsalt)){
    StringValue(vsalt);
    if(RSTRING_LEN(vsalt) != PKCS5_SALT_LEN)
        ossl_raise(eCipherError, "salt must be an 8-octet string");
    salt = (unsigned char *)RSTRING_PTR(vsalt);
}
iter = NIL_P(viter) ? 2048 : NUM2INT(viter);
digest = NIL_P(vdigest) ? EVP_md5() : GetDigestPtr(vdigest);
GetCipher(self, ctx);
EVP_BytesToKey(EVP_CIPHER_CTX_cipher(ctx), digest, salt,
               (unsigned char *)RSTRING_PTR(vpass),

RSTRING_LENINT(vpass), iter, key, iv);
if (EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, -1) != 1)
ossl_raise(eCipherError, NULL);
OPENSSL_cleanse(key, sizeof key);
OPENSSL_cleanse(iv, sizeof iv);

return Qnil;}

----- end source

I realize this method is only PKCS5 v1.5 compliant (rather than 2.0
compliant) and that we should not be using it, but the conversion to
setting a known random initialization vector (IV) is in a medium/long
term
phase. What I would like to be able to figure out in the near-term is:

(a) how to read the C code above. From my rusty understanding of general
C
and virtually zero understanding (despite attempting to find Ruby/C docs
but they appear to be out of date?) of Ruby/C APIs The salt variable
should
still be NULL since now vsalt (second argument) was created.
(b) at some point I would love to learn how to debug this myself. i.e.
setup my environment to trace the C code in the Ruby MRI runtime so I
can
fish for myself next time.

If the salt variable is NULL then what is the IV used in this case? I
need
th IV for the Scala code otherwise I cannot decrypt what the Ruby code
is
encrypting. Also I found a highly related StackOverflow question on this
[2], but the solution involves not using pkcs5_keyivgen method at all
and
setting a known random IV on the OpenSSL::Cipher::Cipher object so it
doesn’t answer my question sadly as I am not in a position to do this
migration yet.

Any pointers on this would be much appreciated.

Cheers,
Susan

[1]
https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_cipher.c#L302-330
[2]

Hi Susan,

I realize this method is only PKCS5 v1.5 compliant (rather than 2.0
compliant) and that we should not be using it, but the conversion to setting
a known random initialization vector (IV) is in a medium/long term phase.
What I would like to be able to figure out in the near-term is:

(a) how to read the C code above. From my rusty understanding of general C
and virtually zero understanding (despite attempting to find Ruby/C docs but
they appear to be out of date?) of Ruby/C APIs The salt variable should
still be NULL since now vsalt (second argument) was created.

salt is initialized as null, but later set in the line

salt = (unsigned char *)RSTRING_PTR(vsalt);

The Ruby String vsalt’s value is assigned to salt there.

(b) at some point I would love to learn how to debug this myself. i.e. setup
my environment to trace the C code in the Ruby MRI runtime so I can fish for
myself next time.

You may simply use gdb or if you prefer some comfort, I frequently use
Netbeans to debug
Ruby C code.

If the salt variable is NULL then what is the IV used in this case? I need
th IV for the Scala code otherwise I cannot decrypt what the Ruby code is
encrypting. Also I found a highly related StackOverflow question on this
[2], but the solution involves not using pkcs5_keyivgen method at all and
setting a known random IV on the OpenSSL::Cipher::Cipher object so it
doesn’t answer my question sadly as I am not in a position to do this
migration yet.

You shouldn’t require the actual key and IV used to do password-based
encryption. What you would normally do on the receiving end is to go
through the same steps as you did when encrypting the initial message,
using the same password, salt and iterations that the encrypting party
used. In Java, this would look something like

----- start source

//These three need to be exactly the same as the ones used in Ruby
char[] password = …
byte[] salt = …
int count = …

PBEParamterSpec params = new PBEParameterSpec(salt, count);
PBEKeySpec keySpec = new PBEKeySpec(password);
SecretKeyFactory keyFac =
SecretKeyFactory.getInstance(“PBEWithMD5AndDES”);
SecretKey key = keyFac.generateSecret(keySpec);
Cipher cipher = Cipher.getInstance(“PBEWithMD5AndDES”);
pbeCipher.init(Cipher.DECRYPT_MODE, key, params);
//now decrypt

----- end source

Figuring out what the key and iv are that were generated would sort of
defeat the purpose of password-based encryption. Although not
impossible, it would definitely result in a lot more work.

For your migration to using PBKDF2, I just added some notes on
how to use it yesterday [1]. I also gave an example on SO how we
did similar upgrading of password schemes in the past [2]. It talks
about moving from MD5 to bcrypt but applies to arbitrary algorithms.
I hope this still helps you in the process!

-Martin

[1] https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl.c#L545-L606
[2]
ruby on rails 3 - Migrate old md5 passwords to bcrypt passwords - Stack Overflow

Hi Martin,

On Sun, Jun 10, 2012 at 5:48 PM, Martin B. <
[email protected]> wrote:

salt is initialized as null, but later set in the line

salt = (unsigned char *)RSTRING_PTR(vsalt);

The Ruby String vsalt’s value is assigned to salt there.

Except I believe that is inside the if block that checks whether or not
vsalt was passed in or not:

if(!NIL_P(vsalt)){

}

So that wouldn’t get set, I believe. I should have explained my
reasoning
in the original message to be clear. Maybe that isn’t what NIL_P is
doing
though or that when an argument isn’t passed in it gets a non NULL
value?
Perhaps NIL (in Ruby/C API) and NULL (in C) are different?

I probably should have also mentioned that the actual call in
encrypted_strings gem that we indirectly call is:
cipher.pkcs5_keyivgen(password), only one argument is passed in.

(b) at some point I would love to learn how to debug this myself. i.e.
setup
my environment to trace the C code in the Ruby MRI runtime so I can fish
for
myself next time.

You may simply use gdb or if you prefer some comfort, I frequently use

Netbeans to debug

byte[] salt = …
----- end source

Thanks, I will definitely use that for reference in our longer-term
plans.
Great idea with the two concurrent values!

I think I figured out a workaround anyway for the short-term deliverable
that can’t wait for a migration path like that in the meantime. Testing
now
and will post if successful.

Thanks! :slight_smile: