O contador é derivado do tempo Unix e passa por HMAC com o segredo do usuário. É então truncado em um código numérico conforme a especificação. As entradas são limitadas para evitar que problemas no tempo do sistema travem o app.
static func generateCode(
secret: Data,
algorithm: TOTPAlgorithm,
digits: Int,
period: Int,
date: Date = .now
) -> String {
let safePeriod = max(1, period)
let safeDigits = max(1, min(9, digits))
let timestamp = max(0, date.timeIntervalSince1970)
let counter = UInt64(timestamp) / UInt64(safePeriod)
var bigEndianCounter = counter.bigEndian
let counterData = withUnsafeBytes(of: &bigEndianCounter) { Data($0) }
let key = SymmetricKey(data: secret)
let hmacBytes: [UInt8]
switch algorithm {
case .sha1:
hmacBytes = Array(HMAC<Insecure.SHA1>.authenticationCode(for: counterData, using: key))
case .sha256:
hmacBytes = Array(HMAC<SHA256>.authenticationCode(for: counterData, using: key))
case .sha512:
hmacBytes = Array(HMAC<SHA512>.authenticationCode(for: counterData, using: key))
}
guard hmacBytes.count >= 4, let last = hmacBytes.last else {
return String(repeating: "0", count: safeDigits)
}
let offset = Int(last & 0x0F)
guard offset + 3 < hmacBytes.count else {
return String(repeating: "0", count: safeDigits)
}
let truncated: UInt32 =
(UInt32(hmacBytes[offset] & 0x7F) << 24)
| (UInt32(hmacBytes[offset + 1]) << 16)
| (UInt32(hmacBytes[offset + 2]) << 8)
| UInt32(hmacBytes[offset + 3])
var modulo: UInt32 = 1
for _ in 0..<safeDigits {
modulo *= 10
}
let code = truncated % modulo
return String(format: "%0\(safeDigits)d", code)
}












