add password utilities to crypto module
This commit is contained in:
parent
78b38ee544
commit
7397c3ebbc
@ -9,10 +9,16 @@ import (
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/argon2"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
)
|
||||
|
||||
func GetFunctionList() map[string]luajit.GoFunction {
|
||||
@ -36,6 +42,16 @@ func GetFunctionList() map[string]luajit.GoFunction {
|
||||
"random_hex": random_hex,
|
||||
"random_string": random_string,
|
||||
"secure_compare": secure_compare,
|
||||
"argon2_hash": argon2_hash,
|
||||
"argon2_verify": argon2_verify,
|
||||
"bcrypt_hash": bcrypt_hash,
|
||||
"bcrypt_verify": bcrypt_verify,
|
||||
"scrypt_hash": scrypt_hash,
|
||||
"scrypt_verify": scrypt_verify,
|
||||
"pbkdf2_hash": pbkdf2_hash,
|
||||
"pbkdf2_verify": pbkdf2_verify,
|
||||
"password_hash": password_hash,
|
||||
"password_verify": password_verify,
|
||||
}
|
||||
}
|
||||
|
||||
@ -235,3 +251,298 @@ func secure_compare(s *luajit.State) int {
|
||||
s.PushBoolean(hmac.Equal([]byte(a), []byte(b)))
|
||||
return 1
|
||||
}
|
||||
|
||||
func argon2_hash(s *luajit.State) int {
|
||||
password := s.ToString(1)
|
||||
time := uint32(1)
|
||||
memory := uint32(64 * 1024)
|
||||
threads := uint8(4)
|
||||
keyLen := uint32(32)
|
||||
|
||||
if s.GetTop() >= 2 && !s.IsNil(2) {
|
||||
time = uint32(s.ToNumber(2))
|
||||
}
|
||||
if s.GetTop() >= 3 && !s.IsNil(3) {
|
||||
memory = uint32(s.ToNumber(3))
|
||||
}
|
||||
if s.GetTop() >= 4 && !s.IsNil(4) {
|
||||
threads = uint8(s.ToNumber(4))
|
||||
}
|
||||
if s.GetTop() >= 5 && !s.IsNil(5) {
|
||||
keyLen = uint32(s.ToNumber(5))
|
||||
}
|
||||
|
||||
salt := make([]byte, 16)
|
||||
if _, err := rand.Read(salt); err != nil {
|
||||
s.PushNil()
|
||||
s.PushString("failed to generate salt")
|
||||
return 2
|
||||
}
|
||||
|
||||
hash := argon2.IDKey([]byte(password), salt, time, memory, threads, keyLen)
|
||||
encodedSalt := base64.RawStdEncoding.EncodeToString(salt)
|
||||
encodedHash := base64.RawStdEncoding.EncodeToString(hash)
|
||||
|
||||
result := fmt.Sprintf("$argon2id$v=19$m=%d,t=%d,p=%d$%s$%s",
|
||||
memory, time, threads, encodedSalt, encodedHash)
|
||||
|
||||
s.PushString(result)
|
||||
return 1
|
||||
}
|
||||
|
||||
func argon2_verify(s *luajit.State) int {
|
||||
password := s.ToString(1)
|
||||
hash := s.ToString(2)
|
||||
|
||||
parts := strings.Split(hash, "$")
|
||||
if len(parts) != 6 || parts[1] != "argon2id" {
|
||||
s.PushBoolean(false)
|
||||
return 1
|
||||
}
|
||||
|
||||
var memory, time uint32
|
||||
var threads uint8
|
||||
if _, err := fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &time, &threads); err != nil {
|
||||
s.PushBoolean(false)
|
||||
return 1
|
||||
}
|
||||
|
||||
salt, err := base64.RawStdEncoding.DecodeString(parts[4])
|
||||
if err != nil {
|
||||
s.PushBoolean(false)
|
||||
return 1
|
||||
}
|
||||
|
||||
expectedHash, err := base64.RawStdEncoding.DecodeString(parts[5])
|
||||
if err != nil {
|
||||
s.PushBoolean(false)
|
||||
return 1
|
||||
}
|
||||
|
||||
actualHash := argon2.IDKey([]byte(password), salt, time, memory, threads, uint32(len(expectedHash)))
|
||||
s.PushBoolean(hmac.Equal(actualHash, expectedHash))
|
||||
return 1
|
||||
}
|
||||
|
||||
func bcrypt_hash(s *luajit.State) int {
|
||||
password := s.ToString(1)
|
||||
cost := 12
|
||||
|
||||
if s.GetTop() >= 2 && !s.IsNil(2) {
|
||||
cost = int(s.ToNumber(2))
|
||||
if cost < 4 || cost > 31 {
|
||||
s.PushNil()
|
||||
s.PushString("invalid cost (must be 4-31)")
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), cost)
|
||||
if err != nil {
|
||||
s.PushNil()
|
||||
s.PushString("bcrypt hash failed")
|
||||
return 2
|
||||
}
|
||||
|
||||
s.PushString(string(hash))
|
||||
return 1
|
||||
}
|
||||
|
||||
func bcrypt_verify(s *luajit.State) int {
|
||||
password := s.ToString(1)
|
||||
hash := s.ToString(2)
|
||||
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
s.PushBoolean(err == nil)
|
||||
return 1
|
||||
}
|
||||
|
||||
func scrypt_hash(s *luajit.State) int {
|
||||
password := s.ToString(1)
|
||||
N := 32768 // CPU cost
|
||||
r := 8 // block size
|
||||
p := 1 // parallelization
|
||||
keyLen := 32 // key length
|
||||
|
||||
if s.GetTop() >= 2 && !s.IsNil(2) {
|
||||
N = int(s.ToNumber(2))
|
||||
}
|
||||
if s.GetTop() >= 3 && !s.IsNil(3) {
|
||||
r = int(s.ToNumber(3))
|
||||
}
|
||||
if s.GetTop() >= 4 && !s.IsNil(4) {
|
||||
p = int(s.ToNumber(4))
|
||||
}
|
||||
if s.GetTop() >= 5 && !s.IsNil(5) {
|
||||
keyLen = int(s.ToNumber(5))
|
||||
}
|
||||
|
||||
salt := make([]byte, 16)
|
||||
if _, err := rand.Read(salt); err != nil {
|
||||
s.PushNil()
|
||||
s.PushString("failed to generate salt")
|
||||
return 2
|
||||
}
|
||||
|
||||
hash, err := scrypt.Key([]byte(password), salt, N, r, p, keyLen)
|
||||
if err != nil {
|
||||
s.PushNil()
|
||||
s.PushString("scrypt hash failed")
|
||||
return 2
|
||||
}
|
||||
|
||||
encodedSalt := base64.RawStdEncoding.EncodeToString(salt)
|
||||
encodedHash := base64.RawStdEncoding.EncodeToString(hash)
|
||||
|
||||
result := fmt.Sprintf("$scrypt$N=%d,r=%d,p=%d$%s$%s", N, r, p, encodedSalt, encodedHash)
|
||||
s.PushString(result)
|
||||
return 1
|
||||
}
|
||||
|
||||
func scrypt_verify(s *luajit.State) int {
|
||||
password := s.ToString(1)
|
||||
hash := s.ToString(2)
|
||||
|
||||
parts := strings.Split(hash, "$")
|
||||
if len(parts) != 5 || parts[1] != "scrypt" {
|
||||
s.PushBoolean(false)
|
||||
return 1
|
||||
}
|
||||
|
||||
var N, r, p int
|
||||
if _, err := fmt.Sscanf(parts[2], "N=%d,r=%d,p=%d", &N, &r, &p); err != nil {
|
||||
s.PushBoolean(false)
|
||||
return 1
|
||||
}
|
||||
|
||||
salt, err := base64.RawStdEncoding.DecodeString(parts[3])
|
||||
if err != nil {
|
||||
s.PushBoolean(false)
|
||||
return 1
|
||||
}
|
||||
|
||||
expectedHash, err := base64.RawStdEncoding.DecodeString(parts[4])
|
||||
if err != nil {
|
||||
s.PushBoolean(false)
|
||||
return 1
|
||||
}
|
||||
|
||||
actualHash, err := scrypt.Key([]byte(password), salt, N, r, p, len(expectedHash))
|
||||
if err != nil {
|
||||
s.PushBoolean(false)
|
||||
return 1
|
||||
}
|
||||
|
||||
s.PushBoolean(hmac.Equal(actualHash, expectedHash))
|
||||
return 1
|
||||
}
|
||||
|
||||
func pbkdf2_hash(s *luajit.State) int {
|
||||
password := s.ToString(1)
|
||||
iterations := 100000
|
||||
keyLen := 32
|
||||
|
||||
if s.GetTop() >= 2 && !s.IsNil(2) {
|
||||
iterations = int(s.ToNumber(2))
|
||||
}
|
||||
if s.GetTop() >= 3 && !s.IsNil(3) {
|
||||
keyLen = int(s.ToNumber(3))
|
||||
}
|
||||
|
||||
salt := make([]byte, 16)
|
||||
if _, err := rand.Read(salt); err != nil {
|
||||
s.PushNil()
|
||||
s.PushString("failed to generate salt")
|
||||
return 2
|
||||
}
|
||||
|
||||
hash := pbkdf2.Key([]byte(password), salt, iterations, keyLen, sha256.New)
|
||||
encodedSalt := base64.RawStdEncoding.EncodeToString(salt)
|
||||
encodedHash := base64.RawStdEncoding.EncodeToString(hash)
|
||||
|
||||
result := fmt.Sprintf("$pbkdf2-sha256$i=%d$%s$%s", iterations, encodedSalt, encodedHash)
|
||||
s.PushString(result)
|
||||
return 1
|
||||
}
|
||||
|
||||
func pbkdf2_verify(s *luajit.State) int {
|
||||
password := s.ToString(1)
|
||||
hash := s.ToString(2)
|
||||
|
||||
parts := strings.Split(hash, "$")
|
||||
if len(parts) != 5 || parts[1] != "pbkdf2-sha256" {
|
||||
s.PushBoolean(false)
|
||||
return 1
|
||||
}
|
||||
|
||||
var iterations int
|
||||
if _, err := fmt.Sscanf(parts[2], "i=%d", &iterations); err != nil {
|
||||
s.PushBoolean(false)
|
||||
return 1
|
||||
}
|
||||
|
||||
salt, err := base64.RawStdEncoding.DecodeString(parts[3])
|
||||
if err != nil {
|
||||
s.PushBoolean(false)
|
||||
return 1
|
||||
}
|
||||
|
||||
expectedHash, err := base64.RawStdEncoding.DecodeString(parts[4])
|
||||
if err != nil {
|
||||
s.PushBoolean(false)
|
||||
return 1
|
||||
}
|
||||
|
||||
actualHash := pbkdf2.Key([]byte(password), salt, iterations, len(expectedHash), sha256.New)
|
||||
s.PushBoolean(hmac.Equal(actualHash, expectedHash))
|
||||
return 1
|
||||
}
|
||||
|
||||
func password_hash(s *luajit.State) int {
|
||||
password := s.ToString(1)
|
||||
algorithm := "argon2id" // default
|
||||
|
||||
if s.GetTop() >= 2 && !s.IsNil(2) {
|
||||
algorithm = s.ToString(2)
|
||||
}
|
||||
|
||||
switch algorithm {
|
||||
case "argon2id":
|
||||
s.PushString(password)
|
||||
return argon2_hash(s)
|
||||
case "bcrypt":
|
||||
s.PushString(password)
|
||||
if s.GetTop() >= 3 {
|
||||
s.PushNumber(s.ToNumber(3))
|
||||
}
|
||||
return bcrypt_hash(s)
|
||||
case "scrypt":
|
||||
s.PushString(password)
|
||||
return scrypt_hash(s)
|
||||
case "pbkdf2":
|
||||
s.PushString(password)
|
||||
return pbkdf2_hash(s)
|
||||
default:
|
||||
s.PushNil()
|
||||
s.PushString("unsupported algorithm: " + algorithm)
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
func password_verify(s *luajit.State) int {
|
||||
hash := s.ToString(2)
|
||||
|
||||
// Auto-detect algorithm from hash format
|
||||
if strings.HasPrefix(hash, "$argon2id$") {
|
||||
return argon2_verify(s)
|
||||
} else if strings.HasPrefix(hash, "$2a$") || strings.HasPrefix(hash, "$2b$") || strings.HasPrefix(hash, "$2y$") {
|
||||
return bcrypt_verify(s)
|
||||
} else if strings.HasPrefix(hash, "$scrypt$") {
|
||||
return scrypt_verify(s)
|
||||
} else if strings.HasPrefix(hash, "$pbkdf2-sha256$") {
|
||||
return pbkdf2_verify(s)
|
||||
}
|
||||
|
||||
s.PushBoolean(false)
|
||||
return 1
|
||||
}
|
||||
|
@ -365,4 +365,166 @@ function crypto.verify_integrity(check)
|
||||
return crypto.secure_compare(expected, check.hash)
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- PASSWORD HASHING
|
||||
-- ======================================================================
|
||||
|
||||
-- Generic password hashing (defaults to argon2id)
|
||||
function crypto.hash_password(password, algorithm, options)
|
||||
algorithm = algorithm or "argon2id"
|
||||
options = options or {}
|
||||
|
||||
local result, err
|
||||
|
||||
if algorithm == "argon2id" then
|
||||
local time = options.time or 1
|
||||
local memory = options.memory or 65536 -- 64MB in KB
|
||||
local threads = options.threads or 4
|
||||
local keylen = options.keylen or 32
|
||||
result, err = moonshark.argon2_hash(password, time, memory, threads, keylen)
|
||||
|
||||
elseif algorithm == "bcrypt" then
|
||||
local cost = options.cost or 12
|
||||
result, err = moonshark.bcrypt_hash(password, cost)
|
||||
|
||||
elseif algorithm == "scrypt" then
|
||||
local N = options.N or 32768
|
||||
local r = options.r or 8
|
||||
local p = options.p or 1
|
||||
local keylen = options.keylen or 32
|
||||
result, err = moonshark.scrypt_hash(password, N, r, p, keylen)
|
||||
|
||||
elseif algorithm == "pbkdf2" then
|
||||
local iterations = options.iterations or 100000
|
||||
local keylen = options.keylen or 32
|
||||
result, err = moonshark.pbkdf2_hash(password, iterations, keylen)
|
||||
|
||||
else
|
||||
error("unsupported algorithm: " .. algorithm)
|
||||
end
|
||||
|
||||
if not result then
|
||||
error(err)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- Generic password verification (auto-detects algorithm)
|
||||
function crypto.verify_password(password, hash)
|
||||
return moonshark.password_verify(password, hash)
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- ALGORITHM-SPECIFIC FUNCTIONS
|
||||
-- ======================================================================
|
||||
|
||||
-- Argon2id hashing
|
||||
function crypto.argon2_hash(password, options)
|
||||
options = options or {}
|
||||
local time = options.time or 1
|
||||
local memory = options.memory or 65536
|
||||
local threads = options.threads or 4
|
||||
local keylen = options.keylen or 32
|
||||
|
||||
local result, err = moonshark.argon2_hash(password, time, memory, threads, keylen)
|
||||
if not result then error(err) end
|
||||
return result
|
||||
end
|
||||
|
||||
function crypto.argon2_verify(password, hash)
|
||||
return moonshark.argon2_verify(password, hash)
|
||||
end
|
||||
|
||||
-- bcrypt hashing
|
||||
function crypto.bcrypt_hash(password, cost)
|
||||
cost = cost or 12
|
||||
local result, err = moonshark.bcrypt_hash(password, cost)
|
||||
if not result then error(err) end
|
||||
return result
|
||||
end
|
||||
|
||||
function crypto.bcrypt_verify(password, hash)
|
||||
return moonshark.bcrypt_verify(password, hash)
|
||||
end
|
||||
|
||||
-- scrypt hashing
|
||||
function crypto.scrypt_hash(password, options)
|
||||
options = options or {}
|
||||
local N = options.N or 32768
|
||||
local r = options.r or 8
|
||||
local p = options.p or 1
|
||||
local keylen = options.keylen or 32
|
||||
|
||||
local result, err = moonshark.scrypt_hash(password, N, r, p, keylen)
|
||||
if not result then error(err) end
|
||||
return result
|
||||
end
|
||||
|
||||
function crypto.scrypt_verify(password, hash)
|
||||
return moonshark.scrypt_verify(password, hash)
|
||||
end
|
||||
|
||||
-- PBKDF2 hashing
|
||||
function crypto.pbkdf2_hash(password, iterations, keylen)
|
||||
iterations = iterations or 100000
|
||||
keylen = keylen or 32
|
||||
|
||||
local result, err = moonshark.pbkdf2_hash(password, iterations, keylen)
|
||||
if not result then error(err) end
|
||||
return result
|
||||
end
|
||||
|
||||
function crypto.pbkdf2_verify(password, hash)
|
||||
return moonshark.pbkdf2_verify(password, hash)
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- PASSWORD CONFIG PRESETS
|
||||
-- ======================================================================
|
||||
|
||||
function crypto.hash_password_fast(password, algorithm)
|
||||
algorithm = algorithm or "argon2id"
|
||||
|
||||
local options = {
|
||||
argon2id = { time = 1, memory = 8192, threads = 1 },
|
||||
bcrypt = { cost = 10 },
|
||||
scrypt = { N = 16384, r = 8, p = 1 },
|
||||
pbkdf2 = { iterations = 50000 }
|
||||
}
|
||||
|
||||
return crypto.hash_password(password, algorithm, options[algorithm])
|
||||
end
|
||||
|
||||
function crypto.hash_password_strong(password, algorithm)
|
||||
algorithm = algorithm or "argon2id"
|
||||
|
||||
local options = {
|
||||
argon2id = { time = 3, memory = 131072, threads = 4 },
|
||||
bcrypt = { cost = 14 },
|
||||
scrypt = { N = 65536, r = 8, p = 2 },
|
||||
pbkdf2 = { iterations = 200000 }
|
||||
}
|
||||
|
||||
return crypto.hash_password(password, algorithm, options[algorithm])
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- UTILITY FUNCTIONS
|
||||
-- ======================================================================
|
||||
|
||||
-- Detect algorithm from hash
|
||||
function crypto.detect_algorithm(hash)
|
||||
if hash:match("^%$argon2id%$") then
|
||||
return "argon2id"
|
||||
elseif hash:match("^%$2[aby]%$") then
|
||||
return "bcrypt"
|
||||
elseif hash:match("^%$scrypt%$") then
|
||||
return "scrypt"
|
||||
elseif hash:match("^%$pbkdf2%-sha256%$") then
|
||||
return "pbkdf2"
|
||||
else
|
||||
return "unknown"
|
||||
end
|
||||
end
|
||||
|
||||
return crypto
|
||||
|
175
tests/crypto.lua
175
tests/crypto.lua
@ -301,6 +301,181 @@ test("Error Handling", function()
|
||||
assert_equal(crypto.is_uuid("12345"), false)
|
||||
end)
|
||||
|
||||
-- ======================================================================
|
||||
-- PASSWORD TESTS
|
||||
-- ======================================================================
|
||||
|
||||
test("Password Hash and Verification", function()
|
||||
local password = "hubba-ba-loo117!@#"
|
||||
local hash = crypto.hash_password(password)
|
||||
local hash_fast = crypto.hash_password_fast(password)
|
||||
local hash_strong = crypto.hash_password_strong(password)
|
||||
|
||||
assert(crypto.verify_password(password, hash))
|
||||
assert(crypto.verify_password(password, hash_fast))
|
||||
assert(crypto.verify_password(password, hash_strong))
|
||||
|
||||
assert(not crypto.verify_password("failure", hash))
|
||||
assert(not crypto.verify_password("failure", hash_fast))
|
||||
assert(not crypto.verify_password("failure", hash_strong))
|
||||
end)
|
||||
|
||||
test("Algorithm-Specific Password Hashing", function()
|
||||
local password = "test123!@#"
|
||||
|
||||
-- Test each algorithm individually
|
||||
local argon2_hash = crypto.hash_password(password, "argon2id")
|
||||
local bcrypt_hash = crypto.hash_password(password, "bcrypt")
|
||||
local scrypt_hash = crypto.hash_password(password, "scrypt")
|
||||
local pbkdf2_hash = crypto.hash_password(password, "pbkdf2")
|
||||
|
||||
assert(crypto.verify_password(password, argon2_hash))
|
||||
assert(crypto.verify_password(password, bcrypt_hash))
|
||||
assert(crypto.verify_password(password, scrypt_hash))
|
||||
assert(crypto.verify_password(password, pbkdf2_hash))
|
||||
|
||||
-- Verify wrong passwords fail
|
||||
assert(not crypto.verify_password("wrong", argon2_hash))
|
||||
assert(not crypto.verify_password("wrong", bcrypt_hash))
|
||||
assert(not crypto.verify_password("wrong", scrypt_hash))
|
||||
assert(not crypto.verify_password("wrong", pbkdf2_hash))
|
||||
end)
|
||||
|
||||
test("Algorithm Detection", function()
|
||||
local password = "detectme123"
|
||||
|
||||
local argon2_hash = crypto.hash_password(password, "argon2id")
|
||||
local bcrypt_hash = crypto.hash_password(password, "bcrypt")
|
||||
local scrypt_hash = crypto.hash_password(password, "scrypt")
|
||||
local pbkdf2_hash = crypto.hash_password(password, "pbkdf2")
|
||||
|
||||
assert(crypto.detect_algorithm(argon2_hash) == "argon2id")
|
||||
assert(crypto.detect_algorithm(bcrypt_hash) == "bcrypt")
|
||||
assert(crypto.detect_algorithm(scrypt_hash) == "scrypt")
|
||||
assert(crypto.detect_algorithm(pbkdf2_hash) == "pbkdf2")
|
||||
assert(crypto.detect_algorithm("invalid$format") == "unknown")
|
||||
end)
|
||||
|
||||
test("Custom Algorithm Options", function()
|
||||
local password = "custom123"
|
||||
|
||||
-- Test custom argon2id options
|
||||
local custom_argon2 = crypto.hash_password(password, "argon2id", {
|
||||
time = 2,
|
||||
memory = 32768,
|
||||
threads = 2
|
||||
})
|
||||
assert(crypto.verify_password(password, custom_argon2))
|
||||
|
||||
-- Test custom bcrypt cost
|
||||
local custom_bcrypt = crypto.hash_password(password, "bcrypt", {cost = 10})
|
||||
assert(crypto.verify_password(password, custom_bcrypt))
|
||||
|
||||
-- Test custom scrypt parameters
|
||||
local custom_scrypt = crypto.hash_password(password, "scrypt", {
|
||||
N = 16384,
|
||||
r = 4,
|
||||
p = 2
|
||||
})
|
||||
assert(crypto.verify_password(password, custom_scrypt))
|
||||
|
||||
-- Test custom pbkdf2 iterations
|
||||
local custom_pbkdf2 = crypto.hash_password(password, "pbkdf2", {
|
||||
iterations = 50000
|
||||
})
|
||||
assert(crypto.verify_password(password, custom_pbkdf2))
|
||||
end)
|
||||
|
||||
test("Direct Algorithm Functions", function()
|
||||
local password = "direct123"
|
||||
|
||||
-- Test direct algorithm calls
|
||||
local argon2_direct = crypto.argon2_hash(password)
|
||||
local bcrypt_direct = crypto.bcrypt_hash(password)
|
||||
local scrypt_direct = crypto.scrypt_hash(password)
|
||||
local pbkdf2_direct = crypto.pbkdf2_hash(password)
|
||||
|
||||
assert(crypto.argon2_verify(password, argon2_direct))
|
||||
assert(crypto.bcrypt_verify(password, bcrypt_direct))
|
||||
assert(crypto.scrypt_verify(password, scrypt_direct))
|
||||
assert(crypto.pbkdf2_verify(password, pbkdf2_direct))
|
||||
|
||||
-- Test with custom options
|
||||
local argon2_custom = crypto.argon2_hash(password, {time = 1, memory = 16384})
|
||||
local scrypt_custom = crypto.scrypt_hash(password, {N = 8192})
|
||||
|
||||
assert(crypto.argon2_verify(password, argon2_custom))
|
||||
assert(crypto.scrypt_verify(password, scrypt_custom))
|
||||
end)
|
||||
|
||||
test("Security Level Presets", function()
|
||||
local password = "preset123"
|
||||
local algorithms = {"argon2id", "bcrypt", "scrypt", "pbkdf2"}
|
||||
|
||||
for _, algo in ipairs(algorithms) do
|
||||
local fast_hash = crypto.hash_password_fast(password, algo)
|
||||
local strong_hash = crypto.hash_password_strong(password, algo)
|
||||
|
||||
assert(crypto.verify_password(password, fast_hash))
|
||||
assert(crypto.verify_password(password, strong_hash))
|
||||
|
||||
-- Verify algorithm detection
|
||||
assert(crypto.detect_algorithm(fast_hash) == algo)
|
||||
assert(crypto.detect_algorithm(strong_hash) == algo)
|
||||
end
|
||||
end)
|
||||
|
||||
test("Edge Cases and Error Handling", function()
|
||||
-- Test empty password
|
||||
local empty_hash = crypto.hash_password("")
|
||||
assert(crypto.verify_password("", empty_hash))
|
||||
assert(not crypto.verify_password("not-empty", empty_hash))
|
||||
|
||||
-- Test long password
|
||||
local long_password = string.rep("a", 1000)
|
||||
local long_hash = crypto.hash_password(long_password)
|
||||
assert(crypto.verify_password(long_password, long_hash))
|
||||
|
||||
-- Test unicode password
|
||||
local unicode_password = "🔐password123🔑"
|
||||
local unicode_hash = crypto.hash_password(unicode_password)
|
||||
assert(crypto.verify_password(unicode_password, unicode_hash))
|
||||
|
||||
-- Test invalid hash formats
|
||||
assert(not crypto.verify_password("test", "invalid-hash"))
|
||||
assert(not crypto.verify_password("test", "$invalid$format$"))
|
||||
|
||||
-- Test unsupported algorithm error
|
||||
local success, err = pcall(crypto.hash_password, "test", "invalid-algo")
|
||||
assert(not success)
|
||||
assert(string.find(err, "unsupported algorithm"))
|
||||
end)
|
||||
|
||||
test("Cross-Algorithm Verification", function()
|
||||
local password = "cross123"
|
||||
|
||||
-- Create hashes with different algorithms
|
||||
local hashes = {
|
||||
crypto.hash_password(password, "argon2id"),
|
||||
crypto.hash_password(password, "bcrypt"),
|
||||
crypto.hash_password(password, "scrypt"),
|
||||
crypto.hash_password(password, "pbkdf2")
|
||||
}
|
||||
|
||||
-- Each hash should only verify with correct password
|
||||
for _, hash in ipairs(hashes) do
|
||||
assert(crypto.verify_password(password, hash))
|
||||
assert(not crypto.verify_password("wrong", hash))
|
||||
end
|
||||
|
||||
-- Hashes should be different from each other
|
||||
for i = 1, #hashes do
|
||||
for j = i + 1, #hashes do
|
||||
assert(hashes[i] ~= hashes[j])
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- ======================================================================
|
||||
-- PERFORMANCE TESTS
|
||||
-- ======================================================================
|
||||
|
Loading…
x
Reference in New Issue
Block a user