Ver código fonte

GCM decryption

Marcin Krzyżanowski 7 anos atrás
pai
commit
dd81d01c04

+ 2 - 2
Sources/CryptoSwift/AES.Cryptors.swift

@@ -59,7 +59,7 @@ extension AES {
             processedBytesTotalCount += processedBytes
 
             if var finalizingWorker = worker as? BlockModeWorkerFinalizing, isLast == true {
-                encrypted = finalizingWorker.finalize(encrypt: encrypted.slice)
+                encrypted = try finalizingWorker.finalize(encrypt: encrypted.slice)
             }
 
             return encrypted
@@ -122,7 +122,7 @@ extension AES {
             }
 
             if var finalizingWorker = worker as? BlockModeWorkerFinalizing, isLast == true {
-                plaintext = finalizingWorker.finalize(decrypt: plaintext.slice)
+                plaintext = try finalizingWorker.finalize(decrypt: plaintext.slice)
             }
 
             return plaintext

+ 2 - 2
Sources/CryptoSwift/BlockMode/BlockModeWorker.swift

@@ -23,6 +23,6 @@ public protocol BlockModeWorker {
 // TODO: remove and merge with BlockModeWorker
 public protocol BlockModeWorkerFinalizing: BlockModeWorker {
     // Any final calculations, eg. calculate tag
-    mutating func finalize(encrypt ciphertext: ArraySlice<UInt8>) -> Array<UInt8>
-    mutating func finalize(decrypt plaintext: ArraySlice<UInt8>) -> Array<UInt8>
+    mutating func finalize(encrypt ciphertext: ArraySlice<UInt8>) throws -> Array<UInt8>
+    mutating func finalize(decrypt plaintext: ArraySlice<UInt8>) throws -> Array<UInt8>
 }

+ 45 - 24
Sources/CryptoSwift/BlockMode/GCM.swift

@@ -18,16 +18,22 @@
 //  ref: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.694.695&rep=rep1&type=pdf
 //
 
-public class GCM: BlockMode {
-    public let options: BlockModeOptions = .initializationVectorRequired
+public final class GCM: BlockMode {
+    public let options: BlockModeOptions = [.initializationVectorRequired, .useEncryptToDecrypt]
 
     public enum Error: Swift.Error {
         /// Invalid IV
         case invalidInitializationVector
+        /// Special symbol FAIL that indicates that the inputs are not authentic.
+        case fail
     }
 
     private let iv: Array<UInt8>
     private let additionalAuthenticatedData: Array<UInt8>?
+
+    // `authenticationTag` nil for encryption, known tag for decryption
+    /// For encryption, the value is set at the end of the encryption.
+    /// For decryption, this is a known Tag to validate against.
     public var authenticationTag: Array<UInt8>?
 
     // encrypt
@@ -47,7 +53,7 @@ public class GCM: BlockMode {
             throw Error.invalidInitializationVector
         }
 
-        var worker = GCMModeWorker(iv: iv.slice, aad: additionalAuthenticatedData?.slice, cipherOperation: cipherOperation)
+        let worker = GCMModeWorker(iv: iv.slice, aad: additionalAuthenticatedData?.slice, expectedTag: authenticationTag, cipherOperation: cipherOperation)
         worker.didCalculateTag = { tag in
             self.authenticationTag = tag
         }
@@ -55,7 +61,11 @@ public class GCM: BlockMode {
     }
 }
 
-struct GCMModeWorker: BlockModeWorkerFinalizing {
+
+// MARK: - Worker
+
+final class GCMModeWorker: BlockModeWorkerFinalizing {
+
     let cipherOperation: CipherOperationOnBlock
 
     // Callback called when authenticationTag is ready
@@ -75,6 +85,8 @@ struct GCMModeWorker: BlockModeWorkerFinalizing {
 
     // Additional authenticated data
     private let aad: ArraySlice<UInt8>?
+    // Known Tag used to validate during decryption
+    private let expectedTag: Array<UInt8>?
 
     // Note: need new worker to reset instance
     // Use empty aad if not specified. AAD is optional.
@@ -85,10 +97,11 @@ struct GCMModeWorker: BlockModeWorkerFinalizing {
         return GF(aad: [UInt8](), h: h, blockSize: blockSize)
     }()
 
-    init(iv: ArraySlice<UInt8>, aad: ArraySlice<UInt8>?, cipherOperation: @escaping CipherOperationOnBlock) {
+    init(iv: ArraySlice<UInt8>, aad: ArraySlice<UInt8>? = nil, expectedTag: Array<UInt8>? = nil, cipherOperation: @escaping CipherOperationOnBlock) {
         self.cipherOperation = cipherOperation
         self.iv = iv
         self.aad = aad
+        self.expectedTag = expectedTag
         self.h = UInt128(cipherOperation(Array<UInt8>(repeating: 0, count: blockSize).slice)!) // empty block
         
         // Assume nonce is 12 bytes long, otherwise initial counter would be calulated from GHASH
@@ -103,7 +116,7 @@ struct GCMModeWorker: BlockModeWorkerFinalizing {
         eky0 = UInt128(cipherOperation(counter.bytes.slice)!)
     }
 
-    mutating func encrypt(_ plaintext: ArraySlice<UInt8>) -> Array<UInt8> {
+    func encrypt(_ plaintext: ArraySlice<UInt8>) -> Array<UInt8> {
         counter = incrementCounter(counter)
 
         guard let ekyN = cipherOperation(counter.bytes.slice) else {
@@ -119,44 +132,51 @@ struct GCMModeWorker: BlockModeWorkerFinalizing {
         return Array(ciphertext)
     }
 
-    mutating func decrypt(_ ciphertext: ArraySlice<UInt8>) -> Array<UInt8> {
+    func decrypt(_ ciphertext: ArraySlice<UInt8>) -> Array<UInt8> {
         counter = incrementCounter(counter)
 
-        let paddedCiphertext = addPadding(Array(ciphertext), blockSize: blockSize)
-        let paddedAuthData = addPadding([UInt8](), blockSize: blockSize)
-        let ghash = GF.ghash(h: h, aad: paddedAuthData, ciphertext: paddedCiphertext, blockSize: blockSize)
-        let computedT = (ghash ^ eky0).bytes.prefix(GCMModeWorker.tagSize)
-
-        // TODO: validate Tag(T)
-        //        if !GCM.tsCompare(d1: computedT, d2: givenT) {
-        //            throw GCMError.authTagValidation
-        //        }
+        // update ghash incrementally
+        gf.ghashUpdate(block: Array(ciphertext))
 
-        guard let ek1 = cipherOperation(counter.bytes.slice) else {
+        guard let ekN = cipherOperation(counter.bytes.slice) else {
             return Array(ciphertext)
         }
 
         // ciphertext block ^ ek1
-        let plaintext = xor(ciphertext, ek1) as [UInt8]
+        let plaintext = xor(ciphertext, ekN) as Array<UInt8>
         return plaintext
     }
 
-    mutating func finalize(encrypt ciphertext: ArraySlice<UInt8>) -> Array<UInt8> {
-        // Calculate MAC tag for a given ciphertext.
+    func finalize(encrypt ciphertext: ArraySlice<UInt8>) throws -> Array<UInt8> {
+        // Calculate MAC tag.
         let ghash = gf.ghashFinish()
         let tag = Array((ghash ^ eky0).bytes.prefix(GCMModeWorker.tagSize))
 
         // Notify handler
         didCalculateTag?(tag)
-        // Append Tag at the end (arguable, but popular)
-        return Array(ciphertext) + tag
+        
+        return Array(ciphertext)
     }
 
-    func finalize(decrypt plaintext: ArraySlice<UInt8>) -> Array<UInt8> {
-        return Array(plaintext)
+    // The authenticated decryption operation has five inputs: K, IV , C, A, and T. It has only a single
+    // output, either the plaintext value P or a special symbol FAIL that indicates that the inputs are not
+    // authentic.
+    func finalize(decrypt plaintext: ArraySlice<UInt8>) throws -> Array<UInt8> {
+        // Calculate MAC tag.
+        let ghash = gf.ghashFinish()
+        let computedTag = Array((ghash ^ eky0).bytes.prefix(GCMModeWorker.tagSize))
+
+        // Validate tag
+        if let expectedTag = self.expectedTag, computedTag == expectedTag {
+            return Array(plaintext)
+        }
+
+        throw GCM.Error.fail
     }
 }
 
+// MARK: - Local utils
+
 private func makeCounter(nonce: Array<UInt8>) -> UInt128 {
     return UInt128(nonce + [0, 0, 0, 1])
 }
@@ -190,6 +210,7 @@ private func addPadding(_ bytes: Array<UInt8>, blockSize: Int) -> Array<UInt8> {
 
 // MARK: - GF
 
+/// The Field GF(2^128)
 private final class GF {
     static let r = UInt128(a: 0xE100000000000000, b: 0)
 

+ 21 - 10
Tests/Tests/AESTests.swift

@@ -373,7 +373,7 @@ extension AESTests {
         let gcm = GCM(iv: iv)
         let aes = try! AES(key: key, blockMode: gcm, padding: .noPadding)
         let encrypted = try! aes.encrypt([UInt8]())
-        XCTAssertEqual(Array(encrypted.prefix(encrypted.endIndex - 16)), [UInt8](hex: "")) // C
+        XCTAssertEqual(Array(encrypted), [UInt8](hex: "")) // C
         XCTAssertEqual(gcm.authenticationTag, [UInt8](hex: "58e2fccefa7e3061367f1d57a4e7455a")) // T (128-bit)
     }
 
@@ -386,7 +386,7 @@ extension AESTests {
         let gcm = GCM(iv: iv)
         let aes = try! AES(key: key, blockMode: gcm, padding: .noPadding)
         let encrypted = try! aes.encrypt(plaintext)
-        XCTAssertEqual(Array(encrypted.prefix(encrypted.endIndex - 16)), [UInt8](hex: "0388dace60b6a392f328c2b971b2fe78")) // C
+        XCTAssertEqual(Array(encrypted), [UInt8](hex: "0388dace60b6a392f328c2b971b2fe78")) // C
         XCTAssertEqual(gcm.authenticationTag, [UInt8](hex: "ab6e47d42cec13bdf53a67b21257bddf")) // T (128-bit)
     }
 
@@ -396,12 +396,23 @@ extension AESTests {
         let plaintext = Array<UInt8>(hex: "0xd9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255")
         let iv = Array<UInt8>(hex: "0xcafebabefacedbaddecaf888")
 
-        let gcm = GCM(iv: iv)
-        let aes = try! AES(key: key, blockMode: gcm, padding: .noPadding)
+        let encGCM = GCM(iv: iv)
+        let aes = try! AES(key: key, blockMode: encGCM, padding: .noPadding)
         let encrypted = try! aes.encrypt(plaintext)
 
-        XCTAssertEqual(Array(encrypted.prefix(encrypted.endIndex - 16)), [UInt8](hex: "0x42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f5985")) // C
-        XCTAssertEqual(gcm.authenticationTag, [UInt8](hex: "0x4d5c2af327cd64a62cf35abd2ba6fab4")) // T (128-bit)
+        XCTAssertNotNil(encGCM.authenticationTag)
+        XCTAssertEqual(Array(encrypted), [UInt8](hex: "0x42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f5985")) // C
+        XCTAssertEqual(encGCM.authenticationTag, [UInt8](hex: "0x4d5c2af327cd64a62cf35abd2ba6fab4")) // T (128-bit)
+
+        // decrypt
+        func decrypt(_ encrypted: Array<UInt8>, tag: Array<UInt8>) -> Array<UInt8> {
+            let decGCM = GCM(iv: iv, authenticationTag: tag)
+            let aes = try! AES(key: key, blockMode: decGCM, padding: .noPadding)
+            return try! aes.decrypt(encrypted)
+        }
+
+        let decrypted = decrypt(encrypted, tag: encGCM.authenticationTag!)
+        XCTAssertEqual(decrypted, plaintext)
     }
 
     func testAESGCMTestCase4() {
@@ -415,7 +426,7 @@ extension AESTests {
         let aes = try! AES(key: key, blockMode: gcm, padding: .noPadding)
         let encrypted = try! aes.encrypt(plaintext)
 
-        XCTAssertEqual(Array(encrypted.prefix(encrypted.endIndex - 16)), [UInt8](hex: "0x42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091")) // C
+        XCTAssertEqual(Array(encrypted), [UInt8](hex: "0x42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091")) // C
         XCTAssertEqual(gcm.authenticationTag, [UInt8](hex: "0x5bc94fbc3221a5db94fae95ae7121a47")) // T (128-bit)
     }
 
@@ -430,7 +441,7 @@ extension AESTests {
         let aes = try! AES(key: key, blockMode: gcm, padding: .noPadding)
         let encrypted = try! aes.encrypt(plaintext)
 
-        XCTAssertEqual(Array(encrypted.prefix(encrypted.endIndex - 16)), [UInt8](hex: "0x61353b4c2806934a777ff51fa22a4755699b2a714fcdc6f83766e5f97b6c742373806900e49f24b22b097544d4896b424989b5e1ebac0f07c23f4598")) // C
+        XCTAssertEqual(Array(encrypted), [UInt8](hex: "0x61353b4c2806934a777ff51fa22a4755699b2a714fcdc6f83766e5f97b6c742373806900e49f24b22b097544d4896b424989b5e1ebac0f07c23f4598")) // C
         XCTAssertEqual(gcm.authenticationTag, [UInt8](hex: "0x3612d2e79e3b0785561be14aaca2fccb")) // T (128-bit)
     }
 
@@ -445,7 +456,7 @@ extension AESTests {
         let aes = try! AES(key: key, blockMode: gcm, padding: .noPadding)
         let encrypted = try! aes.encrypt(plaintext)
 
-        XCTAssertEqual(Array(encrypted.prefix(encrypted.endIndex - 16)), [UInt8](hex: "0x8ce24998625615b603a033aca13fb894be9112a5c3a211a8ba262a3cca7e2ca701e4a9a4fba43c90ccdcb281d48c7c6fd62875d2aca417034c34aee5")) // C
+        XCTAssertEqual(Array(encrypted), [UInt8](hex: "0x8ce24998625615b603a033aca13fb894be9112a5c3a211a8ba262a3cca7e2ca701e4a9a4fba43c90ccdcb281d48c7c6fd62875d2aca417034c34aee5")) // C
         XCTAssertEqual(gcm.authenticationTag, [UInt8](hex: "0x619cc5aefffe0bfa462af43c1699d050")) // T (128-bit)
     }
 
@@ -459,7 +470,7 @@ extension AESTests {
         let aes = try! AES(key: key, blockMode: gcm, padding: .noPadding)
         let encrypted = try! aes.encrypt(plaintext)
 
-        XCTAssertEqual(Array(encrypted.prefix(encrypted.endIndex - 16)), [UInt8](hex: "")) // C
+        XCTAssertEqual(Array(encrypted), [UInt8](hex: "")) // C
         XCTAssertEqual(gcm.authenticationTag, [UInt8](hex: "0xcd33b28ac773f74ba00ed1f312572435")) // T (128-bit)
     }
 }