Class: SafeDb::KeyPbkdf2
- Inherits:
-
Object
- Object
- SafeDb::KeyPbkdf2
- Defined in:
- lib/utils/kdfs/pbkdf2.rb
Overview
PBKDF2 is a powerful leading Key Derivation Function (KDF) that exists to convert low entropy human created passwords into a high entropy key that is computationally infeasible to acquire through brute force.
As human generated passwords have a relatively small key space, key derivation functions must be slow to compute with any implementation.
PBKDF2 offers an iteration count that configures the number of iterations performed to create the key.
One million (1,000,000) should be the iteration count’s lower bound.
Upgrading the OpenSSL pbkdf2_hmac Behaviour
As soon as the new Ruby and OpenSSL libraries become commonplace this class should be upgraded to use the new and improved OpenSSL::KDF.pbkdf2_hmac behaviour rather than OpenSSL::PKCS5.pbkdf2_hmac.
The difficulty is in detecting the operating system’s C libraries that are directly accessed for OpenSSL functionality. If the distinction can be made accurately, those with newer libraries can reap the benefits immediately.
PBKDF2 Cost Iteration Timings on an Intel i-5 Laptop
An IBM ThinkPad was used to generate the timings.
Memory RAM ~> 15GiB
Processors ~> Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz
The timing results show that a prudent value is somewhere between one hundred thousand and ten million iterations.
9.6 seconds for 10,000,000 ten million iterations
0.96 seconds for 1,000,000 one million iterations
0.096 seconds for 100,000 one hundred thousand iterations
Open key sets iteration counts for PBKDF2 in hexadecimal and a valid range starts at 1 and counts up in chunks of a hundred thousand (100,000).
1 ~> 100,000
5 ~> 500,000
10 ~> 1,000,000
16 ~> 16,000,000
256 ~> 256,000,000
The maximum iteration multiplier allowed is 16,384.
Constant Summary collapse
- PBKDF2_ITERATION_MULTIPLIER =
One million iterations is necessary due to the growth of GPU driven cloud based computing power that is curently being honed by mining BitCoin and training neural networks.
PBKDF2 Cost Iteration Timings on an Intel i-5 Laptop
An IBM ThinkPad was used to generate the timings.
Memory RAM ~> 15GiB Processors ~> Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz
The timing results show that a prudent value is somewhere between one hundred thousand and ten million iterations.
Open key sets iteration counts for PBKDF2 in hexadecimal and a valid range starts at 1 and counts up in chunks of a hundred thousand (100,000).
1 ~> 100,000 5 ~> 500,000 10 ~> 1,000,000 16 ~> 16,000,000 256 ~> 256,000,000
1
- ONE_HUNDRED_THOUSAND =
The quantity used to multiply the iteration multiplier by to gain the iteration count.
100000
- PBKDF2_EXPORT_KEY_LENGTH =
Documentation for this algorithm says this about the key length.
Make the key length larger than or equal to the output length of the underlying digest function, otherwise an attacker could simply try to brute-force the key.
According to PKCS#5, security is limited by the output length of the underlying digest function, i.e. security is not improved if a key length strictly larger than the digest output length is chosen.
Therefore, when using PKCS5 for password storage, it suffices to store values equal to the digest output length, nothing is gained by storing larger values.
OpenSSL::Digest::SHA384.new.digest_length
- PBKDF2_EXPORT_BIT_LENGTH =
For a 384 bit digest the key length is 48 bytes and the bit length is 384 bits.
PBKDF2_EXPORT_KEY_LENGTH * 8
- PBKDF2_SALT_LENGTH_BYTES =
The documented recommended salt length in bytes for the PBKDF2 algorithm is between 16 and 24 bytes. The setting here is at the upper bound of that range.
24
Class Method Summary collapse
-
.generate_key(human_secret, pbkdf2_string) ⇒ Key
Generate a 128 bit binary key from the PBKDF2 password derivation function.
-
.generate_pbkdf2_salt ⇒ String
Return a random cryptographic salt generated from twenty-four random bytes produced by a secure random number generator.
Class Method Details
.generate_key(human_secret, pbkdf2_string) ⇒ Key
Generate a 128 bit binary key from the PBKDF2 password derivation function. The most important input to this function is the human generated key. The best responsibly sourced key with at least 95% entropy will contain about 40 characters spread randomly over the set of 95 typable characters.
Aside from the human password the other inputs are
-
a base64 encoded randomly generated salt of 16 to 24 bytes
-
an iteration count of at least 1 million (due to GPU advances)
-
an output key length that is at least 16 bytes (128 bits)
-
a digest algorithm implementation (we use SHA512K)
The SafeDb::Key returned by this method encapsulates the derived key of the byte (bit) length specified.
PBKDF2 Output Key Length Note
Documentation for this algorithm says this about the key length.
Typically, the key length should be larger than or equal to the
output length of the underlying digest function, otherwise an
attacker could simply try to brute-force the key. According to
PKCS#5, security is limited by the output length of the underlying
digest function, i.e. security is not improved if a key length
strictly larger than the digest output length is chosen.
Therefore, when using PKCS5 for password storage, it suffices to
store values equal to the digest output length, nothing is gained
by storing larger values.
Upgrading the OpenSSL pbkdf2_hmac Behaviour
As soon as the new Ruby and OpenSSL libraries become commonplace this class should be upgraded to use the new and improved OpenSSL::KDF.pbkdf2_hmac behaviour rather than OpenSSL::PKCS5.pbkdf2_hmac.
The difficulty is in detecting the operating system’s C libraries that are directly accessed for OpenSSL functionality. If the distinction can be made accurately, those with newer libraries can reap the benefits immediately.
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/utils/kdfs/pbkdf2.rb', line 214 def self.generate_key human_secret, pbkdf2_string KeyError.not_new pbkdf2_string, "PBKDF2 Algorithm Salt" multiplier = pbkdf2_string.split("+")[0].to_i pbkdf2_salt = pbkdf2_string.split("+")[1] mult_msg = "Iteration multiplier is an integer from 1 to 16,384 not [#{multiplier}]." raise ArgumentError, mult_msg_msg unless( multiplier > 0 && multiplier < 16385 ) iteration_count = multiplier * ONE_HUNDRED_THOUSAND binary_salt = Key.to_binary_from_bit_string( Key64.to_bits( pbkdf2_salt ) ) err_msg = "Expected salt of #{PBKDF2_SALT_LENGTH_BYTES} bytes not #{binary_salt.length}." raise ArgumentError, err_msg unless binary_salt.length == PBKDF2_SALT_LENGTH_BYTES pbkdf2_key = OpenSSL::PKCS5.pbkdf2_hmac( human_secret, binary_salt, iteration_count, PBKDF2_EXPORT_KEY_LENGTH, OpenSSL::Digest::SHA384.new ) return Key.from_binary( pbkdf2_key ) end |
.generate_pbkdf2_salt ⇒ String
Return a random cryptographic salt generated from twenty-four random bytes produced by a secure random number generator. The returned salt is primarily a Base64 encoded string that can be stored and then passed to the generate_key method.
+ ------------ + -------- + ------------ + ------------- +
| | Bits | Bytes | Base64 |
| ------------ | -------- | ------------ | ------------- |
| PBKDF2 Salt | 192 Bits | 24 bytes | 32 characters |
+ ------------ + -------- + ------------ + ------------- +
The leading part of the character sequence indicates the length of the salt in chunks of 100,000 and is plus sign separated.
42+12345678abcdefgh12345678ABCDEFGH ~> 4,200,000 iterations
9+12345678abcdefgh12345678ABCDEFGH ~> 900,000 iterations
100+12345678abcdefgh12345678ABCDEFGH ~> 10,000,000 iterations
Note that the generate key method will convert the trailing 32 base64 characters back into a 24 byte binary string.
144 145 146 147 148 149 |
# File 'lib/utils/kdfs/pbkdf2.rb', line 144 def self.generate_pbkdf2_salt pbkdf2_salt = Key64.from_bits( Key.to_random_bits( PBKDF2_SALT_LENGTH_BYTES ) ) return "#{PBKDF2_ITERATION_MULTIPLIER}+#{pbkdf2_salt}" end |