Procházet zdrojové kódy

Use BlockModeWorker.additionalBufferSize to strip auth tag

Marcin Krzyzanowski před 7 roky
rodič
revize
0c6a5e6f27

+ 24 - 22
Sources/CryptoSwift/AES.Cryptors.swift

@@ -74,6 +74,7 @@ extension AES {
     public struct Decryptor: RandomAccessCryptor, Updatable {
     public struct Decryptor: RandomAccessCryptor, Updatable {
         private var worker: BlockModeWorker
         private var worker: BlockModeWorker
         private let padding: Padding
         private let padding: Padding
+        private let additionalBufferSize: Int
         private var accumulated = Array<UInt8>()
         private var accumulated = Array<UInt8>()
         private var processedBytesTotalCount: Int = 0
         private var processedBytesTotalCount: Int = 0
 
 
@@ -88,6 +89,8 @@ extension AES {
             } else {
             } else {
                 worker = try aes.blockMode.worker(blockSize: AES.blockSize, cipherOperation: aes.decrypt)
                 worker = try aes.blockMode.worker(blockSize: AES.blockSize, cipherOperation: aes.decrypt)
             }
             }
+
+            additionalBufferSize = worker.additionalBufferSize
         }
         }
 
 
         public mutating func update(withBytes bytes: ArraySlice<UInt8>, isLast: Bool = false) throws -> Array<UInt8> {
         public mutating func update(withBytes bytes: ArraySlice<UInt8>, isLast: Bool = false) throws -> Array<UInt8> {
@@ -100,29 +103,28 @@ extension AES {
                 accumulated += bytes
                 accumulated += bytes
             }
             }
 
 
+            // If a worker (eg GCM) can combine ciphertext + tag
+            // we need to remove tag from the ciphertext.
+            if !isLast && accumulated.count < worker.blockSize + additionalBufferSize {
+                return []
+            }
+
+            let accumulatedWithoutSuffix: Array<UInt8>
+            if additionalBufferSize > 0 {
+                // FIXME: how slow is that?
+                accumulatedWithoutSuffix = Array(accumulated.prefix(accumulated.count - additionalBufferSize))
+            } else {
+                accumulatedWithoutSuffix = accumulated
+            }
+
             var processedBytesCount = 0
             var processedBytesCount = 0
-            var plaintext = Array<UInt8>(reserveCapacity: accumulated.count)
+            var plaintext = Array<UInt8>(reserveCapacity: accumulatedWithoutSuffix.count)
             // Processing in a block-size manner. It's good for block modes, but bad for stream modes.
             // Processing in a block-size manner. It's good for block modes, but bad for stream modes.
-            for var chunk in accumulated.batched(by: AES.blockSize) {
-                if isLast || (accumulated.count - processedBytesCount) >= AES.blockSize {
+            for var chunk in accumulatedWithoutSuffix.batched(by: worker.blockSize) {
+                if isLast || (accumulatedWithoutSuffix.count - processedBytesCount) >= worker.blockSize {
 
 
                     if isLast, var finalizingWorker = worker as? BlockModeWorkerFinalizing {
                     if isLast, var finalizingWorker = worker as? BlockModeWorkerFinalizing {
-                        // ERR: chunk is limited by block, but I don't want block
-                        // For GCM: Tag is appended at the end and shouldn't be processed
-                        //          so chunk is stripped of the Tag.
-                        /*
-                         Failure scenario:
-                         Encrypt:
-                         Input: 11 bytes
-                         Combined: 11 + 16 (ciphertext + tag)
-
-                         Decrypt in chunks:
-                         1st chunk: 16 bytes = 11 ciphertext + 4 bytes of tag
-                         2nd chunk: 11 bytes = 11 remaining bytes of tag
-
-                         Problem: by the time of 1st chunk, the total length is unknown so can't decide to decrypt only 11 bytes.
-                         */
-                        chunk = try finalizingWorker.willDecryptLast(block: chunk)
+                        chunk = try finalizingWorker.willDecryptLast(block: chunk + accumulated.suffix(additionalBufferSize)) // tag size
                     }
                     }
 
 
                     if !chunk.isEmpty {
                     if !chunk.isEmpty {
@@ -146,7 +148,7 @@ extension AES {
             processedBytesTotalCount += processedBytesCount
             processedBytesTotalCount += processedBytesCount
 
 
             if isLast {
             if isLast {
-                plaintext = padding.remove(from: plaintext, blockSize: AES.blockSize)
+                plaintext = padding.remove(from: plaintext, blockSize: worker.blockSize)
             }
             }
 
 
             return plaintext
             return plaintext
@@ -157,10 +159,10 @@ extension AES {
                 return false
                 return false
             }
             }
 
 
-            worker.counter = UInt(position / AES.blockSize)
+            worker.counter = UInt(position / AES.blockSize) // TODO: worker.blockSize
             self.worker = worker
             self.worker = worker
 
 
-            offset = position % AES.blockSize
+            offset = position % worker.blockSize
 
 
             accumulated = []
             accumulated = []
 
 

+ 1 - 1
Sources/CryptoSwift/BlockMode/BlockMode.swift

@@ -16,7 +16,7 @@
 public typealias CipherOperationOnBlock = (_ block: ArraySlice<UInt8>) -> Array<UInt8>?
 public typealias CipherOperationOnBlock = (_ block: ArraySlice<UInt8>) -> Array<UInt8>?
 
 
 public protocol BlockMode {
 public protocol BlockMode {
-    var options: BlockModeOptions { get }
+    var options: BlockModeOption { get }
     //TODO: doesn't have to be public
     //TODO: doesn't have to be public
     func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock) throws -> BlockModeWorker
     func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock) throws -> BlockModeWorker
 }
 }

+ 9 - 5
Sources/CryptoSwift/BlockMode/BlockModeOptions.swift

@@ -13,15 +13,19 @@
 //  - This notice may not be removed or altered from any source or binary distribution.
 //  - This notice may not be removed or altered from any source or binary distribution.
 //
 //
 
 
-public struct BlockModeOptions: OptionSet {
+public struct BlockModeOption: OptionSet {
     public let rawValue: Int
     public let rawValue: Int
 
 
     public init(rawValue: Int) {
     public init(rawValue: Int) {
         self.rawValue = rawValue
         self.rawValue = rawValue
     }
     }
 
 
-    static let none = BlockModeOptions(rawValue: 1 << 0)
-    static let initializationVectorRequired = BlockModeOptions(rawValue: 1 << 1)
-    static let paddingRequired = BlockModeOptions(rawValue: 1 << 2)
-    static let useEncryptToDecrypt = BlockModeOptions(rawValue: 1 << 3)
+    public init(rawValue: Int, authenticationTagSize: Int) {
+        self.rawValue = rawValue
+    }
+
+    static let none = BlockModeOption(rawValue: 1 << 0)
+    static let initializationVectorRequired = BlockModeOption(rawValue: 1 << 1)
+    static let paddingRequired = BlockModeOption(rawValue: 1 << 2)
+    static let useEncryptToDecrypt = BlockModeOption(rawValue: 1 << 3)
 }
 }

+ 4 - 0
Sources/CryptoSwift/BlockMode/BlockModeWorker.swift

@@ -15,6 +15,10 @@
 
 
 public protocol BlockModeWorker {
 public protocol BlockModeWorker {
     var cipherOperation: CipherOperationOnBlock { get }
     var cipherOperation: CipherOperationOnBlock { get }
+    var blockSize: Int { get }
+    // Additional space needed when incrementally process data
+    // eg. for GCM combined mode
+    var additionalBufferSize: Int { get }
 
 
     mutating func encrypt(block plaintext: ArraySlice<UInt8>) -> Array<UInt8>
     mutating func encrypt(block plaintext: ArraySlice<UInt8>) -> Array<UInt8>
     mutating func decrypt(block ciphertext: ArraySlice<UInt8>) -> Array<UInt8>
     mutating func decrypt(block ciphertext: ArraySlice<UInt8>) -> Array<UInt8>

+ 6 - 3
Sources/CryptoSwift/BlockMode/CBC.swift

@@ -22,7 +22,7 @@ public struct CBC: BlockMode {
         case invalidInitializationVector
         case invalidInitializationVector
     }
     }
 
 
-    public let options: BlockModeOptions = [.initializationVectorRequired, .paddingRequired]
+    public let options: BlockModeOption = [.initializationVectorRequired, .paddingRequired]
     private let iv: Array<UInt8>
     private let iv: Array<UInt8>
 
 
     public init(iv: Array<UInt8>) {
     public init(iv: Array<UInt8>) {
@@ -34,16 +34,19 @@ public struct CBC: BlockMode {
             throw Error.invalidInitializationVector
             throw Error.invalidInitializationVector
         }
         }
 
 
-        return CBCModeWorker(iv: iv.slice, cipherOperation: cipherOperation)
+        return CBCModeWorker(blockSize: blockSize, iv: iv.slice, cipherOperation: cipherOperation)
     }
     }
 }
 }
 
 
 struct CBCModeWorker: BlockModeWorker {
 struct CBCModeWorker: BlockModeWorker {
     let cipherOperation: CipherOperationOnBlock
     let cipherOperation: CipherOperationOnBlock
+    var blockSize: Int
+    let additionalBufferSize: Int = 0
     private let iv: ArraySlice<UInt8>
     private let iv: ArraySlice<UInt8>
     private var prev: ArraySlice<UInt8>?
     private var prev: ArraySlice<UInt8>?
 
 
-    init(iv: ArraySlice<UInt8>, cipherOperation: @escaping CipherOperationOnBlock) {
+    init(blockSize: Int, iv: ArraySlice<UInt8>, cipherOperation: @escaping CipherOperationOnBlock) {
+        self.blockSize = blockSize
         self.iv = iv
         self.iv = iv
         self.cipherOperation = cipherOperation
         self.cipherOperation = cipherOperation
     }
     }

+ 6 - 3
Sources/CryptoSwift/BlockMode/CFB.swift

@@ -22,7 +22,7 @@ public struct CFB: BlockMode {
         case invalidInitializationVector
         case invalidInitializationVector
     }
     }
 
 
-    public let options: BlockModeOptions = [.initializationVectorRequired, .useEncryptToDecrypt]
+    public let options: BlockModeOption = [.initializationVectorRequired, .useEncryptToDecrypt]
     private let iv: Array<UInt8>
     private let iv: Array<UInt8>
 
 
     public init(iv: Array<UInt8>) {
     public init(iv: Array<UInt8>) {
@@ -34,16 +34,19 @@ public struct CFB: BlockMode {
             throw Error.invalidInitializationVector
             throw Error.invalidInitializationVector
         }
         }
 
 
-        return CFBModeWorker(iv: iv.slice, cipherOperation: cipherOperation)
+        return CFBModeWorker(blockSize: blockSize, iv: iv.slice, cipherOperation: cipherOperation)
     }
     }
 }
 }
 
 
 struct CFBModeWorker: BlockModeWorker {
 struct CFBModeWorker: BlockModeWorker {
     let cipherOperation: CipherOperationOnBlock
     let cipherOperation: CipherOperationOnBlock
+    let blockSize: Int
+    let additionalBufferSize: Int = 0
     private let iv: ArraySlice<UInt8>
     private let iv: ArraySlice<UInt8>
     private var prev: ArraySlice<UInt8>?
     private var prev: ArraySlice<UInt8>?
 
 
-    init(iv: ArraySlice<UInt8>, cipherOperation: @escaping CipherOperationOnBlock) {
+    init(blockSize: Int, iv: ArraySlice<UInt8>, cipherOperation: @escaping CipherOperationOnBlock) {
+        self.blockSize = blockSize
         self.iv = iv
         self.iv = iv
         self.cipherOperation = cipherOperation
         self.cipherOperation = cipherOperation
     }
     }

+ 6 - 3
Sources/CryptoSwift/BlockMode/CTR.swift

@@ -22,7 +22,7 @@ public struct CTR: BlockMode {
         case invalidInitializationVector
         case invalidInitializationVector
     }
     }
 
 
-    public let options: BlockModeOptions = [.initializationVectorRequired, .useEncryptToDecrypt]
+    public let options: BlockModeOption = [.initializationVectorRequired, .useEncryptToDecrypt]
     private let iv: Array<UInt8>
     private let iv: Array<UInt8>
 
 
     public init(iv: Array<UInt8>) {
     public init(iv: Array<UInt8>) {
@@ -34,16 +34,19 @@ public struct CTR: BlockMode {
             throw Error.invalidInitializationVector
             throw Error.invalidInitializationVector
         }
         }
 
 
-        return CTRModeWorker(iv: iv.slice, cipherOperation: cipherOperation)
+        return CTRModeWorker(blockSize: blockSize, iv: iv.slice, cipherOperation: cipherOperation)
     }
     }
 }
 }
 
 
 struct CTRModeWorker: RandomAccessBlockModeWorker {
 struct CTRModeWorker: RandomAccessBlockModeWorker {
     let cipherOperation: CipherOperationOnBlock
     let cipherOperation: CipherOperationOnBlock
+    let blockSize: Int
+    let additionalBufferSize: Int = 0
     private let iv: ArraySlice<UInt8>
     private let iv: ArraySlice<UInt8>
     var counter: UInt = 0
     var counter: UInt = 0
 
 
-    init(iv: ArraySlice<UInt8>, cipherOperation: @escaping CipherOperationOnBlock) {
+    init(blockSize: Int, iv: ArraySlice<UInt8>, cipherOperation: @escaping CipherOperationOnBlock) {
+        self.blockSize = blockSize
         self.iv = iv
         self.iv = iv
         self.cipherOperation = cipherOperation
         self.cipherOperation = cipherOperation
     }
     }

+ 6 - 3
Sources/CryptoSwift/BlockMode/ECB.swift

@@ -17,21 +17,24 @@
 //
 //
 
 
 public struct ECB: BlockMode {
 public struct ECB: BlockMode {
-    public let options: BlockModeOptions = .paddingRequired
+    public let options: BlockModeOption = .paddingRequired
 
 
     public init() {
     public init() {
     }
     }
 
 
     public func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock) throws -> BlockModeWorker {
     public func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock) throws -> BlockModeWorker {
-        return ECBModeWorker(cipherOperation: cipherOperation)
+        return ECBModeWorker(blockSize: blockSize, cipherOperation: cipherOperation)
     }
     }
 }
 }
 
 
 struct ECBModeWorker: BlockModeWorker {
 struct ECBModeWorker: BlockModeWorker {
     typealias Element = Array<UInt8>
     typealias Element = Array<UInt8>
     let cipherOperation: CipherOperationOnBlock
     let cipherOperation: CipherOperationOnBlock
+    let blockSize: Int
+    let additionalBufferSize: Int = 0
 
 
-    init(cipherOperation: @escaping CipherOperationOnBlock) {
+    init(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock) {
+        self.blockSize = blockSize
         self.cipherOperation = cipherOperation
         self.cipherOperation = cipherOperation
     }
     }
 
 

+ 16 - 5
Sources/CryptoSwift/BlockMode/GCM.swift

@@ -20,13 +20,22 @@
 
 
 public final class GCM: BlockMode {
 public final class GCM: BlockMode {
     public enum Mode {
     public enum Mode {
-        /// Some applications may need to store the authentication tag and the encrypted message at different locations.
-        case detached
         /// In combined mode, the authentication tag is directly appended to the encrypted message. This is usually what you want.
         /// In combined mode, the authentication tag is directly appended to the encrypted message. This is usually what you want.
         case combined
         case combined
+        /// Some applications may need to store the authentication tag and the encrypted message at different locations.
+        case detached
+
+        var additionalBufferSize: Int {
+            switch self {
+            case .combined:
+                return GCMModeWorker.tagSize
+            case .detached:
+                return 0
+            }
+        }
     }
     }
 
 
-    public let options: BlockModeOptions = [.initializationVectorRequired, .useEncryptToDecrypt]
+    public let options: BlockModeOption = [.initializationVectorRequired, .useEncryptToDecrypt]
 
 
     public enum Error: Swift.Error {
     public enum Error: Swift.Error {
         /// Invalid IV
         /// Invalid IV
@@ -79,12 +88,13 @@ final class GCMModeWorker: BlockModeWorkerFinalizing {
     var didCalculateTag: ((Array<UInt8>) -> Void)?
     var didCalculateTag: ((Array<UInt8>) -> Void)?
 
 
     // 128 bit tag. Other possible tags 4,8,12,13,14,15,16
     // 128 bit tag. Other possible tags 4,8,12,13,14,15,16
-    private static let tagSize = 16
+    fileprivate static let tagSize = 16
     // 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
 
 
     // GCM is designed for 128-bit ciphers like AES (but not really for Blowfish). 64-bit mode is not implemented.
     // GCM is designed for 128-bit ciphers like AES (but not really for Blowfish). 64-bit mode is not implemented.
-    private let blockSize = 16 // 128 bit
+    let blockSize = 16 // 128 bit
+    let additionalBufferSize: Int
     private let iv: ArraySlice<UInt8>
     private let iv: ArraySlice<UInt8>
     private let mode: GCM.Mode
     private let mode: GCM.Mode
     private var counter: UInt128
     private var counter: UInt128
@@ -109,6 +119,7 @@ final class GCMModeWorker: BlockModeWorkerFinalizing {
         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
         h = UInt128(cipherOperation(Array<UInt8>(repeating: 0, count: blockSize).slice)!) // empty block
         h = UInt128(cipherOperation(Array<UInt8>(repeating: 0, count: blockSize).slice)!) // empty block

+ 6 - 3
Sources/CryptoSwift/BlockMode/OFB.swift

@@ -22,7 +22,7 @@ public struct OFB: BlockMode {
         case invalidInitializationVector
         case invalidInitializationVector
     }
     }
 
 
-    public let options: BlockModeOptions = [.initializationVectorRequired, .useEncryptToDecrypt]
+    public let options: BlockModeOption = [.initializationVectorRequired, .useEncryptToDecrypt]
     private let iv: Array<UInt8>
     private let iv: Array<UInt8>
 
 
     public init(iv: Array<UInt8>) {
     public init(iv: Array<UInt8>) {
@@ -34,16 +34,19 @@ public struct OFB: BlockMode {
             throw Error.invalidInitializationVector
             throw Error.invalidInitializationVector
         }
         }
 
 
-        return OFBModeWorker(iv: iv.slice, cipherOperation: cipherOperation)
+        return OFBModeWorker(blockSize: blockSize, iv: iv.slice, cipherOperation: cipherOperation)
     }
     }
 }
 }
 
 
 struct OFBModeWorker: BlockModeWorker {
 struct OFBModeWorker: BlockModeWorker {
     let cipherOperation: CipherOperationOnBlock
     let cipherOperation: CipherOperationOnBlock
+    let blockSize: Int
+    let additionalBufferSize: Int = 0
     private let iv: ArraySlice<UInt8>
     private let iv: ArraySlice<UInt8>
     private var prev: ArraySlice<UInt8>?
     private var prev: ArraySlice<UInt8>?
 
 
-    init(iv: ArraySlice<UInt8>, cipherOperation: @escaping CipherOperationOnBlock) {
+    init(blockSize: Int, iv: ArraySlice<UInt8>, cipherOperation: @escaping CipherOperationOnBlock) {
+        self.blockSize = blockSize
         self.iv = iv
         self.iv = iv
         self.cipherOperation = cipherOperation
         self.cipherOperation = cipherOperation
     }
     }

+ 6 - 3
Sources/CryptoSwift/BlockMode/PCBC.swift

@@ -22,7 +22,7 @@ public struct PCBC: BlockMode {
         case invalidInitializationVector
         case invalidInitializationVector
     }
     }
 
 
-    public let options: BlockModeOptions = [.initializationVectorRequired, .paddingRequired]
+    public let options: BlockModeOption = [.initializationVectorRequired, .paddingRequired]
     private let iv: Array<UInt8>
     private let iv: Array<UInt8>
 
 
     public init(iv: Array<UInt8>) {
     public init(iv: Array<UInt8>) {
@@ -34,16 +34,19 @@ public struct PCBC: BlockMode {
             throw Error.invalidInitializationVector
             throw Error.invalidInitializationVector
         }
         }
 
 
-        return PCBCModeWorker(iv: iv.slice, cipherOperation: cipherOperation)
+        return PCBCModeWorker(blockSize: blockSize, iv: iv.slice, cipherOperation: cipherOperation)
     }
     }
 }
 }
 
 
 struct PCBCModeWorker: BlockModeWorker {
 struct PCBCModeWorker: BlockModeWorker {
     let cipherOperation: CipherOperationOnBlock
     let cipherOperation: CipherOperationOnBlock
+    var blockSize: Int
+    let additionalBufferSize: Int = 0
     private let iv: ArraySlice<UInt8>
     private let iv: ArraySlice<UInt8>
     private var prev: ArraySlice<UInt8>?
     private var prev: ArraySlice<UInt8>?
 
 
-    init(iv: ArraySlice<UInt8>, cipherOperation: @escaping CipherOperationOnBlock) {
+    init(blockSize: Int, iv: ArraySlice<UInt8>, cipherOperation: @escaping CipherOperationOnBlock) {
+        self.blockSize = blockSize
         self.iv = iv
         self.iv = iv
         self.cipherOperation = cipherOperation
         self.cipherOperation = cipherOperation
     }
     }

+ 54 - 27
Tests/Tests/AESTests.swift

@@ -440,33 +440,6 @@ extension AESTests {
         XCTAssertEqual(decrypted, plaintext)
         XCTAssertEqual(decrypted, plaintext)
     }
     }
 
 
-    func testAESGCMTestCaseIrregularCombined() {
-        // echo -n "0123456789010123456789012345" | openssl enc -aes-128-gcm -K feffe9928665731c6d6a8f9467308308 -iv cafebabefacedbaddecaf888 -nopad -nosalt
-        // openssl note: The enc program does not support authenticated encryption modes like CCM and GCM. The utility does not store or retrieve the authentication tag
-        let key = Array<UInt8>(hex: "0xfeffe9928665731c6d6a8f9467308308")
-        //let plaintext = Array<UInt8>(hex: "0xd9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255")
-        let plaintext = "0123456789010123456789012345".bytes
-        let iv = Array<UInt8>(hex: "0xcafebabefacedbaddecaf888")
-
-        let encGCM = GCM(iv: iv, mode: .combined)
-        let aes = try! AES(key: key, blockMode: encGCM, padding: .noPadding)
-        let encrypted = try! aes.encrypt(plaintext)
-
-        XCTAssertNotNil(encGCM.authenticationTag)
-        XCTAssertEqual(Array(encrypted), [UInt8](hex: "0xab831ed4edc644f6d61218431b14c0355138be4b010f630b29be7a2b9793b9fbecc7b44cc86dfd697a50c1c6")) // C
-        XCTAssertEqual(encGCM.authenticationTag, [UInt8](hex: "0x9793b9fbecc7b44cc86dfd697a50c1c6")) // T (128-bit)
-
-        // decrypt
-        func decrypt(_ encrypted: Array<UInt8>) -> Array<UInt8> {
-            let decGCM = GCM(iv: iv, 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 testAESGCMTestCase4() {
     func testAESGCMTestCase4() {
         // Test Case 4
         // Test Case 4
         let key = Array<UInt8>(hex: "0xfeffe9928665731c6d6a8f9467308308")
         let key = Array<UInt8>(hex: "0xfeffe9928665731c6d6a8f9467308308")
@@ -525,6 +498,58 @@ 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 testAESGCMTestCaseIrregularCombined1() {
+        // echo -n "0123456789010123456789012345" | openssl enc -aes-128-gcm -K feffe9928665731c6d6a8f9467308308 -iv cafebabefacedbaddecaf888 -nopad -nosalt
+        // openssl note: The enc program does not support authenticated encryption modes like CCM and GCM. The utility does not store or retrieve the authentication tag
+        let key = Array<UInt8>(hex: "0xfeffe9928665731c6d6a8f9467308308")
+        let plaintext = "0123456789010123456789012345".bytes
+        let iv = Array<UInt8>(hex: "0xcafebabefacedbaddecaf888")
+
+        let encGCM = GCM(iv: iv, mode: .combined)
+        let aes = try! AES(key: key, blockMode: encGCM, padding: .noPadding)
+        let encrypted = try! aes.encrypt(plaintext)
+
+        XCTAssertNotNil(encGCM.authenticationTag)
+        XCTAssertEqual(Array(encrypted), [UInt8](hex: "0xab831ed4edc644f6d61218431b14c0355138be4b010f630b29be7a2b9793b9fbecc7b44cc86dfd697a50c1c6")) // C
+        XCTAssertEqual(encGCM.authenticationTag, [UInt8](hex: "0x9793b9fbecc7b44cc86dfd697a50c1c6")) // T (128-bit)
+
+        // decrypt
+        func decrypt(_ encrypted: Array<UInt8>) -> Array<UInt8> {
+            let decGCM = GCM(iv: iv, 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 testAESGCMTestCaseIrregularCombined2() {
+        // echo -n "0123456789010123456789012345012345678901012345678901234567" | openssl enc -aes-128-gcm -K feffe9928665731c6d6a8f9467308308 -iv cafebabefacedbaddecaf888 -nopad -nosalt
+        // openssl note: The enc program does not support authenticated encryption modes like CCM and GCM. The utility does not store or retrieve the authentication tag
+        let key = Array<UInt8>(hex: "0xfeffe9928665731c6d6a8f9467308308")
+        let plaintext = "0123456789010123456789012345012345678901012345678901234567".bytes
+        let iv = Array<UInt8>(hex: "0xcafebabefacedbaddecaf888")
+
+        let encGCM = GCM(iv: iv, mode: .combined)
+        let aes = try! AES(key: key, blockMode: encGCM, padding: .noPadding)
+        let encrypted = try! aes.encrypt(plaintext)
+
+        XCTAssertNotNil(encGCM.authenticationTag)
+        XCTAssertEqual(Array(encrypted), [UInt8](hex: "0xab831ed4edc644f6d61218431b14c0355138be4b010f630b29be7a2b93ac196f09dc2e10f937aa7e6271564dd117291792f0d6fdf2347ef5b10c86a7f414f0c91a8e59fd2405b850527e")) // C
+        XCTAssertEqual(encGCM.authenticationTag, [UInt8](hex: "0x86a7f414f0c91a8e59fd2405b850527e")) // T (128-bit)
+
+        // decrypt
+        func decrypt(_ encrypted: Array<UInt8>) -> Array<UInt8> {
+            let decGCM = GCM(iv: iv, mode: .combined)
+            let aes = try! AES(key: key, blockMode: decGCM, padding: .noPadding)
+            return try! aes.decrypt(encrypted)
+        }
+
+        let decrypted = decrypt(encrypted)
+        XCTAssertEqual(decrypted, plaintext)
+    }
 }
 }
 
 
 extension AESTests {
 extension AESTests {
@@ -560,6 +585,8 @@ extension AESTests {
             ("testAESGCMTestCase5", testAESGCMTestCase5),
             ("testAESGCMTestCase5", testAESGCMTestCase5),
             ("testAESGCMTestCase6", testAESGCMTestCase6),
             ("testAESGCMTestCase6", testAESGCMTestCase6),
             ("testAESGCMTestCase7", testAESGCMTestCase7),
             ("testAESGCMTestCase7", testAESGCMTestCase7),
+            ("testAESGCMTestCaseIrregularCombined1", testAESGCMTestCaseIrregularCombined1),
+            ("testAESGCMTestCaseIrregularCombined2", testAESGCMTestCaseIrregularCombined2),
         ]
         ]
         return tests
         return tests
     }
     }