CryptoKitError.authenticationFailure when decrypting an AES SealedBox
arielelkin opened this issue · 6 comments
Question Checklist
- I'm using the latest version of Swift Crypto (master branch)
- I read the Contribution Guidelines
- I searched for existing GitHub issues
Question Description
I'm trying to decrypt existing data encrypted using AES GCM. I initialise a SealedBox
with the encrypted data, but when I pass that to AES.GCM.open
I get a .CryptoKitError.authenticationFailure
Below is some sample code below that reproduces the issue:
let secret = "my-256-bit-secret-my-secret-my-s"
let key = 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: key, nonce: nonce, authenticating: tag)
let ciphertext = sealedBox.ciphertext.base64EncodedString()
print("ciphertext: \(ciphertext)") // bWtTZkPAu7oXpQ3QpHvoTvc4NQgDTIycXHFJWvjk
let sealedBoxToDecrypt = try! AES.GCM.SealedBox(nonce: nonce,
ciphertext: Data(base64Encoded: ciphertext)!,
tag: tag)
let decrypted = try! AES.GCM.open(sealedBoxToDecrypt, using: key) // Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: CryptoKit.CryptoKitError.authenticationFailure
Hey @arielelkin,
The problem with your code is that on the seal side, you're passing tag
to authenticating
. That means that tag
is added as authenticated data to the GCM encryption operation.
You're not passing that same data on the open side of the API, resulting in inconsistent data being authenticated.
The other issue is that you should use the tag that was computed, not the hardcoded one you have.
Here's a fixed snipped of code:
let secret = "my-256-bit-secret-my-secret-my-s"
let key = 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: key, nonce: nonce)
let ciphertext = sealedBox.ciphertext.base64EncodedString()
print("ciphertext: \(ciphertext)") // bWtTZkPAu7oXpQ3QpHvoTvc4NQgDTIycXHFJWvjk
let sealedBoxToDecrypt = try! AES.GCM.SealedBox(nonce: nonce,
ciphertext: Data(base64Encoded: ciphertext)!,
tag: sealedBox.tag)
let decrypted = try! AES.GCM.open(sealedBoxToDecrypt, using: key) // Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: CryptoKit.CryptoKitError.authenticationFailure
Thanks for your response. I understand the issue, I'm trying to understand how this works when you're passed a String
or Data
and not a SealedBox
instance...
Should I therefore expect to be given a ciphertext as well as the tag that was computed during encryption?
If you're not passing a SealedBox, you can reconstruct it like you did in your example.
It can be initialized with the nonce, ciphertext and tag that are transmitted by the sender (who encrypted the payload).
See examples at:
https://developer.apple.com/documentation/cryptokit/performing_common_cryptographic_operations
swift-crypto seems to be behaving like intended. The Playground should provide further info on how to combine those.
Note that we do not provide security reviews of protocols and it's the caller's responsibility to correctly and safely distribute the relevant values.
Thanks!
Direct Encrypt and Decrypt in Single method
func testEncryptandDecrypt(){
let secret = "my-256-bit-secret-my-secret-my-s"
let key = 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: key, nonce: nonce)
let ciphertext = sealedBox.ciphertext.base64EncodedString()
print("ciphertext: (ciphertext)") // bWtTZkPAu7oXpQ3QpHvoTvc4NQgDTIycXHFJWvjk
let sealedBoxToDecrypt = try! AES.GCM.SealedBox(nonce: nonce,
ciphertext: Data(base64Encoded: ciphertext)!,
tag: sealedBox.tag)
let decrypted = try! AES.GCM.open(sealedBoxToDecrypt, using: key)
print(String(decoding: decrypted, as: UTF8.self))
}
Encrypt and Decrypt in Single method by passing tag with Encrypt message
func testEncryptandDecryptFirstWay() {
let keyStr = "d5a423f64b607ea7c65b311d855dc48f36114b227bd0c7a3d403f6158a9e4412"
let key = SymmetricKey(data: Data(hex:keyStr))
let nonceData = Data(hex: "131348c0987c7eece60fc0bc")
let nonce: AES.GCM.Nonce = try! AES.GCM.Nonce(data: nonceData)
let plain = "This is first cypto graphy method"
let encyptedData = plain.asData.aesGCMEncypt(nonce: nonce, key: key)
var decyptedStr = ""
if let encyptedData = plain.asData.aesGCMEncypt(nonce: nonce, key: key) {
decyptedStr = encyptedData.aesGCMDecyrpt(nonce: nonce, key: key)
}
XCTAssertEqual(plain, decyptedStr)
extension String {
var asData: Data {
return self.data(using: .utf8) ?? Data()
}
}
extension Data {
func aesGCMEncypt(nonce: AES.GCM.Nonce, key: SymmetricKey) ->Data?{
// Encrypt
do {
let sealedBox = try AES.GCM.seal(self, using: key, nonce: nonce)
let ciphertext = sealedBox.ciphertext.base64EncodedString()
let tag = sealedBox.tag
let tagPlusCiphertext = tag + ciphertext.asData
return tagPlusCiphertext
}
catch let exceptioInfo {
debugPrint("Encyption exceptioInfo: (exceptioInfo)")
}
return nil
}
func aesGCMDecyrpt(nonce: AES.GCM.Nonce, key: SymmetricKey) -> String{
let tag = self.subtract(0, 16)
let ciphertextData = self.subtract(tag.count, self.count - tag.count)
let ciphertext = ciphertextData.asString
// Decypt
var decodeStr: String = ""
do {
let sealedBoxToDecrypt = try AES.GCM.SealedBox(nonce: nonce,
ciphertext: Data(base64Encoded: ciphertext)!,
tag: tag)
let decrypted = try AES.GCM.open(sealedBoxToDecrypt, using: key)
decodeStr = String(decoding: decrypted, as: UTF8.self)
} catch let exceptioInfo {
debugPrint("Decyption exceptioInfo: \(exceptioInfo)")
}
return decodeStr
}
/* Substract data object by passing start bytes and needed number of bytes called length*/
public func subtract(_ start: Int, _ length: Int) ->Data {
precondition(self.count >= start + length,
"Invalid data range range. trying to find out of bound data")
let allBytes = Array(Data(bytes: self.bytes, count: self.count))
let partBytes = Array(allBytes[start..<start + length])
let dataPart = Data(bytes: partBytes, count: partBytes.count)
return dataPart
}
var asString: String {
let str = String(decoding: self, as: UTF8.self)
return str
}
}