Переглянути джерело

Allow cipher length not equal to multiple of AES block size for certain block modes. #162

Marcin Krzyżanowski 9 роки тому
батько
коміт
5badf947d7

+ 6 - 4
CryptoSwift/AES.swift

@@ -91,7 +91,7 @@ final public class AES {
         self.iv = iv
         self.blockMode = blockMode
         
-        if (blockMode.needIV && iv.count != AES.blockSize) {
+        if (blockMode.options.contains(.InitializationVectorRequired) && iv.count != AES.blockSize) {
             assert(false, "Block size and Initialization Vector must be the same length!")
             throw Error.InvalidInitializationVector
         }
@@ -107,6 +107,7 @@ final public class AES {
     Encrypt message. If padding is necessary, then PKCS7 padding is added and needs to be removed after decryption.
     
     - parameter message: Plaintext data
+    - parameter padding: Optional padding
     
     - returns: Encrypted data
     */
@@ -116,10 +117,11 @@ final public class AES {
         
         if let padding = padding {
             finalBytes = padding.add(bytes, blockSize: AES.blockSize)
-        } else if bytes.count % AES.blockSize != 0 {
+        } else if blockMode.options.contains(.PaddingRequired) && (bytes.count % AES.blockSize != 0) {
             throw Error.BlockSizeExceeded
         }
-        
+
+
         let blocks = finalBytes.chunks(AES.blockSize)
         return try blockMode.encryptBlocks(blocks, iv: self.iv, cipherOperation: encryptBlock)
     }
@@ -170,7 +172,7 @@ final public class AES {
     }
     
     public func decrypt(bytes:[UInt8], padding:Padding? = PKCS7()) throws -> [UInt8] {
-        if bytes.count % AES.blockSize != 0 {
+        if padding == nil && blockMode.options.contains(.PaddingRequired) && (bytes.count % AES.blockSize != 0) {
             throw Error.BlockSizeExceeded
         }
         

+ 24 - 18
CryptoSwift/CipherBlockMode.swift

@@ -13,8 +13,16 @@ enum BlockError: ErrorType {
     case MissingInitializationVector
 }
 
+struct BlockModeOptions: OptionSetType {
+    let rawValue: Int
+
+    static let None = BlockModeOptions(rawValue: 0)
+    static let InitializationVectorRequired = BlockModeOptions(rawValue: 1)
+    static let PaddingRequired = BlockModeOptions(rawValue: 2)
+}
+
 private protocol BlockMode {
-    var needIV:Bool { get }
+    var options: BlockModeOptions { get }
     func encryptBlocks(blocks:[[UInt8]], iv:[UInt8]?, cipherOperation:CipherOperationOnBlock) throws -> [UInt8]
     func decryptBlocks(blocks:[[UInt8]], iv:[UInt8]?, cipherOperation:CipherOperationOnBlock) throws -> [UInt8]
 }
@@ -34,10 +42,8 @@ public enum CipherBlockMode {
             return CTRMode()
         }
     }
-    
-    var needIV: Bool {
-        return mode.needIV
-    }
+
+    var options: BlockModeOptions { return mode.options }
     
     /**
     Process input blocks with given block cipher mode. With fallback to plain mode.
@@ -74,7 +80,7 @@ public enum CipherBlockMode {
 Cipher-block chaining (CBC)
 */
 private struct CBCMode: BlockMode {
-    let needIV = true
+    let options: BlockModeOptions = [.InitializationVectorRequired, .PaddingRequired]
     
     func encryptBlocks(blocks:[[UInt8]], iv:[UInt8]?, cipherOperation:CipherOperationOnBlock) throws -> [UInt8] {
         precondition(blocks.count > 0)
@@ -86,7 +92,7 @@ private struct CBCMode: BlockMode {
         out.reserveCapacity(blocks.count * blocks[blocks.startIndex].count)
         var prevCiphertext = iv // for the first time prevCiphertext = iv
         for plaintext in blocks {
-            if let encrypted = cipherOperation(block: xor(prevCiphertext, b: plaintext)) {
+            if let encrypted = cipherOperation(block: xor(prevCiphertext, plaintext)) {
                 out.appendContentsOf(encrypted)
                 prevCiphertext = encrypted
             }
@@ -105,7 +111,7 @@ private struct CBCMode: BlockMode {
         var prevCiphertext = iv // for the first time prevCiphertext = iv
         for ciphertext in blocks {
             if let decrypted = cipherOperation(block: ciphertext) { // decrypt
-                out.appendContentsOf(xor(prevCiphertext, b: decrypted)) //FIXME: b:
+                out.appendContentsOf(xor(prevCiphertext, decrypted)) //FIXME: b:
             }
             prevCiphertext = ciphertext
         }
@@ -118,8 +124,8 @@ private struct CBCMode: BlockMode {
 Cipher feedback (CFB)
 */
 private struct CFBMode: BlockMode {
-    let needIV = true
-    
+    let options: BlockModeOptions = [.InitializationVectorRequired]
+
     func encryptBlocks(blocks:[[UInt8]], iv:[UInt8]?, cipherOperation:CipherOperationOnBlock) throws -> [UInt8] {
         guard let iv = iv else {
             throw BlockError.MissingInitializationVector
@@ -131,7 +137,7 @@ private struct CFBMode: BlockMode {
         var lastCiphertext = iv
         for plaintext in blocks {
             if let ciphertext = cipherOperation(block: lastCiphertext) {
-                lastCiphertext = xor(plaintext,b: ciphertext)
+                lastCiphertext = xor(plaintext, ciphertext)
                 out.appendContentsOf(lastCiphertext)
             }
         }
@@ -149,7 +155,7 @@ private struct CFBMode: BlockMode {
         var lastCiphertext = iv
         for ciphertext in blocks {
             if let decrypted = cipherOperation(block: lastCiphertext) {
-                out.appendContentsOf(xor(decrypted, b: ciphertext))
+                out.appendContentsOf(xor(decrypted, ciphertext))
             }
             lastCiphertext = ciphertext
         }
@@ -163,8 +169,8 @@ private struct CFBMode: BlockMode {
 Electronic codebook (ECB)
 */
 private struct ECBMode: BlockMode {
-    let needIV = false
-    
+    let options: BlockModeOptions = [.PaddingRequired]
+
     func encryptBlocks(blocks:[[UInt8]], iv:[UInt8]?, cipherOperation:CipherOperationOnBlock) -> [UInt8] {
         var out:[UInt8] = [UInt8]()
         out.reserveCapacity(blocks.count * blocks[blocks.startIndex].count)
@@ -185,8 +191,8 @@ private struct ECBMode: BlockMode {
 Counter (CTR)
 */
 private struct CTRMode: BlockMode {
-    let needIV = true
-    
+    let options = BlockModeOptions.InitializationVectorRequired
+
     private func buildNonce(iv: [UInt8], counter: UInt) -> [UInt8] {
         let noncePartLen = AES.blockSize / 2
         let noncePrefix = Array(iv[0..<noncePartLen])
@@ -210,7 +216,7 @@ private struct CTRMode: BlockMode {
         for plaintext in blocks {
             let nonce = buildNonce(iv, counter: counter++)
             if let encrypted = cipherOperation(block: nonce) {
-                out.appendContentsOf(xor(plaintext, b: encrypted))
+                out.appendContentsOf(xor(plaintext, encrypted))
             }
         }
         return out
@@ -227,7 +233,7 @@ private struct CTRMode: BlockMode {
         for plaintext in blocks {
             let nonce = buildNonce(iv, counter: counter++)
             if let encrypted = cipherOperation(block: nonce) {
-                out.appendContentsOf(xor(encrypted, b: plaintext))
+                out.appendContentsOf(xor(encrypted, plaintext))
             }
         }
         return out

+ 2 - 2
CryptoSwift/Utils.swift

@@ -67,8 +67,8 @@ func toUInt64Array(slice: ArraySlice<UInt8>) -> Array<UInt64> {
     return result
 }
 
-func xor(a: [UInt8], b:[UInt8]) -> [UInt8] {
-    var xored = [UInt8](count: a.count, repeatedValue: 0)
+func xor(a: [UInt8], _ b:[UInt8]) -> [UInt8] {
+    var xored = [UInt8](count: min(a.count, b.count), repeatedValue: 0)
     for i in 0..<xored.count {
         xored[i] = a[i] ^ b[i]
     }

+ 16 - 2
CryptoSwiftTests/AESTests.swift

@@ -87,7 +87,7 @@ final class AESTests: XCTestCase {
     }
     
     // https://github.com/krzyzanowskim/CryptoSwift/issues/142
-    func testAES_encrypt_cfb2() {
+    func testAES_encrypt_cfb_long() {
         let key: [UInt8] = [56, 118, 37, 51, 125, 78, 103, 107, 119, 40, 74, 88, 117, 112, 123, 75, 122, 89, 72, 36, 46, 91, 106, 60, 54, 110, 34, 126, 69, 126, 61, 87]
         let iv: [UInt8] = [69, 122, 99, 87, 83, 112, 110, 65, 54, 109, 107, 89, 73, 122, 74, 49]
         let plaintext: [UInt8] = [123, 10, 32, 32, 34, 67, 111, 110, 102, 105, 114, 109, 34, 32, 58, 32, 34, 116, 101, 115, 116, 105, 110, 103, 34, 44, 10, 32, 32, 34, 70, 105, 114, 115, 116, 78, 97, 109, 101, 34, 32, 58, 32, 34, 84, 101, 115, 116, 34, 44, 10, 32, 32, 34, 69, 109, 97, 105, 108, 34, 32, 58, 32, 34, 116, 101, 115, 116, 64, 116, 101, 115, 116, 46, 99, 111, 109, 34, 44, 10, 32, 32, 34, 76, 97, 115, 116, 78, 97, 109, 101, 34, 32, 58, 32, 34, 84, 101, 115, 116, 101, 114, 34, 44, 10, 32, 32, 34, 80, 97, 115, 115, 119, 111, 114, 100, 34, 32, 58, 32, 34, 116, 101, 115, 116, 105, 110, 103, 34, 44, 10, 32, 32, 34, 85, 115, 101, 114, 110, 97, 109, 101, 34, 32, 58, 32, 34, 84, 101, 115, 116, 34, 10, 125]
@@ -109,7 +109,21 @@ final class AESTests: XCTestCase {
         let decrypted = try! aes.decrypt(encrypted, padding: nil)
         XCTAssertEqual(decrypted, plaintext, "decryption failed")
     }
-    
+
+    func testAES_encrypt_ctr_irregular_length() {
+        let key:[UInt8] = [0x2b,0x7e,0x15,0x16,0x28,0xae,0xd2,0xa6,0xab,0xf7,0x15,0x88,0x09,0xcf,0x4f,0x3c];
+        let iv:[UInt8] = [0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff]
+        let plaintext:[UInt8] = [0x6b,0xc1,0xbe,0xe2,0x2e,0x40,0x9f,0x96,0xe9,0x3d,0x7e,0x11,0x73,0x93,0x17,0x2a,0x01]
+        let expected:[UInt8] = [103, 238, 5, 84, 116, 153, 248, 188, 240, 195, 131, 36, 232, 96, 92, 40, 174]
+
+        let aes = try! AES(key: key, iv:iv, blockMode: .CTR)
+        XCTAssertTrue(aes.blockMode == .CTR, "Invalid block mode")
+        let encrypted = try! aes.encrypt(plaintext, padding: nil)
+        XCTAssertEqual(encrypted, expected, "encryption failed")
+        let decrypted = try! aes.decrypt(encrypted, padding: nil)
+        XCTAssertEqual(decrypted, plaintext, "decryption failed")
+    }
+
     func testAES_encrypt_performance() {
         let key:[UInt8] = [0x2b,0x7e,0x15,0x16,0x28,0xae,0xd2,0xa6,0xab,0xf7,0x15,0x88,0x09,0xcf,0x4f,0x3c];
         let iv:[UInt8] = [0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F]