Hi
I'm using the new CryptoKit to generate a 6 or 8 digit TOTP code. Anyone been successful doing this?
Using Xcode 11 BETA 5, targeting iOS 13 and Swift 5.1. Here is a snippet of generating an TOTP via CommonCrypto versus CryptoKit in playground (BETA). The base32Decode function returns Data.
import CryptoKit
import CommonCrypto
import Foundation
let period = TimeInterval(30)
let digits = 6
let secret = base32Decode(value: "5FAA5JZ7WHO5WDNN")!
var counter = UInt64(Date().timeIntervalSince1970 / period).bigEndian
func cryptoKitOTP() {
// Generate the key based on the counter.
let key = SymmetricKey(data: Data(bytes: &counter, count: MemoryLayout.size(ofValue: counter)))
let hash = HMAC<Insecure.SHA1>.authenticationCode(for: secret, using: key)
var truncatedHash = hash.withUnsafeBytes { ptr -> UInt32 in
let offset = ptr[hash.byteCount - 1] & 0x0f
let truncatedHashPtr = ptr.baseAddress! + Int(offset)
return truncatedHashPtr.bindMemory(to: UInt32.self, capacity: 1).pointee
}
truncatedHash = UInt32(bigEndian: truncatedHash)
truncatedHash = truncatedHash & 0x7FFF_FFFF
truncatedHash = truncatedHash % UInt32(pow(10, Float(digits)))
print("CryptoKit OTP value: \(String(format: "%0*u", digits, truncatedHash))")
}
func commonCryptoOTP() {
let key = Data(bytes: &counter, count: MemoryLayout.size(ofValue: counter))
let (hashAlgorithm, hashLength) = (CCHmacAlgorithm(kCCHmacAlgSHA1), Int(CC_SHA1_DIGEST_LENGTH))
let hashPtr = UnsafeMutablePointer.allocate(capacity: Int(hashLength))
defer {
hashPtr.deallocate()
}
secret.withUnsafeBytes { secretBytes in
// Generate the key from the counter value.
counterData.withUnsafeBytes { counterBytes in
CCHmac(hashAlgorithm, secretBytes.baseAddress, secret.count, counterBytes.baseAddress, key.count, hashPtr)
}
}
let hash = Data(bytes: hashPtr, count: Int(hashLength))
var truncatedHash = hash.withUnsafeBytes { ptr -> UInt32 in
let offset = ptr[hash.count - 1] & 0x0F
let truncatedHashPtr = ptr.baseAddress! + Int(offset)
return truncatedHashPtr.bindMemory(to: UInt32.self, capacity: 1).pointee
}
truncatedHash = UInt32(bigEndian: truncatedHash)
truncatedHash = truncatedHash & 0x7FFF_FFFF
truncatedHash = truncatedHash % UInt32(pow(10, Float(digits)))
print("CommonCrypto OTP value: \(String(format: "%0*u", digits, truncatedHash))")
}
func otp() {
commonCryptoOTP()
cryptoKitOTP()
}
otp()
The output based on now as in 2:28pm is:
CommonCrypto OTP value: 819944
CryptoKit OTP value: 745890
To confirm the OTP value, I used oathtool which you can brew install to generate an array of TOTP's. For example:
oathtool --totp --base32 5FAA5JZ7WHO5WDNN -w 10
Craig
Hello Craig,
The code was almost right, it seems you just swapped the key and data field from the TOTP spec.
Here's the fixed code:
- I changed the serialization of the key to base64 above as I was missing your dependency for base32.
- I swapped the inputs to the HMAC code and adjusted to get the right types for each of the inputs.
Hope this fixes it!
import CryptoKit
import Foundation
let period = TimeInterval(30)
let digits = 6
let secret = Data(base64Encoded: "6UAOpz+x3dsNrQ==")!
var counter = UInt64(Date().timeIntervalSince1970 / period).bigEndian
func cryptoKitOTPFixed() {
let counterData = withUnsafeBytes(of: &counter) { Array($0) }
let hash = HMAC<Insecure.SHA1>.authenticationCode(for: counterData, using: SymmetricKey(data: secret))
var truncatedHash = hash.withUnsafeBytes { ptr -> UInt32 in
let offset = ptr[hash.byteCount - 1] & 0x0f
let truncatedHashPtr = ptr.baseAddress! + Int(offset)
return truncatedHashPtr.bindMemory(to: UInt32.self, capacity: 1).pointee
}
truncatedHash = UInt32(bigEndian: truncatedHash)
truncatedHash = truncatedHash & 0x7FFF_FFFF
truncatedHash = truncatedHash % UInt32(pow(10, Float(digits)))
print("CryptoKitFixed OTP value: \(String(format: "%0*u", digits, truncatedHash))")
}