508 lines
16 KiB
Lua
508 lines
16 KiB
Lua
require("tests")
|
|
local crypto = require("crypto")
|
|
|
|
-- Test data
|
|
local test_data = "Hello, World!"
|
|
local test_key = "secret-key-123"
|
|
|
|
-- ======================================================================
|
|
-- ENCODING/DECODING TESTS
|
|
-- ======================================================================
|
|
|
|
test("Base64 Encoding/Decoding", function()
|
|
local encoded = crypto.base64_encode(test_data)
|
|
assert_equal(type(encoded), "string")
|
|
assert(#encoded > 0, "encoded string should not be empty")
|
|
|
|
local decoded = crypto.base64_decode(encoded)
|
|
assert_equal(decoded, test_data)
|
|
end)
|
|
|
|
test("Base64 URL Encoding/Decoding", function()
|
|
local encoded = crypto.base64_url_encode(test_data)
|
|
assert_equal(type(encoded), "string")
|
|
|
|
local decoded = crypto.base64_url_decode(encoded)
|
|
assert_equal(decoded, test_data)
|
|
end)
|
|
|
|
test("Hex Encoding/Decoding", function()
|
|
local encoded = crypto.hex_encode(test_data)
|
|
assert_equal(type(encoded), "string")
|
|
assert(encoded:match("^[0-9a-f]+$"), "hex should only contain hex characters")
|
|
|
|
local decoded = crypto.hex_decode(encoded)
|
|
assert_equal(decoded, test_data)
|
|
end)
|
|
|
|
test("Encoding Chain", function()
|
|
local chain_encoded = crypto.encode_chain(test_data, {"hex", "base64"})
|
|
local chain_decoded = crypto.decode_chain(chain_encoded, {"hex", "base64"})
|
|
assert_equal(chain_decoded, test_data)
|
|
end)
|
|
|
|
-- ======================================================================
|
|
-- HASHING TESTS
|
|
-- ======================================================================
|
|
|
|
test("MD5 Hash", function()
|
|
local hash = crypto.md5(test_data)
|
|
assert_equal(type(hash), "string")
|
|
assert_equal(#hash, 32) -- MD5 is 32 hex characters
|
|
assert(hash:match("^[0-9a-f]+$"), "hash should be hex")
|
|
|
|
-- Same input should produce same hash
|
|
assert_equal(crypto.md5(test_data), hash)
|
|
end)
|
|
|
|
test("SHA1 Hash", function()
|
|
local hash = crypto.sha1(test_data)
|
|
assert_equal(type(hash), "string")
|
|
assert_equal(#hash, 40) -- SHA1 is 40 hex characters
|
|
assert(hash:match("^[0-9a-f]+$"), "hash should be hex")
|
|
end)
|
|
|
|
test("SHA256 Hash", function()
|
|
local hash = crypto.sha256(test_data)
|
|
assert_equal(type(hash), "string")
|
|
assert_equal(#hash, 64) -- SHA256 is 64 hex characters
|
|
assert(hash:match("^[0-9a-f]+$"), "hash should be hex")
|
|
end)
|
|
|
|
test("SHA512 Hash", function()
|
|
local hash = crypto.sha512(test_data)
|
|
assert_equal(type(hash), "string")
|
|
assert_equal(#hash, 128) -- SHA512 is 128 hex characters
|
|
assert(hash:match("^[0-9a-f]+$"), "hash should be hex")
|
|
end)
|
|
|
|
test("Hash Multiple Inputs", function()
|
|
local hash1 = crypto.hash_multiple({"hello", "world"})
|
|
local hash2 = crypto.hash_multiple({"hello", "world"})
|
|
local hash3 = crypto.hash_multiple({"world", "hello"})
|
|
|
|
assert_equal(hash1, hash2)
|
|
assert(hash1 ~= hash3, "different order should produce different hash")
|
|
end)
|
|
|
|
-- ======================================================================
|
|
-- HMAC TESTS
|
|
-- ======================================================================
|
|
|
|
test("HMAC SHA1", function()
|
|
local hmac = crypto.hmac_sha1(test_data, test_key)
|
|
assert_equal(type(hmac), "string")
|
|
assert_equal(#hmac, 40) -- SHA1 HMAC is 40 hex characters
|
|
assert(hmac:match("^[0-9a-f]+$"), "hmac should be hex")
|
|
end)
|
|
|
|
test("HMAC SHA256", function()
|
|
local hmac = crypto.hmac_sha256(test_data, test_key)
|
|
assert_equal(type(hmac), "string")
|
|
assert_equal(#hmac, 64) -- SHA256 HMAC is 64 hex characters
|
|
assert(hmac:match("^[0-9a-f]+$"), "hmac should be hex")
|
|
end)
|
|
|
|
test("MAC Functions", function()
|
|
local mac = crypto.mac(test_data, test_key)
|
|
assert_equal(type(mac), "string")
|
|
assert(#mac > 0, "mac should not be empty")
|
|
|
|
local valid = crypto.verify_mac(test_data, test_key, mac)
|
|
assert_equal(valid, true)
|
|
|
|
local invalid = crypto.verify_mac("different data", test_key, mac)
|
|
assert_equal(invalid, false)
|
|
end)
|
|
|
|
-- ======================================================================
|
|
-- UUID TESTS
|
|
-- ======================================================================
|
|
|
|
test("UUID Generation", function()
|
|
local uuid1 = crypto.uuid()
|
|
local uuid2 = crypto.uuid_v4()
|
|
|
|
assert_equal(type(uuid1), "string")
|
|
assert_equal(type(uuid2), "string")
|
|
assert_equal(#uuid1, 36) -- Standard UUID length
|
|
assert_equal(#uuid2, 36)
|
|
assert(uuid1 ~= uuid2, "UUIDs should be unique")
|
|
|
|
assert_equal(crypto.is_uuid(uuid1), true)
|
|
assert_equal(crypto.is_uuid(uuid2), true)
|
|
assert_equal(crypto.is_uuid("not-a-uuid"), false)
|
|
end)
|
|
|
|
-- ======================================================================
|
|
-- RANDOM GENERATION TESTS
|
|
-- ======================================================================
|
|
|
|
test("Random Bytes", function()
|
|
local bytes1 = crypto.random_bytes(16)
|
|
local bytes2 = crypto.random_bytes(16)
|
|
|
|
assert_equal(type(bytes1), "string")
|
|
assert_equal(#bytes1, 16)
|
|
assert(bytes1 ~= bytes2, "random bytes should be different")
|
|
end)
|
|
|
|
test("Random Hex", function()
|
|
local hex1 = crypto.random_hex(8)
|
|
local hex2 = crypto.random_hex(8)
|
|
|
|
assert_equal(type(hex1), "string")
|
|
assert_equal(#hex1, 16) -- 8 bytes = 16 hex characters
|
|
assert(hex1:match("^[0-9a-f]+$"), "should be hex")
|
|
assert(hex1 ~= hex2, "random hex should be different")
|
|
end)
|
|
|
|
test("Random String", function()
|
|
local str1 = crypto.random_string(10)
|
|
local str2 = crypto.random_string(10)
|
|
|
|
assert_equal(type(str1), "string")
|
|
assert_equal(#str1, 10)
|
|
assert(str1 ~= str2, "random strings should be different")
|
|
|
|
local custom = crypto.random_string(5, "abc")
|
|
assert_equal(#custom, 5)
|
|
assert(custom:match("^[abc]+$"), "should only contain specified characters")
|
|
end)
|
|
|
|
test("Random Alphanumeric", function()
|
|
local str = crypto.random_alphanumeric(20)
|
|
assert_equal(#str, 20)
|
|
assert(str:match("^[a-zA-Z0-9]+$"), "should be alphanumeric")
|
|
end)
|
|
|
|
test("Random Password", function()
|
|
local pass1 = crypto.random_password(12)
|
|
local pass2 = crypto.random_password(12, true) -- with symbols
|
|
|
|
assert_equal(#pass1, 12)
|
|
assert_equal(#pass2, 12)
|
|
assert(pass1 ~= pass2, "passwords should be different")
|
|
end)
|
|
|
|
test("Token Generation", function()
|
|
local token1 = crypto.token(32)
|
|
local token2 = crypto.token(32)
|
|
|
|
assert_equal(#token1, 64) -- 32 bytes = 64 hex characters
|
|
assert(token1:match("^[0-9a-f]+$"), "token should be hex")
|
|
assert(token1 ~= token2, "tokens should be unique")
|
|
end)
|
|
|
|
test("Nonce Generation", function()
|
|
local nonce1 = crypto.nonce()
|
|
local nonce2 = crypto.nonce(32)
|
|
|
|
assert_equal(#nonce1, 32) -- default 16 bytes = 32 hex
|
|
assert_equal(#nonce2, 64) -- 32 bytes = 64 hex
|
|
assert(nonce1 ~= nonce2, "nonces should be unique")
|
|
end)
|
|
|
|
-- ======================================================================
|
|
-- UTILITY TESTS
|
|
-- ======================================================================
|
|
|
|
test("Secure Compare", function()
|
|
local str1 = "hello"
|
|
local str2 = "hello"
|
|
local str3 = "world"
|
|
|
|
assert_equal(crypto.secure_compare(str1, str2), true)
|
|
assert_equal(crypto.secure_compare(str1, str3), false)
|
|
end)
|
|
|
|
test("Checksum Functions", function()
|
|
local checksum = crypto.checksum(test_data)
|
|
assert_equal(type(checksum), "string")
|
|
|
|
local valid = crypto.verify_checksum(test_data, checksum)
|
|
assert_equal(valid, true)
|
|
|
|
local invalid = crypto.verify_checksum("different", checksum)
|
|
assert_equal(invalid, false)
|
|
end)
|
|
|
|
test("XOR Encryption", function()
|
|
local key = "mykey"
|
|
local encrypted = crypto.xor_encrypt(test_data, key)
|
|
local decrypted = crypto.xor_decrypt(encrypted, key)
|
|
|
|
assert_equal(decrypted, test_data)
|
|
assert(encrypted ~= test_data, "encrypted should be different")
|
|
end)
|
|
|
|
test("Hash Chain", function()
|
|
local chain1 = crypto.hash_chain(test_data, 100)
|
|
local chain2 = crypto.hash_chain(test_data, 100)
|
|
local chain3 = crypto.hash_chain(test_data, 101)
|
|
|
|
assert_equal(chain1, chain2)
|
|
assert(chain1 ~= chain3, "different iterations should produce different results")
|
|
end)
|
|
|
|
test("Key Derivation", function()
|
|
local derived1, salt1 = crypto.derive_key("password", "salt", 1000)
|
|
local derived2, salt2 = crypto.derive_key("password", salt1, 1000)
|
|
|
|
assert_equal(derived1, derived2)
|
|
assert_equal(salt1, salt2)
|
|
assert_equal(type(derived1), "string")
|
|
assert(#derived1 > 0, "derived key should not be empty")
|
|
end)
|
|
|
|
test("Fingerprint", function()
|
|
local data = {name = "test", value = 42}
|
|
local fp1 = crypto.fingerprint(data)
|
|
local fp2 = crypto.fingerprint(data)
|
|
|
|
assert_equal(fp1, fp2)
|
|
assert_equal(type(fp1), "string")
|
|
assert(#fp1 > 0, "fingerprint should not be empty")
|
|
end)
|
|
|
|
test("Integrity Check", function()
|
|
local check = crypto.integrity_check(test_data)
|
|
|
|
assert_equal(check.data, test_data)
|
|
assert_equal(type(check.hash), "string")
|
|
assert_equal(type(check.timestamp), "number")
|
|
assert_equal(type(check.uuid), "string")
|
|
|
|
local valid = crypto.verify_integrity(check)
|
|
assert_equal(valid, true)
|
|
|
|
-- Tamper with data
|
|
check.data = "tampered"
|
|
local invalid = crypto.verify_integrity(check)
|
|
assert_equal(invalid, false)
|
|
end)
|
|
|
|
-- ======================================================================
|
|
-- ERROR HANDLING TESTS
|
|
-- ======================================================================
|
|
|
|
test("Error Handling", function()
|
|
-- Invalid base64
|
|
local success, err = pcall(crypto.base64_decode, "invalid===base64")
|
|
assert_equal(success, false)
|
|
|
|
-- Invalid hex
|
|
local success2, err2 = pcall(crypto.hex_decode, "invalid_hex")
|
|
assert_equal(success2, false)
|
|
|
|
-- Invalid UUID validation (returns boolean, doesn't throw)
|
|
assert_equal(crypto.is_uuid("not-a-uuid"), false)
|
|
assert_equal(crypto.is_uuid(""), false)
|
|
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
|
|
-- ======================================================================
|
|
|
|
test("Performance Test", function()
|
|
local large_data = string.rep("test data for performance ", 1000)
|
|
|
|
local start = os.clock()
|
|
local hash = crypto.sha256(large_data)
|
|
local hash_time = os.clock() - start
|
|
|
|
start = os.clock()
|
|
local encoded = crypto.base64_encode(large_data)
|
|
local encode_time = os.clock() - start
|
|
|
|
start = os.clock()
|
|
local decoded = crypto.base64_decode(encoded)
|
|
local decode_time = os.clock() - start
|
|
|
|
print(string.format(" SHA256 of %d bytes: %.3fs", #large_data, hash_time))
|
|
print(string.format(" Base64 encode: %.3fs", encode_time))
|
|
print(string.format(" Base64 decode: %.3fs", decode_time))
|
|
|
|
assert_equal(decoded, large_data)
|
|
assert_equal(type(hash), "string")
|
|
end)
|
|
|
|
summary()
|
|
test_exit()
|