apple / swift-crypto

Open-source implementation of a substantial portion of the API of Apple CryptoKit suitable for use on Linux platforms.
https://apple.github.io/swift-crypto
Apache License 2.0
1.46k stars 158 forks source link

CryptoKitError.authenticationFailure when decrypting an AES SealedBox #76

Closed arielelkin closed 3 years ago

arielelkin commented 3 years ago

Question Checklist

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
FredericJacobs commented 3 years ago

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
arielelkin commented 3 years ago

Hi @FredericJacobs

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?

FredericJacobs commented 3 years ago

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.

arielelkin commented 3 years ago

Thanks!

awasthi027 commented 2 years ago

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))
}
awasthi027 commented 2 years ago

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
}

}