5 Replies
      Latest reply on Aug 13, 2019 6:25 PM by craigaps
      craigaps Level 1 Level 1 (0 points)

        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

        • Re: CryptoKit TOTP Generation
          eskimo Apple Staff Apple Staff (12,475 points)

          I tried running your code but it failed to compile because, on line 13, the compiler is unable to infer generic parameter H, that is, the hash function used by the HMAC.  How did you get around that?

          Share and Enjoy

          Quinn “The Eskimo!”
          Apple Developer Relations, Developer Technical Support, Core OS/Hardware
          let myEmail = "eskimo" + "1" + "@apple.com"

            • Re: CryptoKit TOTP Generation
              craigaps Level 1 Level 1 (0 points)

              Hey Quinn

               

              It was a cut and paste, then edit problem, I've updated my snippet on line 13.  Needed to read:

                 let hash = HMAC<Insecure.SHA1>.authenticationCode(for: secret, using: key)

               

              Thanks for looking into this for me.

               

              Craig

              • Re: CryptoKit TOTP Generation
                craigaps Level 1 Level 1 (0 points)

                Hey Quinn

                 

                Did you mange to re-try the HMAC generation after I made the update?

                 

                Kind regards

                 

                Craig

                  • Re: CryptoKit TOTP Generation
                    fredericj Apple Staff Apple Staff (10 points)

                    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))")
                    }
                      • Re: CryptoKit TOTP Generation
                        craigaps Level 1 Level 1 (0 points)

                        Just had to specify the algorithm in line 11.  Should be:

                        let hash = HMAC<Insecure.SHA1>.authenticationCode(for: counterData, using: SymmetricKey(data: secret))

                         

                        Can confirm that I get the same OTP code generated with CryptoKit and CommonCrypto

                         

                        Craig