NodeJS+ReactJS项目BCrypt哈希报错:Illegal arguments: string, object
嘿,这个问题我之前也踩过坑,核心原因其实很直白:bcrypt.genSalt()是异步函数,它返回的是一个Promise对象,而非你预期的盐字符串。
你在路由外写的const salt = bcrypt.genSalt(10);,这里的salt变量存的是一个还未完成的Promise,不是实际的盐值字符串。当你把这个Promise对象传给bcrypt.hash(newPassword, salt)时,bcrypt期望第二个参数是字符串类型的盐或者数字类型的加密轮数,结果收到了一个对象,自然就抛出Illegal arguments: string, object的错误了。
而之前你在路由内生成盐时能正常工作,是因为你肯定用了await(比如const salt = await bcrypt.genSalt(10);),await会等待Promise执行完成,拿到的是真正的盐字符串,参数类型完全匹配。
解决办法
方法一:提前异步生成盐(确保拿到实际盐值)
如果一定要共用同一个盐,你需要等待genSalt的Promise完成后再使用。可以在应用启动阶段先生成盐,比如:
const bcrypt = require('bcrypt'); let salt; // 在服务器启动前初始化盐 async function initSalt() { salt = await bcrypt.genSalt(10); } // 先完成盐初始化,再启动服务 initSalt().then(() => { app.listen(3000, () => { console.log('Server running on port 3000'); }); }); // 路由内正常使用 router.post('/register', async (req, res) => { const newPassword = req.body.password; user.password = await bcrypt.hash(newPassword, salt); // 其他业务逻辑... });
这样就能保证salt是实际的字符串值,而非Promise对象。
方法二:直接给hash传加密轮数(更推荐)
其实bcrypt.hash()本身支持直接传入加密轮数作为第二个参数,它会自动帮你生成盐并完成哈希操作。这样既不用手动管理盐,还能保证所有密码使用相同的加密强度,而且每个密码的盐依然是独立的(安全性更高):
// 路由内直接这么写,无需提前生成盐 user.password = await bcrypt.hash(newPassword, 10);
这个方法更简洁,也符合bcrypt的最佳实践——每个密码使用独立的盐是更安全的做法,不建议所有密码共用同一个盐,这样会降低哈希的抗破解能力。
方法三:封装可复用的盐生成函数
如果你想统一管理盐的生成逻辑,可以封装一个异步函数,在需要的时候调用:
async function getSalt() { return await bcrypt.genSalt(10); } // 路由内使用 router.post('/register', async (req, res) => { const newPassword = req.body.password; const salt = await getSalt(); user.password = await bcrypt.hash(newPassword, salt); // 其他业务逻辑... });
不过这个方法和你之前在路由内生成盐的方式本质一致,只是抽离了复用逻辑。
内容的提问来源于stack exchange,提问作者Umair

