Browse Source

Cipher-block chaining (CBC) for AES

Marcin Krzyżanowski 10 năm trước cách đây
mục cha
commit
8fb22578a0

+ 4 - 0
CryptoSwift.xcodeproj/project.pbxproj

@@ -33,6 +33,7 @@
 		759D481119B517BC005FF7FC /* BitExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 759D481019B517BC005FF7FC /* BitExtension.swift */; };
 		75A74B271A1FF6B2004419F1 /* AES.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75A74B261A1FF6B2004419F1 /* AES.swift */; };
 		75B601EB197D6A6C0009B53D /* CryptoSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 754BE45519693E190098E6F3 /* CryptoSwift.framework */; };
+		75BC3AE31A4E412000ADF343 /* CipherBlockMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75BC3AE21A4E412000ADF343 /* CipherBlockMode.swift */; };
 		75D94E2419B60C08007CB2A4 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75D94E2319B60C08007CB2A4 /* Operators.swift */; };
 		75D94E2619B60C4F007CB2A4 /* UInt32Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75D94E2519B60C4F007CB2A4 /* UInt32Extension.swift */; };
 		75D94E2819B60DDE007CB2A4 /* UInt64Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75D94E2719B60DDE007CB2A4 /* UInt64Extension.swift */; };
@@ -133,6 +134,7 @@
 		7599C9C5199EA28700A3988B /* StringExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = "<group>"; };
 		759D481019B517BC005FF7FC /* BitExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitExtension.swift; sourceTree = "<group>"; };
 		75A74B261A1FF6B2004419F1 /* AES.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AES.swift; sourceTree = "<group>"; };
+		75BC3AE21A4E412000ADF343 /* CipherBlockMode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherBlockMode.swift; sourceTree = "<group>"; };
 		75D94E2319B60C08007CB2A4 /* Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = "<group>"; };
 		75D94E2519B60C4F007CB2A4 /* UInt32Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UInt32Extension.swift; sourceTree = "<group>"; };
 		75D94E2719B60DDE007CB2A4 /* UInt64Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UInt64Extension.swift; sourceTree = "<group>"; };
@@ -193,6 +195,7 @@
 				754BE45A19693E190098E6F3 /* CryptoSwift.h */,
 				7552614D1993051E000D2B20 /* Hash.swift */,
 				7563B2E719B14D4300B152CD /* Cipher.swift */,
+				75BC3AE21A4E412000ADF343 /* CipherBlockMode.swift */,
 				755111E719B7B7DF00C2AD86 /* Authenticator.swift */,
 				754DD76D19A149AF00E52288 /* CryptoHashBase.swift */,
 				750A545F1992D2680017DA75 /* MD5.swift */,
@@ -365,6 +368,7 @@
 				75D94E2819B60DDE007CB2A4 /* UInt64Extension.swift in Sources */,
 				751C5C3D19B26B000094C75D /* Poly1305.swift in Sources */,
 				7552614E1993051E000D2B20 /* Hash.swift in Sources */,
+				75BC3AE31A4E412000ADF343 /* CipherBlockMode.swift in Sources */,
 				7599C9C6199EA28700A3988B /* StringExtension.swift in Sources */,
 				7563B2E819B14D4300B152CD /* Cipher.swift in Sources */,
 				752E087B199FF27C005B0EA0 /* SHA1.swift in Sources */,

+ 34 - 32
CryptoSwift/AES.swift

@@ -8,26 +8,30 @@
 
 import Foundation
 
-public class AES {
+private enum AESVariant:Int {
+    case unknown, aes128, aes192, aes256
     
-    enum Variant:Int {
-        case unknown, aes128, aes192, aes256
-
-        var Nk:Int { // Nk words
-            return [4,6,8][self.rawValue - 1]
-        }
-        
-        var Nb:Int { // Nb words
-            return 4
-        }
-        
-        var Nr:Int { // Nr
-            return Nk + 6
-        }
+    var Nk:Int { // Nk words
+        return [4,6,8][self.rawValue - 1]
     }
     
-    let variant:Variant
-    let key:NSData
+    var Nb:Int { // Nb words
+        return 4
+    }
+    
+    var Nr:Int { // Nr
+        return Nk + 6
+    }
+}
+
+public class AES {
+    
+    public let blockSizeBytes = 128 / 8
+    public let blockMode:CipherBlockMode
+    
+    private let variant:AESVariant
+    private let key:NSData
+    private let iv:NSData?
     
     private let sBox:[Byte] = [
         0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, // 0
@@ -90,8 +94,14 @@ public class AES {
                                0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
                                0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d]
     
-    public init?(key:NSData) {
+    public convenience init?(key:NSData) {
+        self.init(key:key, iv:nil)
+    }
+    
+    public init?(key:NSData, iv:NSData?) {
         self.key = key
+        self.iv = iv
+        self.blockMode = .CBC
         switch (key.length * 8) {
         case 128:
             self.variant = .aes128
@@ -107,26 +117,18 @@ public class AES {
             return nil;
         }
     }
-    
-    public func blockSizeBytes() -> Int {
-        return 128 / 8
-    }
-        
+
+    // if "iv" is given then CBC mode is used by default
     public func encrypt(message:NSData) -> NSData? {
-        if (message.length % blockSizeBytes() != 0) {
+        if (message.length % blockSizeBytes != 0) {
             // 128 bit block exceeded
             println("AES 128-bit block exceeded!")
             return nil
         }
         
-        var out:[Byte] = [Byte]()
-        for block in message.bytes().chunks(blockSizeBytes()) {
-            for byte in encryptBlock(block) {
-                out.append(byte)
-            }
-        }
-
-        return NSData.withBytes(out)
+        let blocks = message.bytes().chunks(blockSizeBytes)
+        let out = blockMode.processBlocks(blocks, iv: self.iv?.bytes(), cipher: encryptBlock)
+        return out == nil ? nil : NSData.withBytes(out!)
     }
     
     private func encryptBlock(block:[Byte]) -> [Byte] {

+ 5 - 5
CryptoSwift/Cipher.swift

@@ -10,15 +10,15 @@ import Foundation
 
 public enum Cipher {
     case ChaCha20(key: NSData, iv: NSData)
-    case AES(key: NSData)
+    case AES(key: NSData, iv: NSData)
     
     public func encrypt(message: NSData) -> NSData? {
         switch (self) {
             case .ChaCha20(let key, let iv):
                 var chacha = CryptoSwift.ChaCha20(key: key, iv: iv)
                 return chacha?.encrypt(message)
-            case .AES(let key):
-                var aes = CryptoSwift.AES(key: key)
+            case .AES(let key, let iv):
+                var aes = CryptoSwift.AES(key: key, iv: iv)
                 return aes?.encrypt(message)
         }
     }
@@ -28,8 +28,8 @@ public enum Cipher {
             case .ChaCha20(let key, let iv):
                 var chacha = CryptoSwift.ChaCha20(key: key, iv: iv);
                 return chacha?.decrypt(message)
-            case .AES(let key):
-                var aes = CryptoSwift.AES(key: key);
+            case .AES(let key, let iv):
+                var aes = CryptoSwift.AES(key: key, iv: iv);
                 return aes?.decrypt(message)
         }
     }

+ 78 - 0
CryptoSwift/CipherBlockMode.swift

@@ -0,0 +1,78 @@
+//
+//  CipherBlockMode.swift
+//  CryptoSwift
+//
+//  Created by Marcin Krzyzanowski on 27/12/14.
+//  Copyright (c) 2014 Marcin Krzyzanowski. All rights reserved.
+//
+
+import Foundation
+
+public enum CipherBlockMode {
+    case Plain, CBC
+    
+    func processBlocks(blocks:[[Byte]], iv:[Byte]?, cipher:(block:[Byte]) -> [Byte]?) -> [Byte]? {
+        
+        // if IV is not available, fallback to plain
+        var finalBlockMode:CipherBlockMode = self
+        if (iv == nil) {
+            finalBlockMode = .Plain
+        }
+        
+        switch (finalBlockMode) {
+        case CBC:
+            return CBCMode.processBlocks(blocks, iv: iv, cipher: cipher)
+        case Plain:
+            return PlainMode.processBlocks(blocks, cipher: cipher)
+        }
+    }
+}
+
+private struct CBCMode {
+    static func processBlocks(blocks:[[Byte]], iv:[Byte]?, cipher:(block:[Byte]) -> [Byte]?) -> [Byte]? {
+        
+        if (iv == nil) {
+            return nil
+        }
+        
+        var out:[Byte]?
+        var lastCiphertext:[Byte] = iv!
+        for (idx,plaintext) in enumerate(blocks) {
+            // for the first time ciphertext = iv
+            // ciphertext = plaintext (+) ciphertext
+            var xoredPlaintext:[Byte] = plaintext
+            for i in 0..<plaintext.count {
+                xoredPlaintext[i] = lastCiphertext[i] ^ plaintext[i]
+            }
+            
+            // encrypt with cipher
+            if let encrypted = cipher(block: xoredPlaintext) {
+                lastCiphertext = encrypted
+                
+                if (out == nil) {
+                    out = [Byte]()
+                }
+                
+                out = out! + encrypted
+            }
+        }
+        return out;
+    }
+}
+
+private struct PlainMode {
+    static func processBlocks(blocks:[[Byte]], cipher:(block:[Byte]) -> [Byte]?) -> [Byte]? {
+        var out:[Byte]?
+        for (idx,plaintext) in enumerate(blocks) {
+            if let encrypted = cipher(block: plaintext) {
+                
+                if (out == nil) {
+                    out = [Byte]()
+                }
+
+                out = out! + encrypted
+            }
+        }
+        return out
+    }
+}

+ 16 - 1
CryptoSwiftTests/CipherTests.swift

@@ -35,7 +35,7 @@ class CipherTests: XCTestCase {
         super.tearDown()
     }
     
-    func testAES() {
+    func testAES_encode() {
         let input:[Byte] = [0x00, 0x11, 0x22, 0x33,
                             0x44, 0x55, 0x66, 0x77,
                             0x88, 0x99, 0xaa, 0xbb,
@@ -54,6 +54,21 @@ class CipherTests: XCTestCase {
         }
     }
     
+    func testAES_encode_cbc() {
+        let key:[Byte] = [0x2b,0x7e,0x15,0x16,0x28,0xae,0xd2,0xa6,0xab,0xf7,0x15,0x88,0x09,0xcf,0x4f,0x3c];
+        let iv:[Byte] = [0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F]
+        let plaintext:[Byte] = [0x6b,0xc1,0xbe,0xe2,0x2e,0x40,0x9f,0x96,0xe9,0x3d,0x7e,0x11,0x73,0x93,0x17,0x2a]
+        let expected:[Byte] = [0x76,0x49,0xab,0xac,0x81,0x19,0xb2,0x46,0xce,0xe9,0x8e,0x9b,0x12,0xe9,0x19,0x7d];
+        
+        if let aes = AES(key: NSData.withBytes(key), iv:NSData.withBytes(iv)) {
+            XCTAssertTrue(aes.blockMode == .CBC, "Invalid block mode")
+            let encrypted = aes.encrypt(NSData.withBytes(plaintext))
+            XCTAssertEqual(encrypted!, NSData.withBytes(expected), "encryption failed")
+        } else {
+            XCTAssert(false, "failed")
+        }
+    }
+    
     func testAES_SubBytes() {
         let input:[[Byte]] = [[0x00, 0x10, 0x20, 0x30],
                               [0x40, 0x50, 0x60, 0x70],