Class: SaltyDog::PBKDF2

Inherits:
Object
  • Object
show all
Defined in:
lib/salty_dog/salty_dog.rb

Overview

PBKDF2 encapsulates the identically-named password-based key derivation function outlined in PKCS #5: Password-Based Cryptography Standard. PBKDF1, as set forth in the same document, has been recommended for removal from use, and thus is not implemented in SaltyDog. If you just need to generate keys, skip down to ::digest:

Constant Summary collapse

ALLOWED_DIGESTS =

According to the recommendation, the hash functions that are supported for HMAC (pseudorandom number generation) are SHA1, SHA224, SHA256, SHA384, AND SHA512. These are provided here.

[:sha1, :sha224, :sha256, :sha384, :sha512]

Class Method Summary collapse

Class Method Details

.build_digest(digest) ⇒ Object

Build the derived key. Called directly by SaltyDog::PBKDF2.digest.



53
54
55
56
57
58
59
60
# File 'lib/salty_dog/salty_dog.rb', line 53

def self.build_digest(digest)
  if !ALLOWED_DIGESTS.include?(digest)
    raise PBKDF2Error, 'Invalid digest'
  end

  klass = "OpenSSL::Digest::#{digest.to_s.upcase}"
  @digest = Object::const_get(klass).new
end

.calculate_key(digest, password, salt, l, r, iterations) ⇒ Object

The workhorse of SaltyDog::PBKDF2. ::calculate_key initiates the specified number of iterations of hashing in calculating each block of the derived key. All blocks are then concatenated together in computing the final derived key.



127
128
129
130
131
132
133
134
135
136
137
# File 'lib/salty_dog/salty_dog.rb', line 127

def self.calculate_key(digest, password, salt, l, r, iterations)
  t = ""

  for i in 1..l+1 do
    t << self.xor_sum(digest, password, salt, iterations, i)
  end

  total_length = digest.length * (l-1) + r
  sliced = t.slice(0..total_length - 1)
  sliced
end

.check_key_length_requirements(length) ⇒ Object

Check desired key length requirements. These are:

  • Must be present

  • Must be strictly positive

  • Must be no larger than (2^32 - 1) * digest length of the chosen hash

function

Raises a PBKDF2Error if any of these requirements are not met.

Raises:



72
73
74
75
76
# File 'lib/salty_dog/salty_dog.rb', line 72

def self.check_key_length_requirements(length)
  raise PBKDF2Error, 'A key length must be provided' if !length
  raise PBKDF2Error, 'Desired key is too long' if ((2**32 - 1) * @digest.length) < length
  raise PBKDF2Error, 'Desired key length must be positive' if length < 0
end

.digest(options = {}) ⇒ Object

The primary point of entry for SaltyDog::PBKDF2. The available options are:

  • :digest - One of :sha1, :sha224, :sha256, :sha384, or

:sha512. Defaults to :sha512.

  • :password - A password for use in deriving the key. Required, and must be a string.

  • :salt - A salt that is concatenated to the password in key derivation.

Required, and must ba a string.

  • :length - The desired length, in bytes, of the derived key. Required.

  • :iterations - The number of iterations to be used in key derivation.

Defaults to 10000.

Returns a hex-string representing the derived key.



36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/salty_dog/salty_dog.rb', line 36

def self.digest(options = {})
  digest = options[:digest] || :sha512
  self.build_digest(digest)
  
  check_key_length_requirements(options[:length])
  @length = options[:length]
  @iterations = options[:iterations] || 10000

  @l = (@length / @digest.length).ceil
  @r = @length - (@l - 1) * @digest.length

  self.calculate_key(@digest, options[:password].to_s, options[:salt].to_s, @l, @r, @iterations).unpack('H*')[0]
end

.prf(digest, password, seed) ⇒ Object

Uses a pseudorandom function based on the digest function provided to SaltyDog::PBKDF2.digest to generate input for each iteration round.

Raises:



97
98
99
100
# File 'lib/salty_dog/salty_dog.rb', line 97

def self.prf(digest, password, seed)
  raise PBKDF2Error if !password || !seed
  OpenSSL::HMAC.digest(digest, password, seed)
end

.xor(x, y) ⇒ Object

XOR two strings x and y.

Raises a PBKDF2Error if a and b are not the same length.

Returns a string of bytes representing the XORed value.

Raises:



85
86
87
88
89
90
91
# File 'lib/salty_dog/salty_dog.rb', line 85

def self.xor(x, y)
  raise PBKDF2Error, 'XOR arguments are not the same length' if x.length - y.length != 0 
  output = "".encode('ASCII-8BIT')

  x.bytes.zip(y.bytes) { |x,y| output << (x^y) }
  output
end

.xor_sum(digest, password, salt, iterations, block_number) ⇒ Object

Within each iteration, SaltyDog::PBKDF2.xor_sum XORs each block of output from SaltyDog::PBKDF2.prf. The result of this chain of XORs is provided to ::calculate_key to be used as a block of the final derived key.



107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/salty_dog/salty_dog.rb', line 107

def self.xor_sum(digest, password, salt, iterations, block_number)
  packed_index = [block_number].pack("N")
  seed = salt + packed_index
  final = self.prf(digest, password, seed)
  u = final

  for i in 2..iterations do
    u = self.prf(digest, password, u)
    final = self.xor(final, u)
  end

  final
end