2026-08-21 08:43:24 +08:00
|
|
|
# otp-cj
|
|
|
|
|
|
|
|
|
|
仓颉(Cangjie)一次性密码(OTP)库,实现 **HOTP**(RFC 4226)与 **TOTP**(RFC 6238),提供 Base32 密钥编解码(RFC 4648)与 `otpauth://` URI 生成,可直接与 Google Authenticator / Microsoft Authenticator 等常见 OTP App 互通。
|
|
|
|
|
|
|
|
|
|
组织:`simcu` | 包名:`otp` | 版本:`1.0.0`
|
|
|
|
|
|
|
|
|
|
## 特性
|
|
|
|
|
|
|
|
|
|
- **TOTP**:基于时间步长的动态码,默认 6 位 / 30 秒步长,支持自定义位数、步长与时钟偏移窗口
|
|
|
|
|
- **HOTP**:基于计数器的一次性密码,默认 6 位
|
|
|
|
|
- **Base32**:RFC 4648 无填充变体编解码,解码时忽略大小写、空格、短横线与 `=`
|
|
|
|
|
- **otpauth:// URI**:生成可用于二维码的认证链接,并附带随机密钥生成工具
|
|
|
|
|
- **零三方依赖**:仅依赖仓颉标准库 `stdx.crypto.digest.SHA1`
|
|
|
|
|
- 密钥支持 **Base32 字符串**(Google Authenticator 惯例)与**原始字节**两种传入方式
|
|
|
|
|
|
|
|
|
|
## 安装
|
|
|
|
|
|
|
|
|
|
在 `cjpm.toml` 中声明依赖(可改用中央仓版本号):
|
|
|
|
|
|
|
|
|
|
```toml
|
|
|
|
|
[dependencies]
|
|
|
|
|
"simcu::otp" = { path = "../../cangjie/otp-cj" }
|
|
|
|
|
# 或发布到中央仓后:"simcu::otp" = "1.0.0"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
代码中引用:
|
|
|
|
|
|
|
|
|
|
```cangjie
|
2026-08-21 08:46:24 +08:00
|
|
|
import simcu::otp.*
|
2026-08-21 08:43:24 +08:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 快速开始
|
|
|
|
|
|
|
|
|
|
### 1. 生成密钥并绑定 OTP
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
// 生成随机 Base32 密钥(默认 20 字节 = 160 bit)
|
|
|
|
|
let secret = OtpUri.generateSecret() // 例如 "N6C5HNT2F633..."
|
|
|
|
|
|
|
|
|
|
// 生成 otpauth:// 链接,前端渲染成二维码供用户绑定
|
|
|
|
|
let uri = OtpUri.build(issuer: "GamePlatform", account: "18600127718", secretBase32: secret)
|
|
|
|
|
// otpauth://totp/GamePlatform%3A18600127718?secret=N6C5HNT2F633...&issuer=GamePlatform&algorithm=SHA1&digits=6&period=30
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 2. 生成动态码
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
// TOTP:默认 6 位 / 30s 步长,基于当前时间
|
|
|
|
|
let code = Totp.generate(secret) // "351047"
|
|
|
|
|
|
|
|
|
|
// 自定义位数与步长
|
|
|
|
|
let code2 = Totp.generate(secret, digits: 8, period: 60)
|
|
|
|
|
|
|
|
|
|
// 原始字节密钥
|
|
|
|
|
let raw = Base32.decode(secret)
|
|
|
|
|
let code3 = Totp.generateBytes(raw)
|
|
|
|
|
|
|
|
|
|
// HOTP:基于计数器(第 N 次动态码)
|
|
|
|
|
let hotp = Hotp.generate(raw, counter: 0) // 默认 6 位
|
|
|
|
|
let hotp2 = Hotp.generate(raw, counter: 0, digits: 8)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 3. 校验动态码
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
// TOTP 校验:默认允许前后各 1 个时间步的时钟偏移
|
|
|
|
|
let ok = Totp.verify(secret, code) // Bool
|
|
|
|
|
|
|
|
|
|
// 放宽/收紧偏移窗口
|
|
|
|
|
let ok2 = Totp.verify(secret, code, digits: 6, period: 30, window: 2)
|
|
|
|
|
|
|
|
|
|
// 字节密钥版本
|
|
|
|
|
let ok3 = Totp.verifyBytes(raw, code)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 4. Base32 编解码
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
let raw = Base32.decode("N6C5HNT2F633") // 忽略空格/短横线/大小写
|
|
|
|
|
let encoded = Base32.encode(raw) // 往返后还原 "N6C5HNT2F633"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## API 参考
|
|
|
|
|
|
2026-08-21 08:46:24 +08:00
|
|
|
包结构:全部类型位于根包 `simcu::otp`(单一命名空间,无子包)
|
2026-08-21 08:43:24 +08:00
|
|
|
|
|
|
|
|
| 类型 | 说明 |
|
|
|
|
|
|------|------|
|
|
|
|
|
| `Base32` | RFC 4648 无填充 Base32 编解码;`encode(data)` / `decode(input)`,非法字符抛 `IllegalArgumentException` |
|
|
|
|
|
| `HmacSha1` | RFC 2104 / 4226 HMAC-SHA1(blockSize=64);`init(key)` / `compute(data)` |
|
|
|
|
|
| `Hotp` | RFC 4226;`generate(secret, counter[, digits])` |
|
|
|
|
|
| `Totp` | RFC 6238;`generate/generateAt/generateBytes(..., digits, period)` 与 `verify/verifyBytes(..., digits, period, window)` 多种重载;默认 `period=30`、`window=1` |
|
|
|
|
|
| `OtpUri` | `generateSecret([bytes])` 随机密钥;`build(issuer, account, secret[, digits, period])` 生成 otpauth:// 链接 |
|
|
|
|
|
|
|
|
|
|
常用常量:
|
|
|
|
|
|
|
|
|
|
- `Totp.DEFAULT_PERIOD = 30`
|
|
|
|
|
- `Totp.DEFAULT_T0 = 0`
|
|
|
|
|
|
|
|
|
|
## 说明
|
|
|
|
|
|
|
|
|
|
- **HMAC-SHA1 为何自实现**:仓颉标准库 `std.crypto.digest` 的 HMAC 目前仅支持 SHA512,而 HOTP/TOTP 标准基于 HMAC-SHA1,因此本库基于 `stdx.crypto.digest.SHA1` 实现了 RFC 2104 的 HMAC-SHA1。
|
|
|
|
|
- **兼容性**:`Totp.generate` / `Totp.verify` 的结果与 Google Authenticator 完全一致,可交叉验证。
|
|
|
|
|
- **服务端校验建议**:绑定阶段将 Base32 secret 持久化到账号表(`otp_secret` 字段);校验时用 `Totp.verify(secret, code)`。
|
|
|
|
|
|
|
|
|
|
## 运行测试
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
cjpm test
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
测试覆盖:Base32 编解码往返、HOTP/TOTP 与 RFC 官方测试向量的对拍、时钟偏移窗口、otpauth:// URI 生成等。
|
|
|
|
|
|
|
|
|
|
## 配合 SimApi 使用
|
|
|
|
|
|
|
|
|
|
在 simapi 后端中作为账号二次验证通道:
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
// AuthCodeService 示例:绑定 OTP 的账号优先接受 TOTP 动态码
|
|
|
|
|
let otpOk = Totp.verify(acc.otpSecret, code)
|
|
|
|
|
```
|