PWhash add key derivation.
This commit is contained in:
parent
3a078523f5
commit
8da7fb47ec
@ -4,7 +4,7 @@ if ARGV.empty?
|
||||
puts "Help select Pwhash ops/mem limits for your application."
|
||||
puts "Usage: #{PROGRAM_NAME} time_min [time_max] [mem_max]"
|
||||
puts "\ttime is in seconds"
|
||||
puts "\tmem is in K"
|
||||
puts "\tmem is in bytes"
|
||||
exit 1
|
||||
end
|
||||
|
||||
@ -14,7 +14,7 @@ time_limit = if t = ARGV.shift?
|
||||
else
|
||||
time_min * 4
|
||||
end
|
||||
mem_limit = (ARGV.shift?.try &.to_i || (128*1024)).to_u64
|
||||
mem_limit = (ARGV.shift?.try &.to_i || (Cox::Pwhash::MEMLIMIT_MAX)).to_u64
|
||||
pwhash = Cox::Pwhash.new
|
||||
pass = "1234"
|
||||
|
||||
@ -24,10 +24,10 @@ loop do
|
||||
|
||||
loop do
|
||||
# p pwhash
|
||||
t = Time.measure { pwhash.hash_str pass }.to_f
|
||||
t = Time.measure { pwhash.store pass }.to_f
|
||||
s = String.build do |sb|
|
||||
sb << "mem_limit "
|
||||
sb << "%7d" % pwhash.memlimit
|
||||
sb << "%7d" % (pwhash.memlimit / 1024)
|
||||
sb << "K ops_limit "
|
||||
sb << "%7d" % pwhash.opslimit
|
||||
sb << " "
|
||||
@ -41,6 +41,7 @@ loop do
|
||||
puts ""
|
||||
|
||||
break if pwhash.memlimit >= mem_limit
|
||||
break if pwhash.opslimit == Cox::Pwhash::OPSLIMIT_MIN # Couldn't get past 1 iteration before going over time.
|
||||
pwhash.memlimit *= 2
|
||||
end
|
||||
|
||||
|
@ -1,15 +1,20 @@
|
||||
require "../spec_helper"
|
||||
|
||||
describe Cox::Pwhash do
|
||||
it "hashes and verifies a password" do
|
||||
private def pw_min
|
||||
pwhash = Cox::Pwhash.new
|
||||
|
||||
# set to minimum to speed up tests
|
||||
pwhash.memlimit = Cox::Pwhash::MEMLIMIT_MIN
|
||||
pwhash.opslimit = Cox::Pwhash::OPSLIMIT_MIN
|
||||
pwhash
|
||||
end
|
||||
|
||||
describe Cox::Pwhash do
|
||||
it "hashes and verifies a password" do
|
||||
pwhash = pw_min
|
||||
|
||||
pass = "1234"
|
||||
hash = pwhash.hash_str pass
|
||||
hash = pwhash.store pass
|
||||
pwhash.verify hash, pass
|
||||
expect_raises(Cox::Pwhash::PasswordVerifyError) do
|
||||
pwhash.verify hash, "5678"
|
||||
@ -19,4 +24,27 @@ describe Cox::Pwhash do
|
||||
pwhash.opslimit = Cox::Pwhash::OPSLIMIT_MAX
|
||||
pwhash.needs_rehash?(hash).should be_true
|
||||
end
|
||||
|
||||
it "key_derive fails without an algorithm" do
|
||||
pwhash = pw_min
|
||||
expect_raises(ArgumentError) do
|
||||
pwhash.key_derive pwhash.salt, "foo", 16
|
||||
end
|
||||
end
|
||||
|
||||
it "derives a key from a password" do
|
||||
pwhash = pw_min
|
||||
pwhash.algorithm = Cox::Pwhash::Algorithm::Argon2id13
|
||||
salt = pwhash.salt
|
||||
key1 = pwhash.key_derive salt, "foo", 16
|
||||
key2 = pwhash.key_derive salt, "foo", 16
|
||||
key3 = pwhash.key_derive salt, "bar", 16
|
||||
key4 = pwhash.key_derive pwhash.salt, "foo", 16
|
||||
|
||||
key1.bytesize.should eq 16
|
||||
key1.should eq key2
|
||||
key1.should_not eq key3
|
||||
key1.should_not eq key4
|
||||
# BUG: validate against known passwords
|
||||
end
|
||||
end
|
||||
|
@ -21,6 +21,11 @@ module Cox
|
||||
fun crypto_pwhash_opslimit_sensitive() : LibC::SizeT
|
||||
fun crypto_pwhash_opslimit_max() : LibC::SizeT
|
||||
fun crypto_pwhash_strbytes() : LibC::SizeT
|
||||
fun crypto_pwhash_alg_argon2i13() : LibC::Int
|
||||
fun crypto_pwhash_alg_argon2id13() : LibC::Int
|
||||
fun crypto_pwhash_saltbytes : LibC::SizeT
|
||||
fun crypto_pwhash_bytes_min() : LibC::SizeT
|
||||
fun crypto_pwhash_bytes_max() : LibC::SizeT
|
||||
fun crypto_generichash_blake2b_statebytes : LibC::SizeT
|
||||
fun crypto_generichash_blake2b_bytes : LibC::SizeT
|
||||
fun crypto_generichash_blake2b_bytes_min : LibC::SizeT
|
||||
@ -109,6 +114,17 @@ module Cox
|
||||
key : Pointer(LibC::UChar)
|
||||
) : LibC::Int
|
||||
|
||||
fun crypto_pwhash(
|
||||
key : Pointer(LibC::UChar),
|
||||
key_size : LibC::ULongLong,
|
||||
pass : Pointer(LibC::UChar),
|
||||
pass_size : LibC::ULongLong,
|
||||
salt : Pointer(LibC::UChar),
|
||||
optslimit : LibC::ULongLong,
|
||||
memlimit : LibC::SizeT,
|
||||
alg : LibC::Int,
|
||||
) : LibC::Int
|
||||
|
||||
fun crypto_pwhash_str(
|
||||
outstr : Pointer(LibC::UChar),
|
||||
pass : Pointer(LibC::UChar),
|
||||
|
@ -1,4 +1,7 @@
|
||||
module Cox
|
||||
# [Argon2 Password Hashing](https://libsodium.gitbook.io/doc/password_hashing/the_argon2i_function)
|
||||
# * #store #verify #needs_rehash? are used together for password verification.
|
||||
# * #key_derive is used on it's own to generate password based keys.
|
||||
class Pwhash
|
||||
class PasswordVerifyError < Cox::Error
|
||||
end
|
||||
@ -13,10 +16,24 @@ module Cox
|
||||
MEMLIMIT_MAX = LibSodium.crypto_pwhash_memlimit_max
|
||||
MEMLIMIT_INTERACTIVE = LibSodium.crypto_pwhash_memlimit_interactive
|
||||
|
||||
enum Algorithm
|
||||
Argon2i13 = 1
|
||||
Argon2id13 = 2
|
||||
end
|
||||
|
||||
property opslimit = OPSLIMIT_INTERACTIVE
|
||||
# Specified in bytes.
|
||||
property memlimit = MEMLIMIT_INTERACTIVE
|
||||
|
||||
def hash_str(pass)
|
||||
# Used by and must be set before calling #key_derive
|
||||
property algorithm : Algorithm?
|
||||
|
||||
# Apply the most recent password hashing algorithm agains a password.
|
||||
# Returns a opaque String which includes:
|
||||
# * the result of a memory-hard, CPU-intensive hash function applied to the password
|
||||
# * the automatically generated salt used for the previous computation
|
||||
# * the other parameters required to verify the password, including the algorithm identifier, its version, opslimit and memlimit.
|
||||
def store(pass)
|
||||
outstr = Bytes.new LibSodium::PWHASH_STR_SIZE
|
||||
if LibSodium.crypto_pwhash_str(outstr, pass, pass.bytesize, @opslimit, @memlimit) != 0
|
||||
raise Cox::Error.new("crypto_pwhash_str")
|
||||
@ -24,17 +41,20 @@ module Cox
|
||||
outstr
|
||||
end
|
||||
|
||||
# Verify a password against a stored String.
|
||||
# raises PasswordVerifyError on failure.
|
||||
def verify(str, pass)
|
||||
# BUG: verify str length
|
||||
case LibSodium.crypto_pwhash_str_verify(str, pass, pass.bytesize)
|
||||
when 0
|
||||
true
|
||||
# Passed
|
||||
else
|
||||
raise PasswordVerifyError.new
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def needs_rehash?(str)
|
||||
def needs_rehash?(str) : Bool
|
||||
# BUG: verify str length
|
||||
case LibSodium.crypto_pwhash_str_needs_rehash(str, @opslimit, @memlimit)
|
||||
when 0
|
||||
@ -45,5 +65,29 @@ module Cox
|
||||
raise Cox::Error.new("crypto_pwhash_str_needs_rehash")
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a consistent key based on [salt, pass, key_bytes, algorithm, ops_limit, mem_limit]
|
||||
#
|
||||
# Must set an algorithm before calling.
|
||||
def key_derive(salt : Bytes, pass : Bytes, key_bytes) : Bytes
|
||||
if alg = algorithm
|
||||
key = Bytes.new key_bytes
|
||||
if LibSodium.crypto_pwhash(key, key.bytesize, pass, pass.bytesize, salt, @opslimit, @memlimit, alg) != 0
|
||||
raise Cox::Error.new("crypto_pwhash_str")
|
||||
end
|
||||
key
|
||||
else
|
||||
raise ArgumentError.new("algorithm not set")
|
||||
end
|
||||
end
|
||||
|
||||
def key_derive(salt, pass, key_bytes)
|
||||
key_derive salt.to_slice, pass.to_slice, key_bytes
|
||||
end
|
||||
|
||||
# Returns a random salt for use with #key_derive
|
||||
def salt
|
||||
Random::Secure.random_bytes LibSodium.crypto_pwhash_saltbytes
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user