diff --git a/spec/cox/pwhash_spec.cr b/spec/cox/pwhash_spec.cr new file mode 100644 index 0000000..b7f61c9 --- /dev/null +++ b/spec/cox/pwhash_spec.cr @@ -0,0 +1,22 @@ +require "../spec_helper" + +describe Cox::Pwhash do + it "hashes and verifies a password" do + pwhash = Cox::Pwhash.new + + # set to minimum to speed up tests + pwhash.memlimit = Cox::Pwhash::MEMLIMIT_MIN + pwhash.opslimit = Cox::Pwhash::OPSLIMIT_MIN + + pass = "1234" + hash = pwhash.hash_str pass + pwhash.verify hash, pass + expect_raises(Cox::Pwhash::PasswordVerifyError) do + pwhash.verify hash, "5678" + end + + pwhash.needs_rehash?(hash).should be_false + pwhash.opslimit = Cox::Pwhash::OPSLIMIT_MAX + pwhash.needs_rehash?(hash).should be_true + end +end diff --git a/src/cox/lib_sodium.cr b/src/cox/lib_sodium.cr index 4d25991..03863c4 100644 --- a/src/cox/lib_sodium.cr +++ b/src/cox/lib_sodium.cr @@ -12,6 +12,15 @@ module Cox fun crypto_sign_bytes() : LibC::SizeT fun crypto_kdf_keybytes() : LibC::SizeT fun crypto_kdf_contextbytes() : LibC::SizeT + fun crypto_pwhash_memlimit_min() : LibC::SizeT + fun crypto_pwhash_memlimit_interactive() : LibC::SizeT + fun crypto_pwhash_memlimit_max() : LibC::SizeT + fun crypto_pwhash_opslimit_min() : LibC::SizeT + fun crypto_pwhash_opslimit_interactive() : LibC::SizeT + fun crypto_pwhash_opslimit_moderate() : LibC::SizeT + fun crypto_pwhash_opslimit_sensitive() : LibC::SizeT + fun crypto_pwhash_opslimit_max() : LibC::SizeT + fun crypto_pwhash_strbytes() : LibC::SizeT PUBLIC_KEY_BYTES = crypto_box_publickeybytes() SECRET_KEY_BYTES = crypto_box_secretkeybytes() @@ -22,6 +31,7 @@ module Cox SIGNATURE_BYTES = crypto_sign_bytes() KDF_KEY_BYTES = crypto_kdf_keybytes() KDF_CONTEXT_BYTES = crypto_kdf_contextbytes() + PWHASH_STR_BYTES = crypto_pwhash_strbytes() fun crypto_box_keypair( public_key_output : Pointer(LibC::UChar), @@ -66,7 +76,6 @@ module Cox public_key : Pointer(LibC::UChar) ) : LibC::Int - fun crypto_kdf_derive_from_key( subkey : Pointer(LibC::UChar), subkey_len : LibC::SizeT, @@ -74,5 +83,25 @@ module Cox ctx : Pointer(LibC::UChar), key : Pointer(LibC::UChar) ) : LibC::Int + + fun crypto_pwhash_str( + outstr : Pointer(LibC::UChar), + pass : Pointer(LibC::UChar), + pass_size : LibC::ULongLong, + optslimit : LibC::ULongLong, + memlimit : LibC::SizeT, + ) : LibC::Int + + fun crypto_pwhash_str_verify( + str : Pointer(LibC::UChar), + pass : Pointer(LibC::UChar), + pass_size : LibC::ULongLong, + ) : LibC::Int + + fun crypto_pwhash_str_needs_rehash( + str : Pointer(LibC::UChar), + optslimit : LibC::ULongLong, + memlimit : LibC::SizeT, + ) : LibC::Int end end diff --git a/src/cox/pwhash.cr b/src/cox/pwhash.cr new file mode 100644 index 0000000..944da48 --- /dev/null +++ b/src/cox/pwhash.cr @@ -0,0 +1,49 @@ +module Cox + class Pwhash + class PasswordVerifyError < Cox::Error + end + + OPSLIMIT_MIN = LibSodium.crypto_pwhash_opslimit_min + OPSLIMIT_INTERACTIVE = LibSodium.crypto_pwhash_opslimit_interactive + OPSLIMIT_MODERATE = LibSodium.crypto_pwhash_opslimit_moderate + OPSLIMIT_SENSITIVE = LibSodium.crypto_pwhash_opslimit_sensitive + OPSLIMIT_MAX = LibSodium.crypto_pwhash_opslimit_max + + MEMLIMIT_MIN = LibSodium.crypto_pwhash_memlimit_min + MEMLIMIT_MAX = LibSodium.crypto_pwhash_memlimit_max + MEMLIMIT_INTERACTIVE = LibSodium.crypto_pwhash_memlimit_interactive + + property opslimit = OPSLIMIT_INTERACTIVE + property memlimit = MEMLIMIT_INTERACTIVE + + def hash_str(pass) + outstr = Bytes.new LibSodium::PWHASH_STR_BYTES + if LibSodium.crypto_pwhash_str(outstr, pass, pass.bytesize, @opslimit, @memlimit) != 0 + raise Cox::Error.new("crypto_pwhash_str") + end + outstr + end + + def verify(str, pass) + # BUG: verify str length + case LibSodium.crypto_pwhash_str_verify(str, pass, pass.bytesize) + when 0 + true + else + raise PasswordVerifyError.new + end + end + + def needs_rehash?(str) + # BUG: verify str length + case LibSodium.crypto_pwhash_str_needs_rehash(str, @opslimit, @memlimit) + when 0 + false + when 1 + true + else + raise Cox::Error.new("crypto_pwhash_str_needs_rehash") + end + end + end +end