扩展库 / argon2
argon2
加密安全Argon2 密码哈希(RFC 9106)— 2015 年密码哈希竞赛(PHC)冠军算法,具备内存硬特性,有效抵抗 GPU/ASIC 暴力破解。C 层实现 Blake2b + Argon2 完整核心,xlang 层提供安全、易用的高层 API,提供当前包公开的密码哈希与 PHC 验证能力。
当前合同与验证
2026-09-26 原生 ABI 与错误边界更新。公开 C 声明为 include/argon2_native.h,
专项门禁为 tools/check_extlib_argon2_bloom.ps1,执行记录绑定源码和宿主哈希。
- 无效参数使用 ValueError,分配失败使用 MemoryError,安全随机源失败使用 IOError。
- 输出 4..256 字节、盐 8..64 字节、内存最多 2097152 KiB;verify 同样检查资源上限。 该上限不是并发配额或时间预算,不承诺取消与中途终止。
- RFC 9106 参数组:64 MiB / t=3 / p=4,或 2 GiB / t=1 / p=4; 默认使用前者。标准原文。
- parallelism 是 lane 数,当前参考实现顺序处理;输入经 UTF-8 C 字符串 ABI,不支持嵌入 NUL。
- verify 当前不接收 secret/ad;带这些字段的 hash 不应宣传为已打通高层验证, 也不要用普通字符串比较充当认证验证。
argon2_basic.xl与argon2_c.c提供同结果范例, 固定盐与低成本仅用于可重复演示。
典型场景
快速开始
import argon2
func main(...) as int
// 注册:一行哈希(自动生成 CSPRNG 随机盐)
string stored = argon2.hash("user_password")
// 登录:一行验证
if argon2.verify("user_password", stored)
println("登录成功")
end
return 0
end核心特性
- 内存硬 — 每次哈希消耗可配置内存(默认 64 MiB),提高专用硬件并行猜测的内存成本
- 自动随机盐 — 使用操作系统 CSPRNG(通过 XRT 的系统安全随机源),无需手动管理
- PHC 标准编码 — 算法、版本、参数、盐、哈希全部内嵌于一个字符串,直接存入数据库
- 三种变体 — Argon2d(抗 GPU)/ Argon2i(抗侧信道)/ Argon2id(混合,RFC 推荐默认)
- secret + ad — 服务器端密钥哈希(pepper)与附加数据绑定用户上下文
- 恒定时间比较 — verify 内部 XOR 累积比较,防时序攻击
- 安全内存擦除 — 哈希中间状态与盐用后即清,不残留在内存中
- 参数升级检测 —
checkNeedsRehash判断旧哈希是否需要用新参数重新计算
包信息
| 包名 | argon2 |
|---|---|
| 版本 | 2.0.0 |
| 实现方式 | C 混编(Blake2b + Argon2 核心 C,API 层 xlang) |
| 依赖 | 无 |
| ABI 版本 | 1 |
| 导出符号 | hash, hashRaw, verify, checkNeedsRehash, extract*, generateSalt, recommended* |
| 源文件 | extlibs/argon2/src/argon2.xl |
计算密码哈希,返回 PHC 编码字符串。salt 为空时自动生成 saltLen 字节的 CSPRNG 随机盐。
$argon2id$v=19$m=65536,t=3,p=4$<salt_b64>$<hash_b64>,包含算法、成本、盐和哈希;secret/ad 不在其中。参数与 hash 完全相同,返回原始哈希的 hex 字符串(不含元数据)。适用于自行管理存储格式的场景。
验证密码是否匹配 PHC 编码字符串。自动解析编码中的算法变体、全部参数、盐和哈希值,重新计算后以恒定时间比较。
判断已有哈希的参数是否与期望值不同。参数不完全匹配即返回 true。典型用法:用户登录成功后检查,若需升级则重新 hash 并更新存储。
从 PHC 编码中提取各项参数,无需手动解码 base64。适用于安全审计与批量迁移场景。解析失败返回 -1。
生成 CSPRNG 随机盐,返回 hex 字符串(len 字节 → len*2 个 hex 字符)。范围 8-64 字节。
返回当前推荐的安全参数,作为 checkNeedsRehash 的默认对比基准。
变体选择常量,传入 hash 的 type 参数。
RFC 9106 预定义参数集,直接传入 hash 对应参数即可使用。
教学范例
以下范例逐步展示全部 API,从最简用法到综合实战。完整源文件:examples/extlibs/argon2.xl
1. 最简用法:一行哈希,一行验证
import argon2
// hash() 自动生成 CSPRNG 随机盐,返回 PHC 标准编码字符串
string encoded = argon2.hash("my_secure_password")
println("哈希: ", encoded)
// 输出: $argon2id$v=19$m=65536,t=3,p=4$<salt_b64>$<hash_b64>
// verify() 自动解析编码中的算法、参数、盐,重新计算并比较
println("正确密码: ", argon2.verify("my_secure_password", encoded)) // true
println("错误密码: ", argon2.verify("wrong_password", encoded)) // false
// 每次 hash() 调用产生不同盐 → 不同编码,但都能验证通过
string encoded2 = argon2.hash("my_secure_password")
println("两次哈希不同: ", encoded != encoded2) // true2. 手动盐(确定性输出)
// 传入固定盐 → 相同输入产生相同输出(用于测试/互操作)
string salt = "my_fixed_salt_16" // 至少 8 字节
string h1 = argon2.hash("password", salt = salt)
string h2 = argon2.hash("password", salt = salt)
println("确定性: ", h1 == h2) // true
// generateSalt() 生成随机盐的 hex 字符串
string randomSalt = argon2.generateSalt(16)
println("随机盐: ", randomSalt) // 32 个 hex 字符3. 参数调节
// timeCost: 迭代次数(越大越慢,抗暴力破解)
// memoryCost: 内存消耗 KiB(越大越抗 GPU/ASIC)
// parallelism: 并行度(影响哈希值,不只是性能)
string custom = argon2.hash("pw",
timeCost = 5, // 5 次迭代
memoryCost = 131072, // 128 MiB
parallelism = 2, // 2 路并行
hashLen = 64, // 64 字节输出
saltLen = 32 // 32 字节盐
)
// 验证时不需要传参数 — 全部从 PHC 编码中自动解析
println("验证: ", argon2.verify("pw", custom)) // true4. 三种变体
// Argon2d: 数据依赖寻址
// Argon2i: 数据无关寻址
// Argon2id: 混合(前半 i + 后半 d),RFC 推荐,默认值
string hd = argon2.hash("test", salt = salt, type = argon2.ARGON2D)
string hi = argon2.hash("test", salt = salt, type = argon2.ARGON2I)
string hid = argon2.hash("test", salt = salt, type = argon2.ARGON2ID)
// 所有变体都能正确验证(verify 自动识别类型)
println("验证 d: ", argon2.verify("test", hd)) // true
println("验证 i: ", argon2.verify("test", hi)) // true
println("验证 id: ", argon2.verify("test", hid)) // true5. secret 密钥哈希(pepper)
// secret 不写入 PHC 编码
// 此处仅演示附加输入,不是当前 verify 可完成的认证流程
string serverKey = "my_server_secret_key_2024"
string withKey = argon2.hash("user_password", secret = serverKey)
// 注意:verify() 不含 secret 参数
// 当前验证 API 缺少 secret/ad 参数,不能用普通字符串比较自行补认证逻辑6. ad 附加数据(绑定上下文)
// ad 将哈希绑定到特定上下文(如用户ID、租户ID)
// ad 不写入 PHC;当前 verify 不能验证此结果
string withAd = argon2.hash("password", ad = "tenant=acme;user=42")7. hashRaw:原始 hex 输出
// 不生成 PHC 编码,直接返回 hex 字符串
string raw = argon2.hashRaw("password", salt = salt, hashLen = 32)
println("Raw hex: ", raw) // 64 个 hex 字符
println("长度: ", raw.length) // 64(32字节 × 2)8. checkNeedsRehash:参数升级检测
// 场景:系统升级后提高了安全参数
string oldHash = argon2.hash("pw", salt = salt,
timeCost = 1, memoryCost = 4096, parallelism = 1)
// 对比当前推荐参数
bool needsUpgrade = argon2.checkNeedsRehash(oldHash,
timeCost = 3, memoryCost = 65536, parallelism = 4)
println("需要升级: ", needsUpgrade) // true
// 用新参数重新哈希后,不再需要升级
string newHash = argon2.hash("pw", timeCost = 3, memoryCost = 65536, parallelism = 4)
println("升级后: ", argon2.checkNeedsRehash(newHash,
timeCost = 3, memoryCost = 65536, parallelism = 4)) // false9. 参数提取(审计/迁移)
string sample = argon2.hash("x", salt = salt,
timeCost = 5, memoryCost = 1024, parallelism = 2)
println("类型: ", argon2.extractType(sample)) // 2 (ARGON2ID)
println("迭代: ", argon2.extractTimeCost(sample)) // 5
println("内存: ", argon2.extractMemoryCost(sample)) // 1024
println("并行: ", argon2.extractParallelism(sample)) // 210. 推荐参数与 Profiles
println("推荐迭代: ", argon2.recommendedTime()) // 3
println("推荐内存: ", argon2.recommendedMemory()) // 65536 KiB
println("推荐并行: ", argon2.recommendedParallelism()) // 4
// 使用 RFC 9106 低内存 Profile
string profiled = argon2.hash("pw",
timeCost = argon2.PROFILE_LOW_MEMORY_TIME,
memoryCost = argon2.PROFILE_LOW_MEMORY_MEMORY,
parallelism = argon2.PROFILE_LOW_MEMORY_PARALLELISM)11. 综合实战:用户注册/登录流程
// ── 注册 ──
string username = "alice"
string password = "S3cur3!Pass#2024"
// 一行完成:自动盐 + 推荐参数 + PHC 编码
string stored = argon2.hash(password)
// ── 登录 ──
string inputPassword = "S3cur3!Pass#2024"
assert argon2.verify(inputPassword, stored), "registration/login must accept the correct password"
assert not argon2.verify("hacker_guess", stored), "registration/login must reject the wrong password"
if argon2.verify(inputPassword, stored) then
println("登录成功!")
// 检查是否需要升级参数(安全策略更新后)
if argon2.checkNeedsRehash(stored) then
println("参数已过期,重新哈希...")
stored = argon2.hash(inputPassword)
// UPDATE users SET password_hash = stored WHERE ...
end
else
println("密码错误!")
end运行范例
cd release xl.exe run examples\extlibs\argon2.xl
测试覆盖
测试文件:test/cases/extlib_argon2.xl,覆盖全部公开 API:
- 自动盐:每次 hash 产生不同编码,均可 verify 通过
- 手动盐确定性:相同输入产生相同输出
- secret 密钥哈希改变结果
- ad 附加数据改变结果
- parallelism 不同值产生不同哈希,均可验证
- 三变体(d/i/id)互不相同,均可验证
- hashRaw hex 长度与确定性
- checkNeedsRehash 弱参数检测与强参数通过
- 参数提取:type/timeCost/memoryCost/parallelism
- generateSalt 随机性与长度
- 推荐参数返回值
- Profile 常量值
- 不同 hashLen 输出长度
测试片段(完整边界以仓库套件为准)
import argon2
func main(...) as int
// ═══ 1. 自动盐 ═══
string encoded = argon2.hash("password123", timeCost = 2, memoryCost = 64, parallelism = 1)
assert encoded.length > 0, "hash should produce output"
assert encoded[0] == '$', "PHC starts with $"
string encoded2 = argon2.hash("password123", timeCost = 2, memoryCost = 64, parallelism = 1)
assert encoded != encoded2, "auto salt produces different hashes"
assert argon2.verify("password123", encoded), "verify auto-salt 1"
assert argon2.verify("password123", encoded2), "verify auto-salt 2"
assert not argon2.verify("wrong", encoded), "wrong password fails"
// ═══ 2. 手动盐(确定性) ═══
string salt = "somesalt12345678"
string h1 = argon2.hash("pw", salt = salt, timeCost = 2, memoryCost = 64, parallelism = 1)
string h2 = argon2.hash("pw", salt = salt, timeCost = 2, memoryCost = 64, parallelism = 1)
assert h1 == h2, "same salt = same hash (deterministic)"
// ═══ 3. secret 密钥哈希 ═══
string withSecret = argon2.hash("pw", salt = salt, secret = "server_key",
timeCost = 2, memoryCost = 64, parallelism = 1)
string noSecret = argon2.hash("pw", salt = salt, timeCost = 2, memoryCost = 64, parallelism = 1)
assert withSecret != noSecret, "secret changes the hash"
// ═══ 4. ad 附加数据 ═══
string withAd = argon2.hash("pw", salt = salt, ad = "user_id=42",
timeCost = 2, memoryCost = 64, parallelism = 1)
assert withAd != noSecret, "ad changes the hash"
// ═══ 5. parallelism 可配 ═══
string p1 = argon2.hash("pw", salt = salt, timeCost = 2, memoryCost = 64, parallelism = 1)
string p2 = argon2.hash("pw", salt = salt, timeCost = 2, memoryCost = 64, parallelism = 2)
assert p1 != p2, "different parallelism = different hash"
assert argon2.verify("pw", p1), "verify p=1"
assert argon2.verify("pw", p2), "verify p=2"
// ═══ 6. 三变体 ═══
string enc_d = argon2.hash("test", salt = salt, timeCost = 2, memoryCost = 64,
parallelism = 1, type = argon2.ARGON2D)
string enc_i = argon2.hash("test", salt = salt, timeCost = 2, memoryCost = 64,
parallelism = 1, type = argon2.ARGON2I)
string enc_id = argon2.hash("test", salt = salt, timeCost = 2, memoryCost = 64,
parallelism = 1, type = argon2.ARGON2ID)
assert enc_d != enc_i, "argon2d != argon2i"
assert enc_i != enc_id, "argon2i != argon2id"
assert argon2.verify("test", enc_d), "verify argon2d"
assert argon2.verify("test", enc_i), "verify argon2i"
assert argon2.verify("test", enc_id), "verify argon2id"
// ═══ 7. hashRaw ═══
string raw = argon2.hashRaw("pw", salt = salt, timeCost = 2, memoryCost = 64,
parallelism = 1, hashLen = 32)
assert raw.length == 64, "32-byte = 64 hex chars"
string raw2 = argon2.hashRaw("pw", salt = salt, timeCost = 2, memoryCost = 64,
parallelism = 1, hashLen = 32)
assert raw == raw2, "hashRaw deterministic"
// ═══ 8. checkNeedsRehash ═══
string weak = argon2.hash("pw", salt = salt, timeCost = 1, memoryCost = 64, parallelism = 1)
assert argon2.checkNeedsRehash(weak, timeCost = 3, memoryCost = 65536, parallelism = 4),
"weak needs rehash"
string strong = argon2.hash("pw", salt = salt, timeCost = 3, memoryCost = 65536, parallelism = 4)
assert not argon2.checkNeedsRehash(strong, timeCost = 3, memoryCost = 65536, parallelism = 4),
"strong ok"
// ═══ 9. 参数提取 ═══
string sample = argon2.hash("x", salt = salt, timeCost = 5, memoryCost = 1024,
parallelism = 2, hashLen = 48)
assert argon2.extractType(sample) == argon2.ARGON2ID, "extract type"
assert argon2.extractTimeCost(sample) == 5, "extract time"
assert argon2.extractMemoryCost(sample) == 1024, "extract memory"
assert argon2.extractParallelism(sample) == 2, "extract parallelism"
// ═══ 10. generateSalt ═══
string s1 = argon2.generateSalt(16)
string s2 = argon2.generateSalt(16)
assert s1.length == 32, "16 bytes = 32 hex"
assert s1 != s2, "random salts differ"
// ═══ 11. 推荐参数 ═══
assert argon2.recommendedTime() == 3, "rec time"
assert argon2.recommendedMemory() == 65536, "rec memory"
assert argon2.recommendedParallelism() == 4, "rec parallelism"
assert argon2.recommendedHashLen() == 32, "rec hashlen"
assert argon2.recommendedSaltLen() == 16, "rec saltlen"
// ═══ 12. Profiles ═══
assert argon2.PROFILE_LOW_MEMORY_TIME == 3, "low mem time"
assert argon2.PROFILE_LOW_MEMORY_MEMORY == 65536, "low mem memory"
assert argon2.PROFILE_LOW_MEMORY_PARALLELISM == 4, "low mem p"
assert argon2.PROFILE_HIGH_MEMORY_TIME == 1, "high mem time"
assert argon2.PROFILE_HIGH_MEMORY_MEMORY == 2097152, "high mem memory"
assert argon2.PROFILE_HIGH_MEMORY_PARALLELISM == 4, "high mem p"
// ═══ 13. 不同 hashLen ═══
string h16 = argon2.hashRaw("pw", salt = salt, timeCost = 2, memoryCost = 64,
parallelism = 1, hashLen = 16)
string h64 = argon2.hashRaw("pw", salt = salt, timeCost = 2, memoryCost = 64,
parallelism = 1, hashLen = 64)
assert h16.length == 32, "16-byte = 32 hex"
assert h64.length == 128, "64-byte = 128 hex"
println("argon2: all tests passed")
return 0
end运行测试
cd release xl.exe run ..\test\cases\extlib_argon2.xl