feat: add TOTPGenerator (RFC 6238)
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
|
||||
public enum TOTPGenerator {
|
||||
public static func generate(config: TOTPConfig, at date: Date = Date()) -> String {
|
||||
let counter = UInt64(date.timeIntervalSince1970) / UInt64(config.period)
|
||||
let keyBytes = base32Decode(config.secret)
|
||||
let counterBytes = withUnsafeBytes(of: counter.bigEndian, Array.init)
|
||||
let symKey = SymmetricKey(data: keyBytes)
|
||||
let hmacBytes: [UInt8]
|
||||
switch config.algorithm {
|
||||
case .sha1:
|
||||
hmacBytes = Array(HMAC<Insecure.SHA1>.authenticationCode(for: counterBytes, using: symKey))
|
||||
case .sha256:
|
||||
hmacBytes = Array(HMAC<SHA256>.authenticationCode(for: counterBytes, using: symKey))
|
||||
case .sha512:
|
||||
hmacBytes = Array(HMAC<SHA512>.authenticationCode(for: counterBytes, using: symKey))
|
||||
}
|
||||
let offset = Int(hmacBytes[hmacBytes.count - 1] & 0x0f)
|
||||
let truncated = ((Int(hmacBytes[offset]) & 0x7f) << 24)
|
||||
| (Int(hmacBytes[offset + 1]) << 16)
|
||||
| (Int(hmacBytes[offset + 2]) << 8)
|
||||
| Int(hmacBytes[offset + 3])
|
||||
let otp = truncated % Int(pow(10.0, Double(config.digits)))
|
||||
return String(format: "%0\(config.digits)d", otp)
|
||||
}
|
||||
|
||||
public static func secondsRemaining(config: TOTPConfig, at date: Date = Date()) -> Int {
|
||||
let elapsed = Int(date.timeIntervalSince1970) % config.period
|
||||
return config.period - elapsed
|
||||
}
|
||||
|
||||
private static func base32Decode(_ input: String) -> [UInt8] {
|
||||
let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
|
||||
let s = input.uppercased().filter { alphabet.contains($0) }
|
||||
var result: [UInt8] = []
|
||||
var buffer = 0
|
||||
var bitsLeft = 0
|
||||
for char in s {
|
||||
guard let idx = alphabet.firstIndex(of: char) else { continue }
|
||||
buffer = (buffer << 5) | alphabet.distance(from: alphabet.startIndex, to: idx)
|
||||
bitsLeft += 5
|
||||
if bitsLeft >= 8 {
|
||||
bitsLeft -= 8
|
||||
result.append(UInt8((buffer >> bitsLeft) & 0xff))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user