Moonshark/tests/crypto.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()