Class: SafeDb::KeyPbkdf2

Inherits:
Object
  • Object
show all
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

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.

Parameters:

  • human_secret (String)

    a robust human generated password with as much entropy as can be mustered. Remember that 40 characters spread randomly over the key space of about 95 characters and not relating to any dictionary word or name is the way to generate a powerful key that has embedded a near 100% entropy rating.

  • pbkdf2_string (String)

    this is a relatively small iteration count multiplier separated from the main salt characters by a plus sign. The salt characters will consist of 32 base64 characters which can be stored and fed into the generate_key.

    The salt string presented here must have either been recently generated by generate_pbkdf2salt or read from a persistence store and resubmitted here in order to regenerate the same key.

Returns:

  • (Key)

    a key holder containing the key which can then be accessed via many different formats. The SafeDb::Key returned by this method encapsulates the derived key with the specified byte count.

Raises:

  • (ArgumentError)


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_saltString

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.

Returns:

  • (String)

    a relatively small iteration count multiplier separated from the main salt characters by a plus sign. The salt characters will consist of 32 base64 characters which can be stored and fed into the generate_key.

    These 32 characters are a representation of the twenty-four (24) randomly and securely generated bytes.



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