danpal/encryptor — comment on commit c1b7e39538cb841a809053a7936b610e92a4bde0
File: lib/encryptor.rb line 57
By danpal:

Could you explain how pkcs5_keyivgen works, it seems it uses

EVP_BytesToKey(EVP_CIPHER_CTX_cipher(ctx), digest, salt, (unsigned char *)RSTRING_PTR(vpass), RSTRING_LEN(vpass), iter, key, iv);

to derive the :iv and the :key.

So the key and iv are going to be derived by concatenating multiple md5 hashes of the password. But without a :salt here this is totally insecure. If someone uses the same password multiple times, he will leak the first block of the cypher-text.
It seems this is confusingly documented.

/*
* call-seq:
* cipher.pkcs5_keyivgen(pass [, salt [, iterations [, digest]]] ) -> nil
*
* Generates and sets the key/iv based on a password.
*
* WARNING: This method is only PKCS5 v1.5 compliant when using RC2, RC4-40, or DES
* with MD5 or SHA1. Using anything else (like AES) will generate the key/iv using an
* OpenSSL specific method. Use a PKCS5 v2 key generation method instead.
*
* === Parameters
* +salt+ must be an 8 byte string if provided.
* +iterations+ is a integer with a default of 2048.
* +digest+ is a Digest object that defaults to 'MD5'
*
* A minimum of 1000 iterations is recommended.
*
*/
static VALUE
ossl_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)
rb_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_LEN(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;
}