Sfoglia il codice sorgente

Merge pull request #855 from NathanFallet/master

Implementing CFB segment size
Marcin Krzyzanowski 4 anni fa
parent
commit
1e7d7ed298

+ 1 - 0
CONTRIBUTORS.txt

@@ -68,6 +68,7 @@ organizations who have contributed source code to CryptoSwift.
 - Michael Redig <mredig@gmail.com>
 - Mo Ramezanpoor <me@mohsenr.com>
 - Nate West <nwest@detroitlabs.com>
+- Nathan Fallet <contact@nathanfallet.me>
 - Nicholas Maccharoli <nicko@screaming-cactus.com>
 - Nobutaka Yuasa <nobutaka.yuasa@gmail.com>
 - Oscar De Moya <oscar.demoya@koombea.com>

+ 8 - 6
Sources/CryptoSwift/AES.Cryptors.swift

@@ -17,19 +17,21 @@
 
 extension AES: Cryptors {
   public func makeEncryptor() throws -> Cryptor & Updatable {
-    let worker = try blockMode.worker(blockSize: AES.blockSize, cipherOperation: encrypt, encryptionOperation: encrypt)
+    let blockSize = blockMode.customBlockSize ?? AES.blockSize
+    let worker = try blockMode.worker(blockSize: blockSize, cipherOperation: encrypt, encryptionOperation: encrypt)
     if worker is StreamModeWorker {
-      return try StreamEncryptor(blockSize: AES.blockSize, padding: padding, worker)
+      return try StreamEncryptor(blockSize: blockSize, padding: padding, worker)
     }
-    return try BlockEncryptor(blockSize: AES.blockSize, padding: padding, worker)
+    return try BlockEncryptor(blockSize: blockSize, padding: padding, worker)
   }
 
   public func makeDecryptor() throws -> Cryptor & Updatable {
+    let blockSize = blockMode.customBlockSize ?? AES.blockSize
     let cipherOperation: CipherOperationOnBlock = blockMode.options.contains(.useEncryptToDecrypt) == true ? encrypt : decrypt
-    let worker = try blockMode.worker(blockSize: AES.blockSize, cipherOperation: cipherOperation, encryptionOperation: encrypt)
+    let worker = try blockMode.worker(blockSize: blockSize, cipherOperation: cipherOperation, encryptionOperation: encrypt)
     if worker is StreamModeWorker {
-      return try StreamDecryptor(blockSize: AES.blockSize, padding: padding, worker)
+      return try StreamDecryptor(blockSize: blockSize, padding: padding, worker)
     }
-    return try BlockDecryptor(blockSize: AES.blockSize, padding: padding, worker)
+    return try BlockDecryptor(blockSize: blockSize, padding: padding, worker)
   }
 }

+ 2 - 1
Sources/CryptoSwift/AES.swift

@@ -496,7 +496,8 @@ private extension AES {
 
 extension AES: Cipher {
   public func encrypt(_ bytes: ArraySlice<UInt8>) throws -> Array<UInt8> {
-    let chunks = bytes.batched(by: AES.blockSize)
+    let blockSize = self.blockMode.customBlockSize ?? AES.blockSize
+    let chunks = bytes.batched(by: blockSize)
 
     var oneTimeCryptor = try makeEncryptor()
     var out = Array<UInt8>(reserveCapacity: bytes.count)

+ 2 - 0
Sources/CryptoSwift/BlockMode/BlockMode.swift

@@ -19,6 +19,8 @@ public protocol BlockMode {
   var options: BlockModeOption { get }
   //TODO: doesn't have to be public
   func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock, encryptionOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker
+
+  var customBlockSize: Int? { get }
 }
 
 typealias StreamMode = BlockMode

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

@@ -24,6 +24,7 @@ public struct CBC: BlockMode {
 
   public let options: BlockModeOption = [.initializationVectorRequired, .paddingRequired]
   private let iv: Array<UInt8>
+  public let customBlockSize: Int? = nil
 
   public init(iv: Array<UInt8>) {
     self.iv = iv

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

@@ -38,6 +38,7 @@ public struct CCM: StreamMode {
   private let additionalAuthenticatedData: Array<UInt8>?
   private let tagLength: Int
   private let messageLength: Int // total message length. need to know in advance
+  public let customBlockSize: Int? = nil
 
   // `authenticationTag` nil for encryption, known tag for decryption
   /// For encryption, the value is set at the end of the encryption.

+ 43 - 13
Sources/CryptoSwift/BlockMode/CFB.swift

@@ -21,20 +21,29 @@ public struct CFB: BlockMode {
     /// Invalid IV
     case invalidInitializationVector
   }
+    
+  public enum SegmentSize: Int {
+    case cfb8 = 1 // Encrypt byte per byte
+    case cfb128 = 16 // Encrypt 16 bytes per 16 bytes (default)
+  }
 
   public let options: BlockModeOption = [.initializationVectorRequired, .useEncryptToDecrypt]
   private let iv: Array<UInt8>
+  private let segmentSize: SegmentSize
+  public let customBlockSize: Int?
 
-  public init(iv: Array<UInt8>) {
+  public init(iv: Array<UInt8>, segmentSize: SegmentSize = .cfb128) {
     self.iv = iv
+    self.segmentSize = segmentSize
+    self.customBlockSize = segmentSize.rawValue
   }
 
   public func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock, encryptionOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker {
-    if self.iv.count != blockSize {
+    if !(self.iv.count == blockSize || (segmentSize == .cfb8 && self.iv.count == AES.blockSize)) {
       throw Error.invalidInitializationVector
     }
 
-    return CFBModeWorker(blockSize: blockSize, iv: self.iv.slice, cipherOperation: cipherOperation)
+    return CFBModeWorker(blockSize: blockSize, iv: self.iv.slice, segmentSize: segmentSize, cipherOperation: cipherOperation)
   }
 }
 
@@ -43,28 +52,49 @@ struct CFBModeWorker: BlockModeWorker {
   let blockSize: Int
   let additionalBufferSize: Int = 0
   private let iv: ArraySlice<UInt8>
+  private let segmentSize: CFB.SegmentSize
   private var prev: ArraySlice<UInt8>?
 
-  init(blockSize: Int, iv: ArraySlice<UInt8>, cipherOperation: @escaping CipherOperationOnBlock) {
+  init(blockSize: Int, iv: ArraySlice<UInt8>, segmentSize: CFB.SegmentSize, cipherOperation: @escaping CipherOperationOnBlock) {
     self.blockSize = blockSize
     self.iv = iv
+    self.segmentSize = segmentSize
     self.cipherOperation = cipherOperation
   }
 
   mutating func encrypt(block plaintext: ArraySlice<UInt8>) -> Array<UInt8> {
-    guard let ciphertext = cipherOperation(prev ?? iv) else {
-      return Array(plaintext)
+    switch segmentSize {
+    case .cfb128:
+      guard let ciphertext = cipherOperation(prev ?? iv) else {
+        return Array(plaintext)
+      }
+      self.prev = xor(plaintext, ciphertext.slice)
+      return Array(self.prev ?? [])
+    case .cfb8:
+      guard let ciphertext = cipherOperation(prev ?? iv) else {
+        return Array(plaintext)
+      }
+      let result = [Array(plaintext)[0] ^ Array(ciphertext)[0]]
+      self.prev = Array((prev ?? iv).dropFirst()) + [result[0]]
+      return result
     }
-    self.prev = xor(plaintext, ciphertext.slice)
-    return Array(self.prev ?? [])
   }
 
   mutating func decrypt(block ciphertext: ArraySlice<UInt8>) -> Array<UInt8> {
-    guard let plaintext = cipherOperation(prev ?? iv) else {
-      return Array(ciphertext)
+    switch segmentSize {
+    case .cfb128:
+      guard let plaintext = cipherOperation(prev ?? iv) else {
+        return Array(ciphertext)
+      }
+      let result: Array<UInt8> = xor(plaintext, ciphertext)
+      prev = ciphertext
+      return result
+    case .cfb8:
+      guard let plaintext = cipherOperation(prev ?? iv) else {
+        return Array(ciphertext)
+      }
+      self.prev = Array((prev ?? iv).dropFirst()) + [Array(ciphertext)[0]]
+      return [Array(ciphertext)[0] ^ Array(plaintext)[0]]
     }
-    let result: Array<UInt8> = xor(plaintext, ciphertext)
-    prev = ciphertext
-    return result
   }
 }

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

@@ -24,6 +24,7 @@ public struct CTR: StreamMode {
   public let options: BlockModeOption = [.initializationVectorRequired, .useEncryptToDecrypt]
   private let iv: Array<UInt8>
   private let counter: Int
+  public let customBlockSize: Int? = nil
 
   public init(iv: Array<UInt8>, counter: Int = 0) {
     self.iv = iv

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

@@ -18,6 +18,7 @@
 
 public struct ECB: BlockMode {
   public let options: BlockModeOption = .paddingRequired
+  public let customBlockSize: Int? = nil
 
   public init() {
   }

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

@@ -38,6 +38,7 @@ public final class GCM: BlockMode {
   private let iv: Array<UInt8>
   private let additionalAuthenticatedData: Array<UInt8>?
   private let mode: Mode
+  public let customBlockSize: Int? = nil
 
   /// Length of authentication tag, in bytes.
   /// For encryption, the value is given as init parameter.

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

@@ -36,6 +36,7 @@ public final class OCB: BlockMode {
   private let N: Array<UInt8>
   private let additionalAuthenticatedData: Array<UInt8>?
   private let mode: Mode
+  public let customBlockSize: Int? = nil
 
   /// Length of authentication tag, in bytes.
   /// For encryption, the value is given as init parameter.

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

@@ -24,6 +24,7 @@ public struct OFB: BlockMode {
 
   public let options: BlockModeOption = [.initializationVectorRequired, .useEncryptToDecrypt]
   private let iv: Array<UInt8>
+  public let customBlockSize: Int? = nil
 
   public init(iv: Array<UInt8>) {
     self.iv = iv

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

@@ -24,6 +24,7 @@ public struct PCBC: BlockMode {
 
   public let options: BlockModeOption = [.initializationVectorRequired, .paddingRequired]
   private let iv: Array<UInt8>
+  public let customBlockSize: Int? = nil
 
   public init(iv: Array<UInt8>) {
     self.iv = iv

+ 15 - 0
Tests/CryptoSwiftTests/AESTests.swift

@@ -161,6 +161,20 @@ final class AESTests: XCTestCase {
     let decrypted: Array<UInt8> = try! AES(key: key, blockMode: CFB(iv: iv)).decrypt(encrypted)
     XCTAssert(decrypted == plaintext, "decryption failed")
   }
+    
+  // https://github.com/krzyzanowskim/CryptoSwift/issues/500
+  func testAESEncryptCFB8() {
+    let key: Array<UInt8> = [0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c]
+    let iv: Array<UInt8> = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f]
+    let plaintext: Array<UInt8> = [0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, 0xae, 0x2d]
+    let expected: Array<UInt8> = [0x3b, 0x79, 0x42, 0x4c, 0x9c, 0x0d, 0xd4, 0x36, 0xba, 0xce, 0x9e, 0x0e, 0xd4, 0x58, 0x6a, 0x4f, 0x32, 0xb9]
+
+    let aes = try! AES(key: key, blockMode: CFB(iv: iv, segmentSize: .cfb8), padding: .noPadding)
+    let encrypted = try! aes.encrypt(plaintext)
+    XCTAssertEqual(encrypted, expected, "encryption failed")
+    let decrypted = try! aes.decrypt(encrypted)
+    XCTAssertEqual(decrypted, plaintext, "decryption failed")
+  }
 
   func testAESEncryptOFB128() {
     let key: Array<UInt8> = [0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c]
@@ -654,6 +668,7 @@ extension AESTests {
       ("testAESDecryptCBCWithPaddingPartial", testAESDecryptCBCWithPaddingPartial),
       ("testAESEncryptCFB", testAESEncryptCFB),
       ("testAESEncryptCFBLong", testAESEncryptCFBLong),
+      ("testAESEncryptCFB8", testAESEncryptCFB8),
       ("testAESEncryptOFB128", testAESEncryptOFB128),
       ("testAESEncryptOFB256", testAESEncryptOFB256),
       ("testAESEncryptPCBC256", testAESEncryptPCBC256),