纯Lua实现PBKDF2遇测试结果不符问题求助
Fixing PBKDF2 Implementation in Lua (HMAC-SHA256 PRF)
我最近在无外部库的Lua沙箱环境里实现纯PBKDF2(以HMAC-SHA256作为伪随机函数PRF)时,碰到了完全一样的问题:测试IETF标准向量时,输出结果和预期完全不符。
一开始我也怀疑是字符串与字节编码的转换环节出了问题,甚至反复核对在线HMAC工具的结果,但都没找到根源。直到仔细核对IETF标准后才发现,问题出在迭代索引的编码格式上——PBKDF2要求将循环变量i编码为32位大端整数,之前的实现里这块处理有误,修复后结果就完全匹配标准向量了。
修复后的完整PBKDF2代码
do package.preload["pbkdf2"] = function() local hmac = require 'hmac' local len = string.len local gsub = string.gsub local format = string.format local byte = string.byte local char = string.char local concat = table.concat local ceil = math.ceil local function toBytes(str) local tmp = {} for i = 1, len(str) do tmp[i] = byte(str, i) end return tmp end local function toString(bArray) local tmp = {} for i = 1, #bArray do tmp[i] = char(bArray[i]) end tmp = concat(tmp) return tmp end -- transform a string of bytes in a string of hexadecimal digits local function asHex(s) local h = gsub(s, ".", function(c) return format("%02x", byte(c)) end) return h end -- 将整数转换为32位大端字节字符串(核心修复点) local num2string = function(l) local s = {} -- 严格按照32位大端格式编码:从高位到低位依次提取字节 s[1] = char((l >> 24) & 0xFF) s[2] = char((l >> 16) & 0xFF) s[3] = char((l >> 8) & 0xFF) s[4] = char(l & 0xFF) return concat(s) end local buildBlock = function(hFun, password, salt, c, int) local tmp local tmp2 -- 拼接盐和32位大端格式的迭代索引 local saltWithIndex = salt .. num2string(int) for i = 1, c do if i == 1 then -- PRF(password, salt || INT_32_BE(i)) tmp = hmac.hash(hFun, password, saltWithIndex, true) else -- 迭代HMAC并执行字节级XOR tmp2 = hmac.hash(hFun, password, tmp, true) tmp2 = toBytes(tmp2) tmp = toBytes(tmp) assert(#tmp == #tmp2) for j = 1, #tmp do tmp[j] = tmp[j] ~ tmp2[j] end tmp = toString(tmp) end end return tmp end local truncate = function(str, pos) return string.sub(str, 1, pos) end local deriveKey = function(hFun, message, salt, c, dLen) local hLen = hFun.outputSize -- 派生密钥长度不能超过2^32 * 哈希输出长度,符合标准限制 if dLen > (2^32) * hLen then error("The derived key cannot be larger than 2^32 times the output size of the hash function.") end local blockSize = ceil(dLen/hLen) local final = {} for i = 1, blockSize do final[i] = buildBlock(hFun, message, salt, c, i) end local result = concat(final) -- 截断到指定的密钥长度 if #result > dLen then result = truncate(result, dLen) end assert(#result == dLen) return asHex(result) end return {deriveKey = deriveKey} end end
关键修复说明
核心问题出在num2string函数的编码逻辑:原来的实现采用了小端字节序,而IETF RFC 8018明确要求PBKDF2的迭代索引必须以32位大端整数格式拼接在盐之后。修复后,编码逻辑完全符合标准要求,PRF的输入格式正确,最终派生密钥结果与标准向量一致。
测试验证(标准向量对比)
修复后测试以下官方标准用例,输出结果与预期完全匹配:
用例1:迭代次数c=1
- 输入:key="password",salt="salt",c=1,dkLen=32
- 修复后输出:
120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b - 预期结果:
120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b
用例2:迭代次数c=2
- 输入:key="password",salt="salt",c=2,dkLen=32
- 修复后输出:
ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43 - 预期结果:
AE4D0C95AF6B46D32D0ADFF928F06DD02A303F8EF3C251DFD6E2D85A95474C43
内容的提问来源于stack exchange,提问作者O. Orum
相关产品推荐
相关产品推荐

