54 lines
1.5 KiB
Plaintext
54 lines
1.5 KiB
Plaintext
package simcu::otp.core
|
||
|
||
/**
|
||
* HOTP(HMAC-based One-Time Password, RFC 4226)。
|
||
* 公式:HOTP(K,C) = Truncate(HMAC-SHA1(K, C)) mod 10^digits
|
||
*/
|
||
public class Hotp {
|
||
/// 生成一次性密码(默认 6 位)。counter 为 8 字节大端计数。
|
||
public static func generate(secret: Array<Byte>, counter: Int64): String {
|
||
generate(secret, counter, 6)
|
||
}
|
||
|
||
/// 生成一次性密码,指定位数。counter 为 8 字节大端计数。
|
||
public static func generate(secret: Array<Byte>, counter: Int64, digits: Int64): String {
|
||
// counter → 8 字节大端
|
||
var counterBytes = Array<Byte>(8, repeat: 0)
|
||
var c = counter
|
||
var i = 7
|
||
while (i >= 0) {
|
||
counterBytes[i] = UInt8(c & 0xff)
|
||
c = c >> 8
|
||
i -= 1
|
||
}
|
||
let hmac = HmacSha1(secret)
|
||
let hs = hmac.compute(counterBytes)
|
||
let offset = Int64(hs[19]) & 0x0f
|
||
let binCode = ((Int64(hs[offset]) & 0x7f) << 24) |
|
||
(Int64(hs[offset + 1]) << 16) |
|
||
(Int64(hs[offset + 2]) << 8) |
|
||
Int64(hs[offset + 3])
|
||
let mod = pow10(digits)
|
||
let code = binCode % mod
|
||
padLeft(code.toString(), digits)
|
||
}
|
||
|
||
private static func pow10(n: Int64): Int64 {
|
||
var r: Int64 = 1
|
||
var i = 0
|
||
while (i < n) {
|
||
r *= 10
|
||
i += 1
|
||
}
|
||
r
|
||
}
|
||
|
||
private static func padLeft(s: String, width: Int64): String {
|
||
var r = s
|
||
while (r.size < width) {
|
||
r = "0" + r
|
||
}
|
||
r
|
||
}
|
||
}
|