Sparkdo.VerificationCode
3.0.0-preview.1
dotnet add package Sparkdo.VerificationCode --version 3.0.0-preview.1
NuGet\Install-Package Sparkdo.VerificationCode -Version 3.0.0-preview.1
<PackageReference Include="Sparkdo.VerificationCode" Version="3.0.0-preview.1" />
<PackageVersion Include="Sparkdo.VerificationCode" Version="3.0.0-preview.1" />
<PackageReference Include="Sparkdo.VerificationCode" />
paket add Sparkdo.VerificationCode --version 3.0.0-preview.1
#r "nuget: Sparkdo.VerificationCode, 3.0.0-preview.1"
#:package Sparkdo.VerificationCode@3.0.0-preview.1
#addin nuget:?package=Sparkdo.VerificationCode&version=3.0.0-preview.1&prerelease
#tool nuget:?package=Sparkdo.VerificationCode&version=3.0.0-preview.1&prerelease
Sparkdo.VerificationCode
验证码核心实现:CSPRNG 生成、版本化 PBKDF2 hash、cache/lock-backed manager、发送间隔限频,
一行 AddSparkdoVerificationCode() 接线。AOT 与裁剪安全。
快速开始
using Microsoft.Extensions.DependencyInjection;
using Sparkdo.VerificationCode;
var services = new ServiceCollection();
// 1) 本产品域:默认内存缓存、json 序列化、TTL 投影、默认服务
services.AddSparkdoVerificationCode(options =>
{
options.Duration = TimeSpan.FromMinutes(5);
options.SendInterval = TimeSpan.FromMinutes(1);
options.HashSecret = secretsProvider.Get("VerificationCode:HashSecret"); // 可选 pepper
});
// 2) 分布式锁必须显式选择(本包故意不注册——见下文红线)
services.AddSparkdoDistributedLocking(); // 单节点:进程内锁
// 跨进程/跨机器部署:改接 Sparkdo.DistributedLocking.Redis
using var provider = services.BuildServiceProvider();
var manager = provider.GetRequiredService<IVerificationCodeManager>();
// 创建:明文只出现一次,直接交给发送 adapter
var info = await manager.CreateAsync(new VerificationCodeCreateOptions
{
Key = "login:sms:user-42"
});
await smsSender.SendAsync(user.Phone, info.Code);
// 校验:成功即消费;失败计入失败次数,达到上限后验证码作废
var ok = await manager.VerifyAsync(info.Key, submittedCode);
为什么分布式锁要显式注册
同 key 串行化保证一次性消费与失败计数不丢更新。若本包静默注册进程内锁,多节点部署会
无声地失去这两条语义(fail-open);本包选择不注册,未接锁 provider 时解析
IVerificationCodeManager 立即失败(fail-closed),把部署形态的决定权留给宿主:
- 单节点:
AddSparkdoDistributedLocking()(进程内锁,零网络开销)。 - 多节点:
Sparkdo.DistributedLocking.Redis(Redis 原子脚本 provider)。
包内做了什么
| 服务 | 默认实现 | 注册语义 |
|---|---|---|
IVerificationCodeGenerator |
DefaultVerificationCodeGenerator(CSPRNG + 拒绝采样) |
TryAdd,可替换 |
IVerificationCodeHasher |
DefaultVerificationCodeHasher(PBKDF2-SHA256,版本化 payload) |
TryAdd,可替换 |
IVerificationCodeManager |
DefaultVerificationCodeManager(cache/lock-backed) |
TryAdd,可替换 |
IConfigureOptions<SparkdoCachingOptions> |
VerificationCodeCachingOptionsConfigure(TTL 投影) |
TryAddEnumerable,可组合 |
IJsonOptionsContributor |
本包 cache payload 的 source-generated resolver | TryAddEnumerable,可组合 |
IDistributedCache / ICache<> / IJsonSerializer |
复用 AddSparkdoCaching / AddSparkdoJsonCore |
TryAdd,宿主实现优先 |
基础设施默认通过 TryAdd 组合:宿主先注册 Redis IDistributedCache 或自己的
IJsonSerializer 时不被覆盖;重复调用 AddSparkdoVerificationCode 幂等。
hash 设计
payload 格式 v1:{iterations}:{base64(hash32)},salt 为 16 字节 base64:
- 版本化:算法或参数升级时换版本号,旧 payload 按自身字段校验,天然平滑迁移。
- 迭代次数在 payload 内:调低全局
HashIterations不使已发出的验证码失效; 校验按 payload 内的迭代次数执行(上限 10000000)。 - pepper(可选):配置
HashSecret后以0x00分隔拼接口令材料,泄漏缓存库 也无法离线穷举 6 位数字码;默认null表示不使用 pepper,此时只剩 600000 次迭代的 成本,10^6 个候选在 GPU 集群上可离线穷举——生产部署须由 secrets provider 提供HashSecret。 - 固定时间比较:
CryptographicOperations.FixedTimeEquals,无短路时序侧信道。 - fail-closed:payload 版本、迭代次数、base64、salt/hash 长度任一非法一律返回 false(且拒绝尾部垃圾字符),不抛异常。
性能与分配(实测,.NET 11 RC / ShortRun 基准)
| 路径 | 耗时 | 托管分配 |
|---|---|---|
| 生成 6 位数字码 | ~0.7 µs | 40 B(仅结果字符串) |
| Hash(1k 迭代) | ~0.33 ms | ~234 B(2 个结果字符串 + BCL Pbkdf2 内部开销) |
| Verify(1k 迭代) | ~0.33 ms | ≤ 34 B |
| Verify(600k 迭代,默认) | ~193 ms | 0 B 显示精度 |
| Verify(非法 payload 快速失败) | ~3 ns | 0 B |
Verify 全链路 span 化:payload 解析(span 切片 + Base64.DecodeFromChars)、
salt/hash/派生结果(栈上)、大小写归一与 UTF-8 编码(免分配,超预算走
ArrayPool)——本实现自身零堆分配;唯一分配来自 BCL Rfc2898DeriveBytes.Pbkdf2
单次调用的内部开销(CNG 提供者句柄),手写 PBKDF2 以换取消除它不值得。
默认 600000 次迭代意味着每次 Create/Verify 约 200 ms CPU——这是抬高「缓存库被拖库 后离线穷举低熵码」成本的定价,但只有同时配置
HashSecret(pepper)时才对 6 位码 构成实际阻碍。吞吐敏感场景可调低HashIterations(payload 版本化保证平滑过渡), 或评估 peppered 快速 hash(HMAC)方案的安全取舍。
存储与 TTL
- 验证码 payload(
VerificationCodeCacheItem):hash、salt、ExpireAt、失败计数、 大小写设置,经ICache<>走Sparkdo.Caching(Redis provider 开箱即用)。 - 发送间隔 payload(
VerificationCodeIntervalCacheItem):间隔到期时间。 - 物理 TTL 由
VerificationCodeCachingOptionsConfigure从全局 Options 投影: 验证码 TTL =Duration、间隔 TTL =SendInterval;逻辑过期由 manager 读取时 再校验,物理 TTL 只是清理兜底。 - 失败计数回写把物理 TTL 钉在条目剩余寿命上;剩余时间不足 1 毫秒(tick 级或因 校验耗时转负)时以 1 毫秒兜底,保证物理清理窗口在底层 provider 侧始终有效—— 逻辑过期仍是唯一权威,可校验寿命不会被拉长。
常见坑
- 多节点没接 Redis 锁:进程内锁只在本进程串行化;跨机器部署必须接 Redis provider, 否则一次性消费与失败计数在节点间各自为政。
HashSecret换值:换 pepper 后旧验证码全部校验失败(hash 不匹配)—— peppering 与盐不同,不支持就地轮换;灰度期请用新 key 前缀过渡。- 调低
HashIterations不影响存量验证码:校验按 payload 内的迭代次数执行; 想强制全部重发就清缓存。 - 明文进日志:
VerificationCodeInfo.Code只应交给发送 adapter;打了日志等于 放弃「服务端不存明文」的审计承诺。 - 单次配置超过全局上限会抛
InvalidOptions:单次Duration/SendInterval是「上限内的收紧」,不是「任意放大」。 - 把用户可控值透传给
Length:长度超过VerificationCodeOptions.MaxLength(64) 会在 manager 校验与VerificationCodeGenerationContext构造期被显式拒绝 (InvalidOptions/ArgumentOutOfRangeException)——生成路径按长度分配, 别把没有上限的输入直接塞进去。 - 字符集重复字符:会在
VerificationCodeGenerationContext构造期抛异常—— 想加字符请确认唯一性,这是熵守门,不是苛刻。
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. net11.0 is compatible. |
-
net10.0
- Microsoft.Extensions.Caching.Abstractions (>= 10.0.8)
- Microsoft.Extensions.Caching.Memory (>= 10.0.8)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.8)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.8)
- Microsoft.Extensions.Options (>= 10.0.8)
- Microsoft.Extensions.Primitives (>= 10.0.8)
- Sparkdo.Caching (>= 3.0.0-preview.1)
- Sparkdo.DistributedLocking.Abstractions (>= 3.0.0-preview.1)
- Sparkdo.Json (>= 3.0.0-preview.1)
- Sparkdo.VerificationCode.Abstractions (>= 3.0.0-preview.1)
-
net11.0
- Microsoft.Extensions.Caching.Memory (>= 10.0.8)
- Sparkdo.Caching (>= 3.0.0-preview.1)
- Sparkdo.DistributedLocking.Abstractions (>= 3.0.0-preview.1)
- Sparkdo.Json (>= 3.0.0-preview.1)
- Sparkdo.VerificationCode.Abstractions (>= 3.0.0-preview.1)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Sparkdo.VerificationCode:
| Package | Downloads |
|---|---|
|
Sparkdo.VerificationCode.Imaging
Sparkdo 验证码图像库,提供验证码图像生成功能实现 |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.0-preview.1 | 58 | 10/30/2026 |
| 2.0.0-preview.2 | 89 | 7/20/2026 |
| 2.0.0-preview.1 | 81 | 7/18/2026 |
| 1.0.4-preview.3 | 87 | 6/8/2026 |
| 1.0.4-preview.2 | 84 | 6/7/2026 |
| 1.0.4-preview.1 | 96 | 6/7/2026 |
| 1.0.3 | 179 | 6/6/2026 |
| 1.0.3-preview.1 | 89 | 5/17/2026 |
| 1.0.2 | 152 | 4/27/2026 |
| 1.0.2-preview.5 | 92 | 4/15/2026 |
| 1.0.2-preview.4 | 121 | 2/8/2026 |
| 1.0.2-preview.3 | 117 | 2/1/2026 |
| 1.0.2-preview.2 | 103 | 1/31/2026 |
| 1.0.2-preview.1 | 200 | 12/4/2025 |