Bläddra i källkod

Merge pull request #681 from twierzbik/gcm-tag-length

#672 GCM tagLength support
Marcin Krzyzanowski 6 år sedan
förälder
incheckning
5ed48e640d
2 ändrade filer med 72 tillägg och 20 borttagningar
  1. 24 20
      Sources/CryptoSwift/BlockMode/GCM.swift
  2. 48 0
      Tests/Tests/AESTests.swift

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

@@ -24,15 +24,6 @@ public final class GCM: BlockMode {
         case combined
         case combined
         /// Some applications may need to store the authentication tag and the encrypted message at different locations.
         /// Some applications may need to store the authentication tag and the encrypted message at different locations.
         case detached
         case detached
-
-        var additionalBufferSize: Int {
-            switch self {
-            case .combined:
-                return GCMModeWorker.tagLength
-            case .detached:
-                return 0
-            }
-        }
     }
     }
 
 
     public let options: BlockModeOption = [.initializationVectorRequired, .useEncryptToDecrypt]
     public let options: BlockModeOption = [.initializationVectorRequired, .useEncryptToDecrypt]
@@ -47,6 +38,11 @@ public final class GCM: BlockMode {
     private let iv: Array<UInt8>
     private let iv: Array<UInt8>
     private let additionalAuthenticatedData: Array<UInt8>?
     private let additionalAuthenticatedData: Array<UInt8>?
     private let mode: Mode
     private let mode: Mode
+    
+    /// Length of authentication tag, in bytes.
+    /// For encryption, the value is given as init parameter.
+    /// For decryption, the lenght of given authentication tag is used.
+    private let tagLength: Int
 
 
     // `authenticationTag` nil for encryption, known tag for decryption
     // `authenticationTag` nil for encryption, known tag for decryption
     /// For encryption, the value is set at the end of the encryption.
     /// For encryption, the value is set at the end of the encryption.
@@ -54,15 +50,17 @@ public final class GCM: BlockMode {
     public var authenticationTag: Array<UInt8>?
     public var authenticationTag: Array<UInt8>?
 
 
     // encrypt
     // encrypt
-    public init(iv: Array<UInt8>, additionalAuthenticatedData: Array<UInt8>? = nil, mode: Mode = .detached) {
+    /// Possible tag lengths: 4,8,12,13,14,15,16
+    public init(iv: Array<UInt8>, additionalAuthenticatedData: Array<UInt8>? = nil, tagLength: Int = 16, mode: Mode = .detached) {
         self.iv = iv
         self.iv = iv
         self.additionalAuthenticatedData = additionalAuthenticatedData
         self.additionalAuthenticatedData = additionalAuthenticatedData
         self.mode = mode
         self.mode = mode
+        self.tagLength = tagLength
     }
     }
 
 
     // decrypt
     // decrypt
     public convenience init(iv: Array<UInt8>, authenticationTag: Array<UInt8>, additionalAuthenticatedData: Array<UInt8>? = nil, mode: Mode = .detached) {
     public convenience init(iv: Array<UInt8>, authenticationTag: Array<UInt8>, additionalAuthenticatedData: Array<UInt8>? = nil, mode: Mode = .detached) {
-        self.init(iv: iv, additionalAuthenticatedData: additionalAuthenticatedData, mode: mode)
+        self.init(iv: iv, additionalAuthenticatedData: additionalAuthenticatedData, tagLength: authenticationTag.count, mode: mode)
         self.authenticationTag = authenticationTag
         self.authenticationTag = authenticationTag
     }
     }
 
 
@@ -71,7 +69,7 @@ public final class GCM: BlockMode {
             throw Error.invalidInitializationVector
             throw Error.invalidInitializationVector
         }
         }
 
 
-        let worker = GCMModeWorker(iv: iv.slice, aad: additionalAuthenticatedData?.slice, expectedTag: authenticationTag, mode: mode, cipherOperation: cipherOperation)
+        let worker = GCMModeWorker(iv: iv.slice, aad: additionalAuthenticatedData?.slice, expectedTag: authenticationTag, tagLength: tagLength, mode: mode, cipherOperation: cipherOperation)
         worker.didCalculateTag = { [weak self] tag in
         worker.didCalculateTag = { [weak self] tag in
             self?.authenticationTag = tag
             self?.authenticationTag = tag
         }
         }
@@ -87,8 +85,7 @@ final class GCMModeWorker: BlockModeWorker, FinalizingEncryptModeWorker, Finaliz
     // Callback called when authenticationTag is ready
     // Callback called when authenticationTag is ready
     var didCalculateTag: ((Array<UInt8>) -> Void)?
     var didCalculateTag: ((Array<UInt8>) -> Void)?
 
 
-    // 128 bit tag. Other possible tags 4,8,12,13,14,15,16
-    fileprivate static let tagLength = 16
+    private let tagLength: Int
     // GCM nonce is 96-bits by default. It's the most effective length for the IV
     // GCM nonce is 96-bits by default. It's the most effective length for the IV
     private static let nonceSize = 12
     private static let nonceSize = 12
 
 
@@ -115,15 +112,21 @@ final class GCMModeWorker: BlockModeWorker, FinalizingEncryptModeWorker, Finaliz
         return GF(aad: [UInt8](), h: h, blockSize: blockSize)
         return GF(aad: [UInt8](), h: h, blockSize: blockSize)
     }()
     }()
 
 
-    init(iv: ArraySlice<UInt8>, aad: ArraySlice<UInt8>? = nil, expectedTag: Array<UInt8>? = nil, mode: GCM.Mode, cipherOperation: @escaping CipherOperationOnBlock) {
+    init(iv: ArraySlice<UInt8>, aad: ArraySlice<UInt8>? = nil, expectedTag: Array<UInt8>? = nil, tagLength: Int, mode: GCM.Mode, cipherOperation: @escaping CipherOperationOnBlock) {
         self.cipherOperation = cipherOperation
         self.cipherOperation = cipherOperation
         self.iv = iv
         self.iv = iv
         self.mode = mode
         self.mode = mode
-        self.additionalBufferSize = mode.additionalBufferSize
         self.aad = aad
         self.aad = aad
         self.expectedTag = expectedTag
         self.expectedTag = expectedTag
+        self.tagLength = tagLength
         h = UInt128(cipherOperation(Array<UInt8>(repeating: 0, count: blockSize).slice)!) // empty block
         h = UInt128(cipherOperation(Array<UInt8>(repeating: 0, count: blockSize).slice)!) // empty block
 
 
+        if mode == .combined {
+            self.additionalBufferSize = tagLength
+        } else {
+            self.additionalBufferSize = 0
+        }
+        
         // Assume nonce is 12 bytes long, otherwise initial counter would be calulated from GHASH
         // Assume nonce is 12 bytes long, otherwise initial counter would be calulated from GHASH
         // counter = GF.ghash(aad: [UInt8](), ciphertext: nonce)
         // counter = GF.ghash(aad: [UInt8](), ciphertext: nonce)
         if iv.count == GCMModeWorker.nonceSize {
         if iv.count == GCMModeWorker.nonceSize {
@@ -155,7 +158,7 @@ final class GCMModeWorker: BlockModeWorker, FinalizingEncryptModeWorker, Finaliz
     func finalize(encrypt ciphertext: ArraySlice<UInt8>) throws -> ArraySlice<UInt8> {
     func finalize(encrypt ciphertext: ArraySlice<UInt8>) throws -> ArraySlice<UInt8> {
         // Calculate MAC tag.
         // Calculate MAC tag.
         let ghash = gf.ghashFinish()
         let ghash = gf.ghashFinish()
-        let tag = Array((ghash ^ eky0).bytes.prefix(GCMModeWorker.tagLength))
+        let tag = Array((ghash ^ eky0).bytes.prefix(tagLength))
 
 
         // Notify handler
         // Notify handler
         didCalculateTag?(tag)
         didCalculateTag?(tag)
@@ -186,13 +189,14 @@ final class GCMModeWorker: BlockModeWorker, FinalizingEncryptModeWorker, Finaliz
     // The authenticated decryption operation has five inputs: K, IV , C, A, and T. It has only a single
     // 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
     // output, either the plaintext value P or a special symbol FAIL that indicates that the inputs are not
     // authentic.
     // authentic.
+    @discardableResult
     func willDecryptLast(bytes ciphertext: ArraySlice<UInt8>) throws -> ArraySlice<UInt8> {
     func willDecryptLast(bytes ciphertext: ArraySlice<UInt8>) throws -> ArraySlice<UInt8> {
         // Validate tag
         // Validate tag
         switch mode {
         switch mode {
         case .combined:
         case .combined:
             // overwrite expectedTag property used later for verification
             // overwrite expectedTag property used later for verification
-            self.expectedTag = Array(ciphertext.suffix(GCMModeWorker.tagLength))
-            return ciphertext[ciphertext.startIndex..<ciphertext.endIndex.advanced(by: -Swift.min(GCMModeWorker.tagLength,ciphertext.count))]
+            self.expectedTag = Array(ciphertext.suffix(tagLength))
+            return ciphertext[ciphertext.startIndex..<ciphertext.endIndex.advanced(by: -Swift.min(tagLength,ciphertext.count))]
         case .detached:
         case .detached:
             return ciphertext
             return ciphertext
         }
         }
@@ -201,7 +205,7 @@ final class GCMModeWorker: BlockModeWorker, FinalizingEncryptModeWorker, Finaliz
     func didDecryptLast(bytes plaintext: ArraySlice<UInt8>) throws -> ArraySlice<UInt8> {
     func didDecryptLast(bytes plaintext: ArraySlice<UInt8>) throws -> ArraySlice<UInt8> {
         // Calculate MAC tag.
         // Calculate MAC tag.
         let ghash = gf.ghashFinish()
         let ghash = gf.ghashFinish()
-        let computedTag = Array((ghash ^ eky0).bytes.prefix(GCMModeWorker.tagLength))
+        let computedTag = Array((ghash ^ eky0).bytes.prefix(tagLength))
 
 
         // Validate tag
         // Validate tag
         guard let expectedTag = self.expectedTag, computedTag == expectedTag else {
         guard let expectedTag = self.expectedTag, computedTag == expectedTag else {

+ 48 - 0
Tests/Tests/AESTests.swift

@@ -521,6 +521,52 @@ extension AESTests {
         XCTAssertEqual(Array(encrypted), [UInt8](hex: "")) // C
         XCTAssertEqual(Array(encrypted), [UInt8](hex: "")) // C
         XCTAssertEqual(gcm.authenticationTag, [UInt8](hex: "0xcd33b28ac773f74ba00ed1f312572435")) // T (128-bit)
         XCTAssertEqual(gcm.authenticationTag, [UInt8](hex: "0xcd33b28ac773f74ba00ed1f312572435")) // T (128-bit)
     }
     }
+    
+    func testAESGCMTagLengthDetached() {
+        // Test Case 2
+        let key = Array<UInt8>(hex: "0x00000000000000000000000000000000")
+        let plaintext = Array<UInt8>(hex: "0x00000000000000000000000000000000")
+        let iv = Array<UInt8>(hex: "0x000000000000000000000000")
+        
+        let gcm = GCM(iv: iv, tagLength: 12, mode: .detached)
+        let aes = try! AES(key: key, blockMode: gcm, padding: .noPadding)
+        let encrypted = try! aes.encrypt(plaintext)
+        XCTAssertEqual(Array(encrypted), [UInt8](hex: "0388dace60b6a392f328c2b971b2fe78")) // C
+        XCTAssertEqual(gcm.authenticationTag, [UInt8](hex: "ab6e47d42cec13bdf53a67b2")) // T (96-bit)
+        
+        // decrypt
+        func decrypt(_ encrypted: Array<UInt8>) -> Array<UInt8> {
+            let decGCM = GCM(iv: iv, authenticationTag: gcm.authenticationTag!, mode: .detached)
+            let aes = try! AES(key: key, blockMode: decGCM, padding: .noPadding)
+            return try! aes.decrypt(encrypted)
+        }
+        
+        let decrypted = decrypt(encrypted)
+        XCTAssertEqual(decrypted, plaintext)
+    }
+    
+    func testAESGCMTagLengthCombined() {
+        // Test Case 2
+        let key = Array<UInt8>(hex: "0x00000000000000000000000000000000")
+        let plaintext = Array<UInt8>(hex: "0x00000000000000000000000000000000")
+        let iv = Array<UInt8>(hex: "0x000000000000000000000000")
+        
+        let gcm = GCM(iv: iv, tagLength: 12, mode: .combined)
+        let aes = try! AES(key: key, blockMode: gcm, padding: .noPadding)
+        let encrypted = try! aes.encrypt(plaintext)
+        XCTAssertEqual(Array(encrypted), [UInt8](hex: "0388dace60b6a392f328c2b971b2fe78ab6e47d42cec13bdf53a67b2")) // C
+        XCTAssertEqual(gcm.authenticationTag, [UInt8](hex: "ab6e47d42cec13bdf53a67b2")) // T (96-bit)
+        
+        // decrypt
+        func decrypt(_ encrypted: Array<UInt8>) -> Array<UInt8> {
+            let decGCM = GCM(iv: iv, authenticationTag: gcm.authenticationTag!, mode: .combined)
+            let aes = try! AES(key: key, blockMode: decGCM, padding: .noPadding)
+            return try! aes.decrypt(encrypted)
+        }
+        
+        let decrypted = decrypt(encrypted)
+        XCTAssertEqual(decrypted, plaintext)
+    }
 
 
     func testAESGCMTestCaseIrregularCombined1() {
     func testAESGCMTestCaseIrregularCombined1() {
         // echo -n "0123456789010123456789012345" | openssl enc -aes-128-gcm -K feffe9928665731c6d6a8f9467308308 -iv cafebabefacedbaddecaf888 -nopad -nosalt
         // echo -n "0123456789010123456789012345" | openssl enc -aes-128-gcm -K feffe9928665731c6d6a8f9467308308 -iv cafebabefacedbaddecaf888 -nopad -nosalt
@@ -609,6 +655,8 @@ extension AESTests {
             ("testAESGCMTestCase5", testAESGCMTestCase5),
             ("testAESGCMTestCase5", testAESGCMTestCase5),
             ("testAESGCMTestCase6", testAESGCMTestCase6),
             ("testAESGCMTestCase6", testAESGCMTestCase6),
             ("testAESGCMTestCase7", testAESGCMTestCase7),
             ("testAESGCMTestCase7", testAESGCMTestCase7),
+            ("testAESGCMTestTagLengthDetached", testAESGCMTagLengthDetached),
+            ("testAESGCMTestTagLengthCombined", testAESGCMTagLengthCombined),
             ("testAESGCMTestCaseIrregularCombined1", testAESGCMTestCaseIrregularCombined1),
             ("testAESGCMTestCaseIrregularCombined1", testAESGCMTestCaseIrregularCombined1),
             ("testAESGCMTestCaseIrregularCombined2", testAESGCMTestCaseIrregularCombined2)
             ("testAESGCMTestCaseIrregularCombined2", testAESGCMTestCaseIrregularCombined2)
         ]
         ]