OPENSSL EVP BytesToKey

From Crypto++ Wiki
Jump to navigation Jump to search

OpenSSL uses a function called EVP_BytesToKey in its utilities. It is a key derivation function used to digest passwords and pass-phrases into bytes for keying material and other parameters, like initialization vectors. OpenSSL's documentation for the function can be found at EVP_BytesToKey.

Be sure to use the correct hash when enlisting OpenSSL's EVP_BytesToKey. Early versions of EVP_BytesToKey used MD5, and later versions use SHA. MD5 is used in OpenSSL 1.0.2 and earlier. OpenSSL 1.1.0c and later use SHA-256 as the hash.

Unless you have a specific need, you should not use OPENSSL_EVP_BytesToKey. Rather, you should use a key derivation function like HKDF or Scrypt.

OPENSSL_EVP_BytesToKey is not part of the Crypto++ library. If you want it, then paste it into a file like misc.h. OPENSSL_EVP_BytesToKey is a header-only definition so you don't need to modify source files. If you want to add it to the library, be sure its in the CryptoPP namespace.

PEM Pack Usage

The PEM Pack uses OPENSSL_EVP_BytesToKey to read and write keys produced by OpenSSL that are password protected. Below is from pem-wr.cpp:

SecByteBlock _key(ksize), _iv(vsize), _salt(vsize);
...

Weak::MD5 md5;
int ret = OPENSSL_EVP_BytesToKey(md5, _salt.data(), _pword, _plen, 1, _key.data(), _key.size(), NULL, 0);
if(ret != static_cast<int>(ksize))
    throw Exception(Exception::OTHER_ERROR, "PEM_CipherForAlgorithm: EVP_BytesToKey failed");

OPENSSL_EVP_BytesToKey

// From crypto/evp/evp_key.h. Signature changed a bit to match Crypto++.
int OPENSSL_EVP_BytesToKey(HashTransformation& hash,
                           const unsigned char *salt, const unsigned char* data, int dlen,
                           unsigned int count, unsigned char *key, unsigned int ksize,
                           unsigned char *iv, unsigned int vsize)
{
    if (data == NULL) return (0);

    unsigned int nkey=ksize;
    unsigned int niv=vsize;
    unsigned int nhash=hash.DigestSize();
    SecByteBlock digest(nhash);

    unsigned int addmd=0,i;
    
    for (;;)
    {
        hash.Restart();
        
        if(addmd++)
            hash.Update(digest.data(), digest.size());
        
        hash.Update(data, dlen);
        
        if (salt != NULL)
            hash.Update(salt, OPENSSL_PKCS5_SALT_LEN);
        
        hash.TruncatedFinal(digest.data(), digest.size());
        
        for (i=1; i<count; i++)
        {
            hash.Restart();
            hash.Update(digest.data(), digest.size());
            hash.TruncatedFinal(digest.data(), digest.size());
        }
        
        i=0;
        if (nkey)
        {
            for (;;)
            {
                if (nkey == 0) break;
                if (i == nhash) break;
                if (key != NULL)
                    *(key++)=digest[i];
                nkey--;
                i++;
            }
        }
        if (niv && (i != nhash))
        {
            for (;;)
            {
                if (niv == 0) break;
                if (i == nhash) break;
                if (iv != NULL)
                    *(iv++)=digest[i];
                niv--;
                i++;
            }
        }
        if ((nkey == 0) && (niv == 0)) break;
    }
    
    return ksize;
}

MD5 and SHA

OpenSSL 1.1.0 changed the digest algorithm used in some internal components. Formerly, MD5 was used, and 1.1.0c switched to SHA256. Be careful the change is not affecting you in both EVP_BytesToKey and commands like openssl enc.