不能用random生成api密钥或jwt盐,因其是伪随机数生成器,基于线性同余算法和低熵种子,可被预测还原,导致密钥泄露、会话劫持等高危风险。

为什么不能用 Random 生成 API 密钥或 JWT 盐
因为 Random 是伪随机数生成器(PRNG),内部用线性同余算法 + 时间种子,只要知道部分输出或初始种子,整个序列就能被还原。攻击者拿到几个 token 后,可能反推出密钥生成逻辑——这在 OAuth、密码重置链接、AES 加密密钥等场景是致命的。真实案例里,有系统用 new Random().Next(0, 1000000).ToString() 当短期验证码,结果被批量预测并绕过验证。
RandomNumberGenerator.Fill() 和 GetBytes() 怎么选
两者都安全,但语义和适用版本不同:
-
Fill(byte[])是 .NET 5+ 推荐写法,更简洁,底层直接调用平台熵源(Windows 的 BCryptGenRandom / Linux 的/dev/urandom),无需手动管理实例生命周期 -
GetBytes(byte[])在旧版(.NET Framework / .NET Core 2.x)中必须配合using块使用,否则可能泄漏句柄;RandomNumberGenerator.Create()返回的实例不是IDisposable的可靠保证,尤其在某些容器环境里 - 别写
new byte[32]; rng.GetBytes(buffer)然后反复复用同一个rng实例——虽然不报错,但部分运行时(如 Alpine 上的 .NET 6)会 fallback 到非加密实现,而Fill()能规避这个风险
示例(推荐):
byte[] key = new byte[32]; RandomNumberGenerator.Fill(key); // .NET 5+
需要随机整数时,为什么不能用 % 取模
取模会破坏均匀分布。比如想生成 [0, 10) 的整数,用 4 字节随机数(0–4294967295)对 10 取模,余数为 0–9 的概率并不相等:0–5 出现次数比 6–9 多一次(因为 4294967296 ÷ 10 = 429496729 余 6)。偏差虽小,但在高并发或长期运行服务中会被放大。
正确做法:
- .NET 6+ 直接用
RandomNumberGenerator.GetInt32(min, max)—— 它内部用拒绝采样(rejection sampling),自动丢弃越界值 - 旧版本先生成足够字节(如 4 字节),再用
BitConverter.ToInt32()转换,然后手动做范围裁剪(仍建议用拒绝采样逻辑) - 绝对不要写
rng.NextBytes(buf); return (int)(BitConverter.ToUInt32(buf, 0) % 100)
Guid.NewGuid() 能当安全随机数用吗
不能。GUID(尤其是 v4)虽然包含随机成分,但其生成规范未要求密码学安全熵源,.NET 的实现实际基于 Random 或系统时间 + 计数器拼接,在某些运行时甚至退化为纯时间戳。微软文档明确指出:Guid 不适用于安全敏感用途。
常见误用:
-
Convert.ToBase64String(Guid.NewGuid().ToByteArray())当 JWTjti -
Guid.NewGuid().ToString("N")当密码重置 token
替代方案:始终从 RandomNumberGenerator 出发,生成字节后再编码:
byte[] tokenBytes = new byte[32];
RandomNumberGenerator.Fill(tokenBytes);
string token = Convert.ToBase64String(tokenBytes).Replace('+', '-').Replace('/', '_').TrimEnd('='); // URL-safe
真正难的是理解「安全随机」不是“看起来乱”,而是“不可预测、无统计偏差、不依赖可推断状态”——这些特性不会因为你多调用几次 Fill() 就自动具备,得从源头约束用法。










