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 "Help select Pwhash ops/mem limits for your application."
|
||||||
puts "Usage: #{PROGRAM_NAME} time_min [time_max] [mem_max]"
|
puts "Usage: #{PROGRAM_NAME} time_min [time_max] [mem_max]"
|
||||||
puts "\ttime is in seconds"
|
puts "\ttime is in seconds"
|
||||||
puts "\tmem is in K"
|
puts "\tmem is in bytes"
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ time_limit = if t = ARGV.shift?
|
|||||||
else
|
else
|
||||||
time_min * 4
|
time_min * 4
|
||||||
end
|
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
|
pwhash = Cox::Pwhash.new
|
||||||
pass = "1234"
|
pass = "1234"
|
||||||
|
|
||||||
@ -24,10 +24,10 @@ loop do
|
|||||||
|
|
||||||
loop do
|
loop do
|
||||||
# p pwhash
|
# p pwhash
|
||||||
t = Time.measure { pwhash.hash_str pass }.to_f
|
t = Time.measure { pwhash.store pass }.to_f
|
||||||
s = String.build do |sb|
|
s = String.build do |sb|
|
||||||
sb << "mem_limit "
|
sb << "mem_limit "
|
||||||
sb << "%7d" % pwhash.memlimit
|
sb << "%7d" % (pwhash.memlimit / 1024)
|
||||||
sb << "K ops_limit "
|
sb << "K ops_limit "
|
||||||
sb << "%7d" % pwhash.opslimit
|
sb << "%7d" % pwhash.opslimit
|
||||||
sb << " "
|
sb << " "
|
||||||
@ -41,6 +41,7 @@ loop do
|
|||||||
puts ""
|
puts ""
|
||||||
|
|
||||||
break if pwhash.memlimit >= mem_limit
|
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
|
pwhash.memlimit *= 2
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
require "../spec_helper"
|
require "../spec_helper"
|
||||||
|
|
||||||
describe Cox::Pwhash do
|
private def pw_min
|
||||||
it "hashes and verifies a password" do
|
|
||||||
pwhash = Cox::Pwhash.new
|
pwhash = Cox::Pwhash.new
|
||||||
|
|
||||||
# set to minimum to speed up tests
|
# set to minimum to speed up tests
|
||||||
pwhash.memlimit = Cox::Pwhash::MEMLIMIT_MIN
|
pwhash.memlimit = Cox::Pwhash::MEMLIMIT_MIN
|
||||||
pwhash.opslimit = Cox::Pwhash::OPSLIMIT_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"
|
pass = "1234"
|
||||||
hash = pwhash.hash_str pass
|
hash = pwhash.store pass
|
||||||
pwhash.verify hash, pass
|
pwhash.verify hash, pass
|
||||||
expect_raises(Cox::Pwhash::PasswordVerifyError) do
|
expect_raises(Cox::Pwhash::PasswordVerifyError) do
|
||||||
pwhash.verify hash, "5678"
|
pwhash.verify hash, "5678"
|
||||||
@ -19,4 +24,27 @@ describe Cox::Pwhash do
|
|||||||
pwhash.opslimit = Cox::Pwhash::OPSLIMIT_MAX
|
pwhash.opslimit = Cox::Pwhash::OPSLIMIT_MAX
|
||||||
pwhash.needs_rehash?(hash).should be_true
|
pwhash.needs_rehash?(hash).should be_true
|
||||||
end
|
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
|
end
|
||||||
|
@ -21,6 +21,11 @@ module Cox
|
|||||||
fun crypto_pwhash_opslimit_sensitive() : LibC::SizeT
|
fun crypto_pwhash_opslimit_sensitive() : LibC::SizeT
|
||||||
fun crypto_pwhash_opslimit_max() : LibC::SizeT
|
fun crypto_pwhash_opslimit_max() : LibC::SizeT
|
||||||
fun crypto_pwhash_strbytes() : 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_statebytes : LibC::SizeT
|
||||||
fun crypto_generichash_blake2b_bytes : LibC::SizeT
|
fun crypto_generichash_blake2b_bytes : LibC::SizeT
|
||||||
fun crypto_generichash_blake2b_bytes_min : LibC::SizeT
|
fun crypto_generichash_blake2b_bytes_min : LibC::SizeT
|
||||||
@ -109,6 +114,17 @@ module Cox
|
|||||||
key : Pointer(LibC::UChar)
|
key : Pointer(LibC::UChar)
|
||||||
) : LibC::Int
|
) : 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(
|
fun crypto_pwhash_str(
|
||||||
outstr : Pointer(LibC::UChar),
|
outstr : Pointer(LibC::UChar),
|
||||||
pass : Pointer(LibC::UChar),
|
pass : Pointer(LibC::UChar),
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
module Cox
|
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 Pwhash
|
||||||
class PasswordVerifyError < Cox::Error
|
class PasswordVerifyError < Cox::Error
|
||||||
end
|
end
|
||||||
@ -13,10 +16,24 @@ module Cox
|
|||||||
MEMLIMIT_MAX = LibSodium.crypto_pwhash_memlimit_max
|
MEMLIMIT_MAX = LibSodium.crypto_pwhash_memlimit_max
|
||||||
MEMLIMIT_INTERACTIVE = LibSodium.crypto_pwhash_memlimit_interactive
|
MEMLIMIT_INTERACTIVE = LibSodium.crypto_pwhash_memlimit_interactive
|
||||||
|
|
||||||
|
enum Algorithm
|
||||||
|
Argon2i13 = 1
|
||||||
|
Argon2id13 = 2
|
||||||
|
end
|
||||||
|
|
||||||
property opslimit = OPSLIMIT_INTERACTIVE
|
property opslimit = OPSLIMIT_INTERACTIVE
|
||||||
|
# Specified in bytes.
|
||||||
property memlimit = MEMLIMIT_INTERACTIVE
|
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
|
outstr = Bytes.new LibSodium::PWHASH_STR_SIZE
|
||||||
if LibSodium.crypto_pwhash_str(outstr, pass, pass.bytesize, @opslimit, @memlimit) != 0
|
if LibSodium.crypto_pwhash_str(outstr, pass, pass.bytesize, @opslimit, @memlimit) != 0
|
||||||
raise Cox::Error.new("crypto_pwhash_str")
|
raise Cox::Error.new("crypto_pwhash_str")
|
||||||
@ -24,17 +41,20 @@ module Cox
|
|||||||
outstr
|
outstr
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Verify a password against a stored String.
|
||||||
|
# raises PasswordVerifyError on failure.
|
||||||
def verify(str, pass)
|
def verify(str, pass)
|
||||||
# BUG: verify str length
|
# BUG: verify str length
|
||||||
case LibSodium.crypto_pwhash_str_verify(str, pass, pass.bytesize)
|
case LibSodium.crypto_pwhash_str_verify(str, pass, pass.bytesize)
|
||||||
when 0
|
when 0
|
||||||
true
|
# Passed
|
||||||
else
|
else
|
||||||
raise PasswordVerifyError.new
|
raise PasswordVerifyError.new
|
||||||
end
|
end
|
||||||
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
def needs_rehash?(str)
|
def needs_rehash?(str) : Bool
|
||||||
# BUG: verify str length
|
# BUG: verify str length
|
||||||
case LibSodium.crypto_pwhash_str_needs_rehash(str, @opslimit, @memlimit)
|
case LibSodium.crypto_pwhash_str_needs_rehash(str, @opslimit, @memlimit)
|
||||||
when 0
|
when 0
|
||||||
@ -45,5 +65,29 @@ module Cox
|
|||||||
raise Cox::Error.new("crypto_pwhash_str_needs_rehash")
|
raise Cox::Error.new("crypto_pwhash_str_needs_rehash")
|
||||||
end
|
end
|
||||||
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
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user