Files
otp-cj/src/OtpUri.cj
T

67 lines
2.2 KiB
Plaintext
Raw Normal View History

package simcu::otp
import std.random.*
import std.convert.*
/**
* otpauth:// URI 生成(Google Authenticator / 常见 OTP App 标准)。
* 格式:otpauth://totp/{issuer}:{account}?secret={base32}&issuer={issuer}&algorithm=SHA1&digits=6&period=30
* 同时提供随机 Base32 secret 生成工具。
*/
public class OtpUri {
/// 生成随机 Base32 secret(默认 20 字节 = 160 bit,与 RFC 4226 密钥长度一致)
public static func generateSecret(): String {
generateSecret(20)
}
/// 生成指定字节数的随机 Base32 secret
public static func generateSecret(bytes: Int64): String {
let rnd = Random()
let data = Array<Byte>(bytes, repeat: 0)
for (i in 0..bytes) {
data[i] = UInt8(rnd.nextUInt64() & 0xff)
}
Base32.encode(data)
}
/// 生成 otpauth:// 链接(默认 6 位 / 30s 步长)
public static func build(issuer: String, account: String, secretBase32: String): String {
build(issuer, account, secretBase32, 6, 30)
}
/// 生成 otpauth:// 链接(指定位数)
public static func build(issuer: String, account: String, secretBase32: String, digits: Int64): String {
build(issuer, account, secretBase32, digits, 30)
}
/// 生成 otpauth:// 链接(指定位数与步长)
public static func build(issuer: String, account: String, secretBase32: String, digits: Int64, period: Int64): String {
let label = issuer + ":" + account
var sb = StringBuilder()
sb.append("otpauth://totp/")
sb.append(urlEncode(label))
sb.append("?secret=")
sb.append(secretBase32)
sb.append("&issuer=")
sb.append(urlEncode(issuer))
sb.append("&algorithm=SHA1&digits=")
sb.append(digits.toString())
sb.append("&period=")
sb.append(period.toString())
sb.toString()
}
private static func urlEncode(s: String): String {
var sb = StringBuilder()
for (ch in s) {
match (ch) {
case ':' => sb.append("%3A")
case '/' => sb.append("%2F")
case ' ' => sb.append("%20")
case _ => sb.append(Rune(ch))
}
}
sb.toString()
}
}