class OpenKey::KeyPbkdf2

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.

Constants

ONE_HUNDRED_THOUSAND

The quantity used to multiply the iteration multiplier by to gain the iteration count.

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

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.

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
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.

Public Class Methods

generate_key(human_secret, pbkdf2_string) click to toggle source

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 {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.

@param 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.

@param 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.

@return [Key]

a key holder containing the key which can then be accessed via
many different formats. The {Key} returned by this method
encapsulates the derived key with the specified byte count.
# File lib/keytools/kdf.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() click to toggle source

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 {KeyPbkdf2.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.

@return [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.
# File lib/keytools/kdf.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