Browse Source

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

Marcin Krzyżanowski 10 years ago
parent
commit
5badf947d7

+ 6 - 4
CryptoSwift/AES.swift

@@ -91,7 +91,7 @@ final public class AES {
         self.iv = iv
         self.iv = iv
         self.blockMode = blockMode
         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!")
             assert(false, "Block size and Initialization Vector must be the same length!")
             throw Error.InvalidInitializationVector
             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.
     Encrypt message. If padding is necessary, then PKCS7 padding is added and needs to be removed after decryption.
     
     
     - parameter message: Plaintext data
     - parameter message: Plaintext data
+    - parameter padding: Optional padding
     
     
     - returns: Encrypted data
     - returns: Encrypted data
     */
     */
@@ -116,10 +117,11 @@ final public class AES {
         
         
         if let padding = padding {
         if let padding = padding {
             finalBytes = padding.add(bytes, blockSize: AES.blockSize)
             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
             throw Error.BlockSizeExceeded
         }
         }
-        
+
+
         let blocks = finalBytes.chunks(AES.blockSize)
         let blocks = finalBytes.chunks(AES.blockSize)
         return try blockMode.encryptBlocks(blocks, iv: self.iv, cipherOperation: encryptBlock)
         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] {
     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
             throw Error.BlockSizeExceeded
         }
         }
         
         

+ 24 - 18
CryptoSwift/CipherBlockMode.swift

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

+ 2 - 2
CryptoSwift/Utils.swift

@@ -67,8 +67,8 @@ func toUInt64Array(slice: ArraySlice<UInt8>) -> Array<UInt64> {
     return result
     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 {
     for i in 0..<xored.count {
         xored[i] = a[i] ^ b[i]
         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
     // 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 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 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]
         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)
         let decrypted = try! aes.decrypt(encrypted, padding: nil)
         XCTAssertEqual(decrypted, plaintext, "decryption failed")
         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() {
     func testAES_encrypt_performance() {
         let key:[UInt8] = [0x2b,0x7e,0x15,0x16,0x28,0xae,0xd2,0xa6,0xab,0xf7,0x15,0x88,0x09,0xcf,0x4f,0x3c];
         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]
         let iv:[UInt8] = [0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F]