Getting Authentcationfailure with nonce and tag using CryptoKit

621 views Asked by At

What am I doing wrong here? It throws below error while decrypting (at second last line)

Fatal error: 'try!' expression unexpectedly raised an error: CryptoKit.CryptoKitError.authenticationFailure

func encryptDecryptWithNonceTag(){
    let secret = "my-xxx-bit-secret-my-secret-my-s"
    let mySymKey = SymmetricKey(data: secret.data(using: .utf8)!)
    let plain = "Say hello to my little friend!"
    let nonce = try! AES.GCM.Nonce(data: Data(base64Encoded: "fv1nixTVoYpSvpdA")!)
    let tag =  Data(base64Encoded: "e1eIgoB4+lA/j3KDHhY4BQ==")!
    
    //ENCRYPT
    let sealedBox = try! AES.GCM.seal(plain.data(using: .utf8)!, using: mySymKey, nonce: nonce, authenticating: tag)
    let ciphertext = sealedBox.ciphertext.base64EncodedString()
    print("ciphertext: \(ciphertext)")
    
    
    //DECRYPT: Recreate sealedbox with nonce and tag and then decrypt
    let sealedBoxRecreated = try! AES.GCM.SealedBox(nonce: nonce,
                                                    ciphertext: Data(base64Encoded: ciphertext)!,
                                                    tag: tag)
    let decrypted = try! AES.GCM.open(sealedBoxRecreated, using: mySymKey)
    print("decryptedtext:\(String(decoding: decrypted, as: UTF8.self))")
}

2

There are 2 answers

0
udi On BEST ANSWER

You are using tag for both encryption and decryption in the authenticating parameter. You should not provide a pre determined tag while encrypting.

func encryptDecryptWithNonceTag() {
    let secret = "my-xxx-bit-secret-my-secret-my-s"
    let mySymKey = SymmetricKey(data: secret.data(using: .utf8)!)
    let plain = "Say hello to my little friend!"
    let nonce = try! AES.GCM.Nonce(data: Data(base64Encoded: "fv1nixTVoYpSvpdA")!)

    // ENCRYPT
    let sealedBox = try! AES.GCM.seal(plain.data(using: .utf8)!, using: mySymKey, nonce: nonce)
    let ciphertext = sealedBox.ciphertext.base64EncodedString()
    print("ciphertext: \(ciphertext)")
    print("tag: \(sealedBox.tag.base64EncodedString())")

    
    //DECRYPT: Recreate sealedbox with nonce and tag and then decrypt
    let sealedBoxRecreated = try! AES.GCM.SealedBox(nonce: nonce,
                                                    ciphertext: Data(base64Encoded: ciphertext)!,
                                                    tag: sealedBox.tag)
    do {
        let decrypted = try AES.GCM.open(sealedBoxRecreated, using: mySymKey)
        print("decryptedtext: \(String(decoding: decrypted, as: UTF8.self))")
    } catch {
        print("Error decrypting: \(error.localizedDescription)")
    }
    
}

output enter image description here

Btw do not use try! in your code. Because whenever an exception get throws (try fails) your code will crash. Worse case is when debugging you are unable to see the real issue. So always use a catch block.

0
Pierre On

When creating a sealed box for encryption, the authenticating parameter should contain data that should be authentified but not ciphered. This is not any sort of key, neither something you need for deciphering. For example, in a mail, you would like the receiver to be authentified (so no one can mess with that field), but you do not want it ciphered, because intermediate people will need it to actually send the mail to it.

-> So I do not think that tag is what you want to put here. Note that you probably can pass something empty.

Concerning the sealed box for decryption, you now understand that you should not pass the same data as in the previous authenticating parameter.

A sealedbox is made of three parts : the ciphertext, the nonce and the authentication tag.

  • The ciphertext is, as its name shows, simply the ciphered content. In this algorithm, it should be the same length as the original text.
  • The nonce is like a seed, it is used to put more randomness in the ciphering - but it is not a key, it can be public. And most importantly, it should be used only once (hence its name), and change for every use (you can simply use a counter). Hence, a same input message is not ciphered in the same way twice.
  • Now the authentication tag is like a checksum : it is computed during the encryption, and is used to authentify the sender.

As you can see in its documentation, you can create the sealed box with the three components separately, or with all of them combined (which is simply their data put one after the other).

That is why you get an authentication error : you pass a random authentication tag instead of the correct one, so it tells your message cannot be authenticated.

So you will also need to extract the authentication tag from your sealedBox (sealedBox.tag) and feed it to your sealedBoxRecreated (at the tag: parameter).

Result :

//ENCRYPT
let sealedBox = try! AES.GCM.seal(plain.data(using: .utf8)!,
    using: mySymKey,
    nonce: nonce,
    authenticating: Data())

let ciphertext = sealedBox.ciphertext.base64EncodedString()
let tag = sealedBox.tag.base64EncodedString()
    
//DECRYPT: Recreate sealedbox with nonce and tag and then decrypt
let sealedBoxRecreated = try! AES.GCM.SealedBox(nonce: nonce,
    ciphertext: Data(base64Encoded: ciphertext)!,
    tag: Data(base64Encoded: tag)!)

You can also simply export the "combined" content, which is better - because as the nonce should change each time, it probably needs to be communicated along with the message.

//ENCRYPT
let sealedBox = try! AES.GCM.seal(plain.data(using: .utf8)!,
    using: mySymKey,
    nonce: nonce,
    authenticating: Data())

let cipheredMessage = sealedBox.combined.base64EncodedString()
    
//DECRYPT: Recreate sealedbox with nonce and tag and then decrypt
let sealedBoxRecreated = try! AES.GCM.SealedBox(combined: Data(base64Encoded: cipheredMessage)!)