Skip to content

Instantly share code, notes, and snippets.

@smosko
Last active March 10, 2019 06:21
Show Gist options
  • Save smosko/ea6bd2c6413ddfc72ab882d9ad92ad82 to your computer and use it in GitHub Desktop.
Save smosko/ea6bd2c6413ddfc72ab882d9ad92ad82 to your computer and use it in GitHub Desktop.
Time-based OTP 2FA Token
// import HMAC in bridging header:
// #import <CommonCrypto/CommonHMAC.h>
import Foundation
enum OTPAlgorithm: String {
case sha1, sha256, sha512
var hmacAlgorithm: CCHmacAlgorithm {
switch self {
case .sha1: return CCHmacAlgorithm(kCCHmacAlgSHA1)
case .sha256: return CCHmacAlgorithm(kCCHmacAlgSHA256)
case .sha512: return CCHmacAlgorithm(kCCHmacAlgSHA512)
}
}
var digestLength: Int {
switch self {
case .sha1: return Int(CC_SHA1_DIGEST_LENGTH)
case .sha256: return Int(CC_SHA256_DIGEST_LENGTH)
case .sha512: return Int(CC_SHA512_DIGEST_LENGTH)
}
}
}
struct Token {
let id: UUID
let label: String
let issuer: String
let secret: Data // should be stored in keychain
let algorithm: OTPAlgorithm
let digits: Int
let period: TimeInterval
init(label: String, issuer: String, secret: String, algorithm: OTPAlgorithm = .sha1,
digits: Int = 6, period: TimeInterval = 30) {
self.id = UUID()
self.label = label
self.issuer = issuer
self.secret = secret.base32DecodedData! // https://github.com/norio-nomura/Base32
self.algorithm = algorithm
self.digits = digits // 6 or 8
self.period = period
}
func code(at time: Date = Date()) -> String {
let secret = self.secret as NSData
var counter = UInt64(time.timeIntervalSince1970 / period).bigEndian
var digest = [UInt8](repeating: 0, count: algorithm.digestLength)
CCHmac(algorithm.hmacAlgorithm, secret.bytes, secret.length, &counter, MemoryLayout<UInt64>.size, &digest)
let offset = Int(digest.last! & 0x0f)
let truncated = UnsafePointer<UInt8>(digest).advanced(by: offset)
.withMemoryRebound(to: UInt32.self, capacity: MemoryLayout<UInt32>.size)
{ $0.pointee.bigEndian & 0x7fffffff }
return String(truncated).suffix(digits).padding(toLength: digits, withPad: "0", startingAt: 0)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment