HiI'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: 745890To 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 10Craig
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Created
HiWhat is the best way to determine if an iPhone or iPad has a biometry sensor? The user may or may not have registered their face or fingerprint, regardless I would like to know what biometry sensor is on the device. My app targets iOS 11, so it has to be either Touch or Face.Many thanksCraig
HiI have created a private key in the Keychain with kSecAttrAccessibleWhenUnlockedThisDeviceOnly. When I attempt to access the key to perform a signing operation, the Touch ID dialog will sometimes appear, but in most cases I have to touch the biometry sensor, then the dialog is displayed. Here's the code I'm using to create the key and access the key in a sign operation.public static func create(with name: String, authenticationRequired: SecAccessControlCreateFlags? = nil) -> Bool
{
guard !name.isEmpty else
{
return false
}
var error: Unmanaged<CFError>?
// Private key parameters
var privateKeyParams: [String: Any] = [
kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: name
]
// If we are using a biometric sensor to access the key, we need to create an SecAccessControl instance.
if authenticationRequired != nil
{
guard let access = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, authenticationRequired!, &error) else
{
return false
}
privateKeyParams[kSecAttrAccessControl as String] = access
}
// Global parameters for our key generation
let parameters: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
kSecPrivateKeyAttrs as String: privateKeyParams
]
// Generate the keys.
guard let privateKey = SecKeyCreateRandomKey(parameters as CFDictionary, &error) else
{
return false
}
// Private key created!
return true
}This is the code to sign the data that should prompt for the biometry sensor (Touch ID or Face ID).public static func sign(using name: String, value: String, localizedReason: String? = nil, base64EncodingOptions: Data.Base64EncodingOptions = []) -> String?
{
guard !name.isEmpty else
{
return nil
}
guard !value.isEmpty else
{
return nil
}
// Check if the private key exists in the chain, otherwise return
guard let privateKey: SecKey = getPrivateKey(name, localizedReason: localizedReason ?? "") else
{
return nil
}
let data = value.data(using: .utf8)!
var error: Unmanaged<CFError>?
guard let signedData = SecKeyCreateSignature(privateKey,
rsaSignatureMessagePKCS1v15SHA512,
data as CFData,
&error) as Data? else
{
return nil
}
return signedData.base64EncodedString(options: base64EncodingOptions)
}
fileprivate static func getPrivateKey(_ name: String, localizedReason: String) -> SecKey?
{
let query: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrApplicationTag as String: name,
kSecReturnRef as String: true,
kSecUseOperationPrompt as String : localizedReason
]
var item: CFTypeRef? = nil
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess else
{
if status == errSecUserCanceled
{
print("\tError: Accessing private key failed: The user cancelled (%@).", "\(status)")
}
else if status == errSecDuplicateItem
{
print("\tError: The specified item already exists in the keychain (%@).", "\(status)")
}
else if status == errSecItemNotFound
{
print("\tError: The specified item could not be found in the keychain (%@).", "\(status)")
}
else if status == errSecInvalidItemRef
{
print("\tError: The specified item is no longer valid. It may have been deleted from the keychain (%@).", "\(status)")
}
else
{
print("\tError: Accessing private key failed (%@).", "\(status)")
}
return nil
}
return (item as! SecKey)
}Then in my app, I would simply call guard let result = sign("mykey", "helloworld") else
{
print("failed to sign")
return
}
print(result)So the getPrivateKey function is the one that calls SecKeyCopyingMatching, the 3 methods are in a helper class; what's the best approach to reliabably display the biometry dialog?Thanks