require 'ffi'
require 'ffi-compiler/loader'
module Argon2
# Direct external bindings. Call these methods via the Engine class to ensure points are dealt with
module Ext
extend FFI::Library
ffi_lib FFI::Compiler::Loader.find('argon2_wrap')
# int argon2i_hash_raw(const uint32_t t_cost, const uint32_t m_cost,
# const uint32_t parallelism, const void *pwd,
# const size_t pwdlen, const void *salt,
# const size_t saltlen, void *hash, const size_t hashlen);
attach_function :argon2i_hash_raw, [
:uint, :uint, :uint, :pointer,
:size_t, :pointer, :size_t, :pointer, :size_t], :int, :blocking => true
# void argon2_wrap(uint8_t *out, char *pwd, size_t pwdlen,
# uint8_t *salt, uint32_t saltlen, uint32_t t_cost,
# uint32_t m_cost, uint32_t lanes,
# uint8_t *secret, uint32_t secretlen)
attach_function :argon2_wrap, [
:pointer, :pointer, :size_t, :pointer, :uint, :uint,
:uint, :uint, :pointer, :size_t], :uint, :blocking => true
# int argon2i_verify(const char *encoded, const void *pwd,
# const size_t pwdlen);
attach_function :wrap_argon2_verify, [:pointer, :pointer, :size_t,
:pointer, :size_t], :int, :blocking => true
end
# The engine class shields users from the FFI interface.
# It is generally not advised to directly use this class.
class Engine
def self.hash_argon2i(password, salt, t_cost, m_cost)
result = ''
FFI::MemoryPointer.new(:char, Constants::OUT_LEN) do |buffer|
ret = Ext.argon2i_hash_raw(t_cost, 1 << m_cost, 1, password,
password.length, salt, salt.length,
buffer, Constants::OUT_LEN)
raise ArgonHashFail, ERRORS[ret.abs] unless ret == 0
result = buffer.read_string(Constants::OUT_LEN)
end
result.unpack('H*').join
end
def self.hash_argon2i_encode(password, salt, t_cost, m_cost, secret)
result = ''
secretlen = secret.nil? ? 0 : secret.bytesize
passwordlen = password.nil? ? 0 : password.bytesize
if salt.length != Constants::SALT_LEN
raise ArgonHashFail, "Invalid salt size"
end
FFI::MemoryPointer.new(:char, Constants::ENCODE_LEN) do |buffer|
ret = Ext.argon2_wrap(buffer, password, passwordlen,
salt, salt.length, t_cost, (1 << m_cost),
1, secret, secretlen)
raise ArgonHashFail, ERRORS[ret.abs] unless ret == 0
result = buffer.read_string(Constants::ENCODE_LEN)
end
result.delete "\0"
end
def self.argon2i_verify(pwd, hash, secret)
secretlen = secret.nil? ? 0 : secret.bytesize
passwordlen = pwd.nil? ? 0 : pwd.bytesize
ret = Ext.wrap_argon2_verify(hash, pwd, passwordlen, secret, secretlen)
return false if ERRORS[ret.abs] == 'ARGON2_DECODING_FAIL'
raise ArgonHashFail, ERRORS[ret.abs] unless ret == 0
true
end
end
end