Эх сурвалжийг харах

Cipher feedback (CFB) for AES

Marcin Krzyżanowski 10 жил өмнө
parent
commit
6ceb6881cf

+ 3 - 3
CryptoSwift/AES.swift

@@ -98,10 +98,10 @@ public class AES {
         self.init(key:key, iv:nil)
     }
     
-    public init?(key:NSData, iv:NSData?) {
+    public init?(key:NSData, iv:NSData?, blockMode:CipherBlockMode = .CBC) {
         self.key = key
         self.iv = iv
-        self.blockMode = .CBC
+        self.blockMode = blockMode
         switch (key.length * 8) {
         case 128:
             self.variant = .aes128
@@ -122,7 +122,7 @@ public class AES {
     public func encrypt(message:NSData) -> NSData? {
         if (message.length % blockSizeBytes != 0) {
             // 128 bit block exceeded
-            println("AES 128-bit block exceeded!")
+            assertionFailure("AES 128-bit block exceeded!")
             return nil
         }
         

+ 53 - 1
CryptoSwift/CipherBlockMode.swift

@@ -9,8 +9,17 @@
 import Foundation
 
 public enum CipherBlockMode {
-    case Plain, CBC
+    case Plain, CBC, CFB
     
+    /**
+    Process input blocks with given block cipher mode. With fallback to plain mode.
+    
+    :param: blocks cipher block size blocks
+    :param: iv     IV
+    :param: cipher single block encryption closure
+    
+    :returns: encrypted bytes
+    */
     func processBlocks(blocks:[[Byte]], iv:[Byte]?, cipher:(block:[Byte]) -> [Byte]?) -> [Byte]? {
         
         // if IV is not available, fallback to plain
@@ -22,16 +31,22 @@ public enum CipherBlockMode {
         switch (finalBlockMode) {
         case CBC:
             return CBCMode.processBlocks(blocks, iv: iv, cipher: cipher)
+        case CFB:
+            return CFBMode.processBlocks(blocks, iv: iv, cipher: cipher)
         case Plain:
             return PlainMode.processBlocks(blocks, cipher: cipher)
         }
     }
 }
 
+/**
+*  Cipher-block chaining (CBC)
+*/
 private struct CBCMode {
     static func processBlocks(blocks:[[Byte]], iv:[Byte]?, cipher:(block:[Byte]) -> [Byte]?) -> [Byte]? {
         
         if (iv == nil) {
+            assertionFailure("CBC require IV")
             return nil
         }
         
@@ -60,6 +75,43 @@ private struct CBCMode {
     }
 }
 
+/**
+*  Cipher feedback (CFB)
+*/
+private struct CFBMode {
+    static func processBlocks(blocks:[[Byte]], iv:[Byte]?, cipher:(block:[Byte]) -> [Byte]?) -> [Byte]? {
+        
+        if (iv == nil) {
+            assertionFailure("CFB require IV")
+            return nil
+        }
+        
+        var out:[Byte]?
+        var lastCiphertext:[Byte] = iv!
+        for (idx,plaintext) in enumerate(blocks) {
+            if let encrypted = cipher(block: lastCiphertext) {
+                var xoredPlaintext:[Byte] = [Byte](count: plaintext.count, repeatedValue: 0)
+                for i in 0..<plaintext.count {
+                    xoredPlaintext[i] = plaintext[i] ^ encrypted[i]
+                }
+                lastCiphertext = xoredPlaintext
+
+                
+                if (out == nil) {
+                    out = [Byte]()
+                }
+                
+                out = out! + xoredPlaintext
+            }
+        }
+        return out;
+    }
+}
+
+
+/**
+*  Plain mode, don't use it. For debuging purposes only
+*/
 private struct PlainMode {
     static func processBlocks(blocks:[[Byte]], cipher:(block:[Byte]) -> [Byte]?) -> [Byte]? {
         var out:[Byte]?

+ 17 - 2
CryptoSwiftTests/CipherTests.swift

@@ -60,7 +60,7 @@ class CipherTests: XCTestCase {
         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)) {
+        if let aes = AES(key: NSData.withBytes(key), iv:NSData.withBytes(iv), blockMode: .CBC) {
             XCTAssertTrue(aes.blockMode == .CBC, "Invalid block mode")
             let encrypted = aes.encrypt(NSData.withBytes(plaintext))
             XCTAssertEqual(encrypted!, NSData.withBytes(expected), "encryption failed")
@@ -68,7 +68,22 @@ class CipherTests: XCTestCase {
             XCTAssert(false, "failed")
         }
     }
-    
+
+    func testAES_encode_cfb() {
+        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] = [0x3b,0x3f,0xd9,0x2e,0xb7,0x2d,0xad,0x20,0x33,0x34,0x49,0xf8,0xe8,0x3c,0xfb,0x4a];
+        
+        if let aes = AES(key: NSData.withBytes(key), iv:NSData.withBytes(iv), blockMode: .CFB) {
+            XCTAssertTrue(aes.blockMode == .CFB, "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],

+ 1 - 1
README.md

@@ -21,7 +21,7 @@ Good mood
 - [CRC32](http://en.wikipedia.org/wiki/Cyclic_redundancy_check) (well, kind of hash)
 
 #####Cipher
-- [AES-128, AES-192, AES-256](http://csrc.nist.gov/publications/fips/fips197/fips-197.pd8) with 128-bit block (caution: encryption only for now)
+- [AES-128, AES-192, AES-256](http://csrc.nist.gov/publications/fips/fips197/fips-197.pd8) (encrypt only, need padding)
 - [ChaCha20](http://cr.yp.to/chacha/chacha-20080128.pdf)
 
 #####Message authenticators