Post

Replies

Boosts

Views

Created

CryptoKit TOTP Generation
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
7
0
5.8k
Aug ’19
Checking is Biometry is Enrolled
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
1
0
3.0k
Jul ’19
SecItemCopyMatching Touch ID Dialog
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
10
0
5.8k
May ’18