module SCryptUtil
Provides the Java.scrypt-like functions
@author Pandu POLUAN <pepoluan@gmail.com> @note Unlike Java.scrypt, the scrypt() function has default values
Constants
- VERSION
Version of this module/gem
Public Class Methods
Checks if a password actually matches a hash (in MCF syntax, probably from DB)
@param password [String] The password to be checked @param hash_mcf [String] The hash (in MCF syntax) to be compared to @param secure_compare [Boolean] Whether to use the more secure but much slower string comparison function @return [Boolean] True if password matches the hash
# File lib/scrypt-util.rb, line 76 def SCryptUtil.check(password, hash_mcf, secure_compare = true) # See EndNote[3] return false if hash_mcf.nil? || hash_mcf.empty? elems = hash_mcf.split('$') raise "Unrecognized MCF hash format: " + hash_mcf if elems.length != 5 signature, params_hex, salt64, hash64 = elems[1..4] return "Unrecognized MCF hash format: " + hash_mcf if \ signature.empty? || params_hex.empty? || salt64.empty? || hash64.empty? salt_bin = Base64.decode64(salt64) params_i = params_hex.to_i(16) nlog = (params_i >> 16) r = (params_i >> 8) & 0xff p = params_i & 0xff calculated = SCrypt::Engine.scrypt(password, salt_bin, (2 ** nlog), r, p, @@HashLength) calculated64 = Base64.strict_encode64(calculated) return secure_compare ? SCryptUtil.secure_compare_string(hash64, calculated64) : (hash64 == calculated64) end
Takes a password and outputs a hash (in MCF syntax) using an internally generated salt
@param password [String] The password to be hashed @param n [Int] CPU/Memory cost parameter, must be power of 2, larger than 1. Default 32768 @param r [Int] Block size parameter, 0 < r < 256. Default 8 @param p [Int] Parallelization parameter, 0 < p < 256. Default 1 @raise [ArgumentError] If any of the parameters do not fulfill the requirements @return [String] The hash (and parameters and salt) in MCF syntax @note In the future, the default values for n, r, and p might need to be increased to adapt to
Moore's Law
# File lib/scrypt-util.rb, line 39 def SCryptUtil.scrypt(password, n = 32768, r = 8, p = 1) raise ArgumentError, 'n must be power of 2, larger than 1' unless ( n > 1 && ( (n & (n-1)) == 0 ) ) raise ArgumentError, '0 < r < 256' unless ( r < 256 && r > 0 ) rause ArgumentError, '0 < p < 256' unless ( p < 256 && p > 0 ) nlog = 0 while n > 1 do nlog += 1 n = n >> 1 end # Generate a "long enough" salt. The function will return a hexstring salt_hex = SCrypt::Engine.generate_salt() # Trim salt to desired # of bytes; 1 byte = 2 hex digits salt_hex = salt_hex[0,@@SaltLength*2] # Change hex digit pairs to actual bytes salt_bin = [salt_hex].pack('H*') # Unlike the generate_salt() function, the scrypt() function returns binary bytes hash_bin = SCrypt::Engine.scrypt(password, salt_bin, (2 ** nlog), r, p, @@HashLength) params_i = (nlog << 16) | (r << 8) | p salt64 = Base64.strict_encode64(salt_bin) hash64 = Base64.strict_encode64(hash_bin) hash_mcf = '$s0$' + params_i.to_s(16) + '$' + salt64 + '$' + hash64 return hash_mcf end
A string-compare function which is (hopefully) resistant to timing attacks since the time to perform
comparison depends only on the length of the longest string, and unlike memcmp() does not depend on how when the first equality happened.
@author Pandu POLUAN <pepoluan@gmail.com> @param str1 [String] First string to compare @param str2 [String] Second string to compare @note This function is SLOW, and it is kind-of by design.
# File lib/scrypt-util.rb, line 109 def SCryptUtil.secure_compare_string(str1, str2) raise ArgumentError, 'str1 and str2 must both be a non-empty string' if \ str1.nil? || str1.empty? || str2.nil? || str2.empty? s1 = str1.bytes s2 = str2.bytes shortest, longest = [s1.length - 1, s2.length - 1].minmax rslt = 0 for idx in (0..longest) # See EndNote[1] rslt |= (idx > shortest) ? ((s1[0] ^ s2[0]) | 0xFF) : (s1[idx] ^ s2[idx]) end return rslt == 0 end