I have a working but bad example of C PBKDF2 through the OpenSSL libraries in my github repository , including scripts for compiling under Linux and Windows (via MinGW). The source code, located in the Releases section, is well known; The source code in the main branch is WIP. This option is licensed under the same 4 domain BSD in addition to the SSLeay OpenSSL license.
I'm still working on adding a few features, and then back to the great contribution I received on the Code Review StackExchange website and updated it to C99 syntax, etc.
The main code is very primitive and may contain flaws, although very extensive string-based test vectors pass. 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 highly recommend upgrading to PBKDF2-HMAC-SHA-512 or PBKDF2-HMAC-SHA-384:
#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]); }
Usage example:
// 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 salt for each user from 8 to 16 binary bytes, i.e. from 16 to 32 hexadecimal digits. In my code there are no examples of creating this yet
No matter what you choose, be sure to check it on test vectors (some of them are in pbkdf2_test.bat / sh in my repository).
In addition, do some benchmarking on your system - of course, on the PBKDF2-HMAC-SHA-384 and PBKDF2-HMAC-SHA-512 variants, compilation under a 64-bit system gives significantly better results. Compare this to my equally bad C ++ Crypto ++ and / or my bad C PolarSSL , or Jither C # Sample Implementation , depending on your target system.
The reason you care about speed is because you need to choose an iteration counter based on the performance of your production system, available compared to the number of users logging / creating passwords at peak times, so as not to create too many complaints of slowness.
Attackers are going to use something like oclHashcat , which on one PC with 8x AMD R9 290Xstock cores can try 3.4 E12 (2 ^ 41) guesses every 30 days against PBKDF2-HMAC-SHA-1 (SSID as salt, password, output length 32 byte, 4096 iterations, also known as WPA / WPA2), which is more or less equivalent to PBKDF2-HMAC-SHA-1 (salt, pw, output length 20 bytes, 8192 iterations).
- If you use iterations 65536, the attacker can only guess about 8.5E11 (~ 2 ^ 39) every 30 days.
- if you use 1024 iterations, an attacker could instead try to use 2.7E13 (~ 2 ^ 44) every 30 days.
The difference becomes important when an attacker begins to choose their attacks.
- In both cases, the attacker goes through very small keys; there is no reason not to spend several minutes or even several hours on low-hanging fruits.
- These are all hexadecimal characters for length 1-n, and then all printed characters from lengths n + 1 to n + m, and then go along the line until they reach n + y with a hard-coded! in the end:).
- In both cases, the attacker intends to use tiny dictionaries and tiny rule sets; let's say phpbb dictionary of 184389 words and Best64 rule set
- If you had 1000 users with 1000 different random salts and used PBKDF2-HMAC-SHA-1 with iterations 65536, which were going to take our only PC attacking 8 GPUs (184389 * 64 * 1000) / (8.5E11 / 30) days = 0.41 days. Well worth it!
- Now, what about the same phpbb dictionary and the middle T0X1C rule set from 4089?
- If you had 1000 users with 1000 different random salts and used PBKDF2-HMAC-SHA-1 with 65536 iterations, this will take our only computer, 8 GPU attackers (184389 * 4089 * 1000) / (8.5E11 / 30) days = 26.61 days.
- Still worth it, but spend almost 4 weeks for this machine on one attack!
- If you had 1000 users with 1000 different random salts and used PBKDF2-HMAC-SHA-1 with iterations 65536 that were going to take our only computer, 8 GPU attackers (184389 * 4089 * 1000) / (2.7E13 / 30) days = 0.83 days.
- Now, what about the same phpbb dictionary and the excellent 35404 d3ead0ne rule set?
- If you had 1000 users with 1000 different random salts and used PBKDF2-HMAC-SHA-1 with iterations 65536, which were going to take our only PC attacking 8 GPUs (184389 * 35404 * 1000) / (8.5E11 / 30) days = 230.39 days.
- That's right, time to buy more cars or work on it in timeless way.
- If you had 1000 users with 1000 different random salts and used PBKDF2-HMAC-SHA-1 with iterations 65536 that were going to take our only computer, 8 GPU attackers (184389 * 4089 * 1000) / (2.7E13 / 30) days = 7.18 days.
- Worth it! This is only a week and food.
Now, for PBKDF2, there are a few more things you need to know:
- For password hashing, never choose a size of binary output that is larger than its own hash size. Personally, I would not recommend a binary output size of less than 20 bytes no matter what the bit limit is for SHA-1.
- SHA-1 - size = 20 bytes
- SHA-224 is 20 <= size <= 28 bytes
- SHA-256 is 20 <= size <= 32 bytes.
- SHA-384 is 20 <= size <= 48 bytes
- SHA-512 is 20 <= size <= 64 bytes
- adjust how you store the hash if it is not in pure binary format, of course.
- Reason: PBKDF2 first starts # iterations requested for one native output size (number on the right, above). If you want more of this, it starts the entire iteration counter again. If you want less than that, it truncates.
- The attacker will only correspond to the first proper size - if the first bytes match, yes, this is the password, so you better increase the iteration counter.