Class: RbNaCl::PasswordHash::Argon2

Inherits:
Object
  • Object
show all
Extended by:
Sodium
Defined in:
lib/rbnacl/password_hash/argon2.rb

Overview

Since version 1.0.9, Sodium provides a password hashing scheme called Argon2. Argon2 summarizes the state of the art in the design of memory- hard functions. It aims at the highest memory filling rate and effective use of multiple computing units, while still providing defense against tradeoff attacks. It prevents ASICs from having a significant advantage over software implementations.

Constant Summary collapse

ARGON2_MIN_OUTLEN =
16
ARGON2_MAX_OUTLEN =
0xFFFFFFFF
ARGON_ERROR_CODES =
{
  -1 => "ARGON2_OUTPUT_PTR_NULL", -2 => "ARGON2_OUTPUT_TOO_SHORT",
  -3 => "ARGON2_OUTPUT_TOO_LONG", -4 => "ARGON2_PWD_TOO_SHORT",
  -5 => "ARGON2_PWD_TOO_LONG", -6 => "ARGON2_SALT_TOO_SHORT",
  -7 => "ARGON2_SALT_TOO_LONG", -8 => "ARGON2_AD_TOO_SHORT",
  -9 => "ARGON2_AD_TOO_LONG", -10 => "ARGON2_SECRET_TOO_SHORT",
  -11 => "ARGON2_SECRET_TOO_LONG", -12 => "ARGON2_TIME_TOO_SMALL",
  -13 => "ARGON2_TIME_TOO_LARGE", -14 => "ARGON2_MEMORY_TOO_LITTLE",
  -15 => "ARGON2_MEMORY_TOO_MUCH", -16 => "ARGON2_LANES_TOO_FEW",
  -17 => "ARGON2_LANES_TOO_MANY", -18 => "ARGON2_PWD_PTR_MISMATCH",
  -19 => "ARGON2_SALT_PTR_MISMATCH", -20 => "ARGON2_SECRET_PTR_MISMATCH",
  -21 => "ARGON2_AD_PTR_MISMATCH", -22 => "ARGON2_MEMORY_ALLOCATION_ERROR",
  -23 => "ARGON2_FREE_MEMORY_CBK_NULL", -24 => "ARGON2_ALLOCATE_MEMORY_CBK_NULL",
  -25 => "ARGON2_INCORRECT_PARAMETER", -26 => "ARGON2_INCORRECT_TYPE",
  -27 => "ARGON2_OUT_PTR_MISMATCH", -28 => "ARGON2_THREADS_TOO_FEW",
  -29 => "ARGON2_THREADS_TOO_MANY", -30 => "ARGON2_MISSING_ARGS",
  -31 => "ARGON2_ENCODING_FAIL", -32 => "ARGON2_DECODING_FAIL",
  -33 => "ARGON2_THREAD_FAIL", -34 => "ARGON2_DECODING_LENGTH_FAIL",
  -35 => "ARGON2_VERIFY_MISMATCH"
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Sodium

primitive, sodium_constant, sodium_function, sodium_function_with_return_code, sodium_primitive, sodium_type

Constructor Details

#initialize(opslimit, memlimit, digest_size = nil) ⇒ RbNaCl::PasswordHash::Argon2

Create a new Argon2 password hash object

opslimit and memlimit may be an integer, or one of the following symbols:

[:interactive] Suitable for interactive online operations. This requires 32 Mb of dedicated RAM. [:moderate] A compromise between interactive and sensitive. This requires 128 Mb of dedicated RAM, and takes about 0.7 seconds on a 2.8 Ghz Core i7 CPU. [:sensitive] For highly sensitive and non-interactive operations. This requires 128 Mb of dedicated RAM, and takes about 0.7 seconds on a 2.8 Ghz Core i7 CPU

Parameters:

  • opslimit (Integer)

    the CPU cost (1..10)

  • memlimit (Integer)

    the memory cost (e.g. 2**24)

  • digest_size (Integer) (defaults to: nil)

    the byte length of the resulting digest



95
96
97
98
99
# File 'lib/rbnacl/password_hash/argon2.rb', line 95

def initialize(opslimit, memlimit, digest_size = nil)
  @opslimit    = self.class.opslimit_value(opslimit)
  @memlimit    = self.class.memlimit_value(memlimit)
  @digest_size = self.class.digest_size_value(digest_size) if digest_size
end

Class Method Details

.digest_size_value(digest_size) ⇒ Integer

Clamps digest size between 16..4294967295

Returns:

  • (Integer)

    digest_size a valid value for digest size

Raises:



215
216
217
218
219
220
221
# File 'lib/rbnacl/password_hash/argon2.rb', line 215

def self.digest_size_value(digest_size)
  digest_size = digest_size.to_i
  raise LengthError, "digest size too short" if digest_size < ARGON2_MIN_OUTLEN
  raise LengthError, "digest size too long"  if digest_size > ARGON2_MAX_OUTLEN

  digest_size
end

.digest_str_verify(password, digest_string) ⇒ boolean

Compares a password with a digest string

Parameters:

  • password (String)

    to be hashed

  • digest_string (String)

    to compare to

Returns:

  • (boolean)

    true if password matches digest_string

Raises:

  • (ArgumentError)


164
165
166
167
168
169
170
171
172
# File 'lib/rbnacl/password_hash/argon2.rb', line 164

def self.digest_str_verify(password, digest_string)
  raise ArgumentError, "password must be a String" unless password.is_a?(String)
  raise ArgumentError, "digest_string must be a String" unless digest_string.is_a?(String)

  pwhash_str_verify(
    digest_string,
    password, password.bytesize
  )
end

.memlimit_value(memlimit) ⇒ Integer

Clamps memlimit between 8192 bytes and 4 TB (eg. 2**32)

Parameters:

  • memlimit, (Integer)

    in bytes

Returns:

  • (Integer)

    memlimit a valid value for memlimit

Raises:

  • (ArgumentError)

    if the value is out of range



199
200
201
202
203
204
205
206
207
208
# File 'lib/rbnacl/password_hash/argon2.rb', line 199

def self.memlimit_value(memlimit)
  case memlimit
  when :interactive then MEMLIMIT_INTERACTIVE
  when :moderate then MEMLIMIT_MODERATE
  when :sensitive then MEMLIMIT_SENSITIVE
  when MEMLIMIT_MIN..MEMLIMIT_MAX then memlimit.to_i
  else
    raise ArgumentError, "memlimit must be within the range 2**(13..32)"
  end
end

.opslimit_value(opslimit) ⇒ Integer

Clamps opslimit to an acceptable range (3..10)

Parameters:

  • opslimit (Integer)

    value to be checked

Returns:

  • (Integer)

    opslimit a valid value for opslimit

Raises:

  • (ArgumentError)

    if the value is out of range



181
182
183
184
185
186
187
188
189
190
# File 'lib/rbnacl/password_hash/argon2.rb', line 181

def self.opslimit_value(opslimit)
  case opslimit
  when :interactive then OPSLIMIT_INTERACTIVE
  when :moderate then OPSLIMIT_MODERATE
  when :sensitive then OPSLIMIT_SENSITIVE
  when OPSLIMIT_MIN..OPSLIMIT_MAX then opslimit.to_i
  else
    raise ArgumentError, "opslimit must be within the range 3..10"
  end
end

Instance Method Details

#digest(password, salt, algo = nil) ⇒ String

Calculate an Argon2 digest for a given password and salt

Parameters:

  • password (String)

    to be hashed

  • salt (String)

    to make the digest unique

  • digest (Symbol)

    algorithm to use (may be :argon2i or :argon2id) if nil, the default is determined by libsodium (argon2i for libsodium < 1.0.15, and argon2id for libsodium >= 1.0.15).

Returns:

  • (String)

    scrypt digest of the string as raw bytes

Raises:

  • (ArgumentError)


111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/rbnacl/password_hash/argon2.rb', line 111

def digest(password, salt, algo = nil)
  raise ArgumentError, "digest_size is required" unless @digest_size

  digest = Util.zeros(@digest_size)
  salt   = Util.check_string(salt, SALTBYTES, "salt")

  if algo.nil?
    algorithm = ALG_DEFAULT
  elsif algo == :argon2i
    algorithm = ALG_ARGON2I13
  elsif algo == :argon2id && Sodium::Version::ARGON2ID_SUPPORTED
    algorithm = ALG_ARGON2ID13
  else
    raise ArgumentError, "digest algorithm is not supported"
  end

  status = self.class.pwhash(
    digest, @digest_size,
    password, password.bytesize, salt,
    @opslimit, @memlimit, algorithm
  )
  raise CryptoError, ARGON_ERROR_CODES[status] if status.nonzero?

  digest
end

#digest_str(password) ⇒ String

Calculate an Argon2 digest in the form of a crypt-style string. The resulting string encodes the parameters and salt.

Parameters:

  • password (String)

    to be hashed

Returns:

  • (String)

    argon2 digest string

Raises:

  • (ArgumentError)


143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/rbnacl/password_hash/argon2.rb', line 143

def digest_str(password)
  raise ArgumentError, "password must be a String" unless password.is_a?(String)

  result = Util.zeros(STRBYTES)

  ok = self.class.pwhash_str(
    result,
    password, password.bytesize,
    @opslimit, @memlimit
  )
  raise CryptoError, "unknown error in Argon2#digest_str" unless ok

  result.delete("\x00")
end