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
|
|||
|
|
}
|
|||
|
|
}
|