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

check(password, hash_mcf, secure_compare = true) click to toggle source

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
scrypt(password, n = 32768, r = 8, p = 1) click to toggle source

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
secure_compare_string(str1, str2) click to toggle source

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