I want to utilize the PBKDF2 algorithm with SHA1 HMAC (based on this answer).
How can I utilize this through the crypto library?
I started by looking at man openssl
, but the openssl passwd
command (man page) only supports a small handful of algorithms. Looking at the crypto documentation, the evp module has an EVP_BytesToKey method.
Careful selection of the parameters will provide a PKCS#5 PBKDF1 compatible implementation. However, new applications should not typically use this (preferring, for example, PBKDF2 from PCKS#5).
Which brings me back to my original question, how do I utilize PBKDF2 via crypto? Do I need to dig into the code and call a non-API-exposed method (such as PKCS5_PBKDF2_HMAC)? (and if so, what's keeping it from being exposed?)
I do have a working but poor C example of PBKDF2 via the OpenSSL libraries at my github repository, including scripts to compile under both Linux and Windows (via MinGW). Source code located under "Releases" is known good; source code in the master branch is a WIP. This variant is licensed under the same 4-clause BSD in addition to SSLeay license OpenSSL is.
I'm still working on adding a few features, then I'll go back to the excellent input I got on the Code Review StackExchange site and upgrade to C99 syntax and so on.
The core code is very primitive, and may contain flaws despite passing very extensive string-based test vectors. It has not (yet) been tested against pure binary input.
#include <openssl/evp.h>
#include <openssl/sha.h>
// crypto.h used for the version
#include <openssl/crypto.h>
void PBKDF2_HMAC_SHA_1nat_string(const char* pass, const unsigned char* salt, int32_t iterations, uint32_t outputBytes, char* hexResult)
{
unsigned int i;
unsigned char digest[outputBytes];
PKCS5_PBKDF2_HMAC_SHA1(pass, strlen(pass), salt, strlen(salt), iterations, outputBytes, digest);
for (i = 0; i < sizeof(digest); i++)
sprintf(hexResult + (i * 2), "%02x", 255 & digest[i]);
}
If you have a 64-bit system, I would highly recommend moving up to PBKDF2-HMAC-SHA-512 or PBKDF2-HMAC-SHA-384 instead:
#include <openssl/evp.h>
#include <openssl/sha.h>
// crypto.h used for the version
#include <openssl/crypto.h>
void PBKDF2_HMAC_SHA_384_string(const char* pass, const unsigned char* salt, int32_t iterations, uint32_t outputBytes, char* hexResult)
{
unsigned int i;
unsigned char digest[outputBytes];
PKCS5_PBKDF2_HMAC(pass, strlen(pass), salt, strlen(salt), iterations, EVP_sha384(), outputBytes, digest);
for (i = 0; i < sizeof(digest); i++)
sprintf(hexResult + (i * 2), "%02x", 255 & digest[i]);
}
void PBKDF2_HMAC_SHA_512_string(const char* pass, const unsigned char* salt, int32_t iterations, uint32_t outputBytes, char* hexResult)
{
unsigned int i;
unsigned char digest[outputBytes];
PKCS5_PBKDF2_HMAC(pass, strlen(pass), salt, strlen(salt), iterations, EVP_sha512(), outputBytes, digest);
for (i = 0; i < sizeof(digest); i++)
sprintf(hexResult + (i * 2), "%02x", 255 & digest[i]);
}
An example of use would be:
// 2*outputBytes+1 is 2 hex bytes per binary byte,
// and one character at the end for the string-terminating \0
char hexResult[2*outputBytes+1];
memset(hexResult,0,sizeof(hexResult));
PBKDF2_HMAC_SHA_1nat_string(pass, salt, iterations, outputBytes, hexResult);
printf("%s\n", hexResult);
or
// 2*outputBytes+1 is 2 hex bytes per binary byte,
// and one character at the end for the string-terminating \0
char hexResult[2*outputBytes+1];
memset(hexResult,0,sizeof(hexResult));
PBKDF2_HMAC_SHA_512_string(pass, salt, iterations, outputBytes, hexResult);
printf("%s\n", hexResult);
Use a random, per-user salt of 8 to 16 binary bytes, i.e. 16 to 32 hex digits - my code does NOT have examples of generating this yet
Regardless of what you choose, be sure to verify it against test vectors (a few are in pbkdf2_test.bat/sh in my repository).
Additionally, on your system, do some benchmarking - certainly on the PBKDF2-HMAC-SHA-384 and PBKDF2-HMAC-SHA-512 variants, compiling under a 64-bit system produces dramatically better results. Compare it against my equally poor C++ Crypto++ and/or my poor C PolarSSL examples, or Jither's C# implementation example, depending on what your target system is.
The reason you care about speed is that you have to choose an iteration count based on the performance your production system has available compared to the number of users logging in/creating passwords at peak times, so as to not generate too many complaints of slowness.
Attackers are going to use something like oclHashcat, which on a single PC with 8x AMD R9 290Xstock core clock is able to attempt 3.4E12 (2^41) guesses every 30 days against PBKDF2-HMAC-SHA-1(SSID as salt, password, 32 bytes output length, 4096 iterations, a.k.a. WPA/WPA2), which is more or less equivalent to PBKDF2-HMAC-SHA-1(salt,pw,20 bytes output length, 8192 iterations).
The difference becomes important when the attacker starts choosing their attacks.
Now, for PBKDF2, there are a few other things to know: