Files
otp-cj/src/core/Hotp.cj
T

54 lines
1.5 KiB
Plaintext
Raw Normal View History

package simcu::otp.core
/**
* HOTPHMAC-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
}
}