CryptoKit authenticationFailure when try decrypt

2.5k Views Asked by At

I'm trying to decrypt payload using SymmetricKey. I've tried ChaChaPoly and AES.GCM to open sealedBox but I'm still getting CryptoKit.CryptoKitError.authenticationFailure here is my implementation:

let iv: [UInt8] = [0x00, 0x01, 0x02, 0x03,
              0x04, 0x05, 0x06, 0x07,
              0x08, 0x09, 0x0A, 0x0B,
              0x0C, 0x0D, 0x0E, 0x0F]

func generatePair() {
        let priv = P256.KeyAgreement.PrivateKey()
        privateKey = priv
        publicKey = priv.publicKey
    }

func createSymmetricKey(serverPublicKeyPEM: String) -> SymmetricKey? {
        guard let privateKey = privateKey, 
              let publicKey = publicKey else { return nil }
        do {
            let serverPubKey = try P256.KeyAgreement.PublicKey(pemRepresentation: serverPublicKeyPEM)
            let shared = try privateKey.sharedSecretFromKeyAgreement(with: serverPubKey)
            let symetricKey = shared.hkdfDerivedSymmetricKey(using: SHA256.self,
                                                             salt: Data(bytes: iv, count: iv.count),
                                                             sharedInfo: publicKey.rawRepresentation + serverPubKey.rawRepresentation,
                                                             outputByteCount: 32)
            return symetricKey
        } catch {
            //TODO: Handle Error
            print("error \(error)")
            return nil
        }
    }

func decrypt(payload: String, symmetricKey: SymmetricKey) {
        guard let cipherText = Data(base64Encoded: payload) else { return }
        do {
//            let sealedBox = try ChaChaPoly.SealedBox(combined: cipherText)
//            let decrypted = try ChaChaPoly.open(sealedBox, using: symmetricKey)
            let sb = try AES.GCM.SealedBox(combined: cipherText)
            let decrypted = try AES.GCM.open(sb, using: symmetricKey)
            print("")
        } catch {
            print("error: \(error)") //here getting CryptoKit.CryptoKitError.authenticationFailure
        }
    }

Also I know how implementation on backend side looks like:

public static String encrypt(String sessionKey, String devicePublicKey, String plainString) throws Exception {
        byte[] plain = Base64.getEncoder().encodeToString(plainString.getBytes(StandardCharsets.UTF_8)).getBytes();
        SecretKey key = generateSharedSecret(decodePrivateKey(sessionKey), decodePublicKey( devicePublicKey));
        Cipher encryptor = Cipher.getInstance("AES/CTR/NoPadding", BouncyCastleProvider.PROVIDER_NAME);
        IvParameterSpec ivSpec = new IvParameterSpec(INITIALIZATION_VECTOR);
        encryptor.init(Cipher.ENCRYPT_MODE, key, ivSpec);
        return Base64.getEncoder().encodeToString(encryptor.doFinal(plain, 0, plain.length));
    }
1

There are 1 best solutions below

0
On BEST ANSWER

The issue probably lies within the initialisation vector or nonce you use. Counting the bytes, we come to a total of 16 nonce bytes, even though GCM only needs 12. Now, using 16 is not necessarily good or bad, but the CryptoKit implementation assumes 12 bytes when calling AES.GCM.SealedBox(combined:). In order to support 16 nonce bytes, you will have to use AES.GCM.SealedBox(nonce:ciphertext:tag:) instead.

let ciphertext = Data(...)

do {
    let nonce = try AES.GCM.Nonce(data: ciphertext[0 ..< 16]
    let message = ciphertext[16 ..< ciphertext.count - 16]
    let tag = ciphertext[ciphertext.count - 16 ..< ciphertext.count]
    
    let sb = try AES.GCM.SealedBox(nonce: nonce, ciphertext: ciphertext, tag: tag)
    let decrypted = try AES.GCM.open(sb, using: key)
} catch {
    print("Error: \(error)")
}

Looking at your server code, make sure the shared secret is not 'just' the shared secret. generateSharedSecret sounds like it's the secret after performing key exchange, but without performing the key derivation (HKDF, as seen in the Swift code).

Also looking deeper into your server code, make sure your response data contains the nonce, encrypted message and tag. Some crypto implementations force you to do this concatenation yourself. So instead of return Base64(doFinal) (pseudo code), you should instead make sure doFinal includes a tag (GCM only), and return Base64(nonce + encrypted message + tag). Again, the tag only when using GCM.

As also mentioned in the comments, GCM and CTR are different modes of operation for AES. Make sure you use the same one on both parties, so either GCM on both iOS and server or CTR on both iOS and server. Not doing so, will always result in failure of decryption.

If you want to use CTR, you'll have to take a look at CommonCrypto, the old Apple crypto library. This one has CTR implemented, but doesn't support GCM (as the implementation was never released).

One final note, when using GCM, also make sure your additional authentication data (if any) is correct.