Browse Source

Implementation of OCB mode without AAD

André Berenguel 5 năm trước cách đây
mục cha
commit
6f5efda284

+ 8 - 0
CryptoSwift.xcodeproj/project.pbxproj

@@ -16,6 +16,8 @@
 		0EE73E74204D59C200110E11 /* CMACTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE73E72204D599C00110E11 /* CMACTests.swift */; };
 		14156CE52011422400DDCFBC /* ChaCha20Poly1305Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14156CE42011422400DDCFBC /* ChaCha20Poly1305Tests.swift */; };
 		1467460F2017BB3600DF04ED /* AEAD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1467460E2017BB3600DF04ED /* AEAD.swift */; };
+		35F3E51C23BF9A6700A024A1 /* OCB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35F3E51B23BF9A6700A024A1 /* OCB.swift */; };
+		35F3E51E23BF9AD300A024A1 /* AESOCBTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35F3E51D23BF9AD300A024A1 /* AESOCBTests.swift */; };
 		674A736F1BF5D85B00866C5B /* RabbitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 674A736E1BF5D85B00866C5B /* RabbitTests.swift */; };
 		750509991F6BEF2A00394A1B /* PKCS7.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750509981F6BEF2A00394A1B /* PKCS7.swift */; };
 		750CC3EB1DC0CACE0096BE6E /* BlowfishTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750CC3EA1DC0CACE0096BE6E /* BlowfishTests.swift */; };
@@ -204,6 +206,8 @@
 		0EE73E72204D599C00110E11 /* CMACTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CMACTests.swift; sourceTree = "<group>"; };
 		14156CE42011422400DDCFBC /* ChaCha20Poly1305Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChaCha20Poly1305Tests.swift; sourceTree = "<group>"; };
 		1467460E2017BB3600DF04ED /* AEAD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AEAD.swift; sourceTree = "<group>"; };
+		35F3E51B23BF9A6700A024A1 /* OCB.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OCB.swift; sourceTree = "<group>"; };
+		35F3E51D23BF9AD300A024A1 /* AESOCBTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AESOCBTests.swift; sourceTree = "<group>"; };
 		674A736E1BF5D85B00866C5B /* RabbitTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RabbitTests.swift; sourceTree = "<group>"; };
 		750509981F6BEF2A00394A1B /* PKCS7.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PKCS7.swift; sourceTree = "<group>"; };
 		750CC3EA1DC0CACE0096BE6E /* BlowfishTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlowfishTests.swift; sourceTree = "<group>"; };
@@ -442,6 +446,7 @@
 				E6200E151FB9B67C00258382 /* HKDFTests.swift */,
 				757DA2541A4ED408002BA3EF /* AESTests.swift */,
 				755E0303217A756F00065FC6 /* AESCCMTests.swift */,
+				35F3E51D23BF9AD300A024A1 /* AESOCBTests.swift */,
 				7576F6EE20726422006688F8 /* AESTestsPerf.swift */,
 				750CC3EA1DC0CACE0096BE6E /* BlowfishTests.swift */,
 				757DA2581A4ED4D7002BA3EF /* ChaCha20Tests.swift */,
@@ -584,6 +589,7 @@
 				75EC52451EE8B6CA0048EB3B /* ECB.swift */,
 				75EC52461EE8B6CA0048EB3B /* OFB.swift */,
 				75EC52471EE8B6CA0048EB3B /* PCBC.swift */,
+				35F3E51B23BF9A6700A024A1 /* OCB.swift */,
 			);
 			path = BlockMode;
 			sourceTree = "<group>";
@@ -897,11 +903,13 @@
 				75EC52B31EE8B83D0048EB3B /* UInt16+Extension.swift in Sources */,
 				75EC52A81EE8B8390048EB3B /* PKCS5.swift in Sources */,
 				1467460F2017BB3600DF04ED /* AEAD.swift in Sources */,
+				35F3E51C23BF9A6700A024A1 /* OCB.swift in Sources */,
 				75EC528A1EE8B8170048EB3B /* PCBC.swift in Sources */,
 				75EC528D1EE8B81A0048EB3B /* ChaCha20.swift in Sources */,
 				75EC52851EE8B8170048EB3B /* CBC.swift in Sources */,
 				75EC52A71EE8B8390048EB3B /* PBKDF2.swift in Sources */,
 				75EC529D1EE8B8200048EB3B /* Utils+Foundation.swift in Sources */,
+				35F3E51E23BF9AD300A024A1 /* AESOCBTests.swift in Sources */,
 				75EC527E1EE8B8130048EB3B /* Authenticator.swift in Sources */,
 				75EC52AB1EE8B83D0048EB3B /* Rabbit.swift in Sources */,
 				75B3ED77210F9DF7005D4ADA /* BlockDecryptor.swift in Sources */,

+ 2 - 2
Sources/CryptoSwift/AES.Cryptors.swift

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

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

@@ -18,7 +18,7 @@ public typealias CipherOperationOnBlock = (_ block: ArraySlice<UInt8>) -> Array<
 public protocol BlockMode {
   var options: BlockModeOption { get }
   //TODO: doesn't have to be public
-  func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker
+  func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock, encryptionOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker
 }
 
 typealias StreamMode = BlockMode

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

@@ -29,7 +29,7 @@ public struct CBC: BlockMode {
     self.iv = iv
   }
 
-  public func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker {
+  public func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock, encryptionOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker {
     if self.iv.count != blockSize {
       throw Error.invalidInitializationVector
     }

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

@@ -69,7 +69,7 @@ public struct CCM: StreamMode {
     self.authenticationTag = authenticationTag
   }
 
-  public func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker {
+  public func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock, encryptionOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker {
     if self.nonce.isEmpty {
       throw Error.invalidInitializationVector
     }

+ 1 - 1
Sources/CryptoSwift/BlockMode/CFB.swift

@@ -29,7 +29,7 @@ public struct CFB: BlockMode {
     self.iv = iv
   }
 
-  public func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker {
+  public func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock, encryptionOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker {
     if self.iv.count != blockSize {
       throw Error.invalidInitializationVector
     }

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

@@ -30,7 +30,7 @@ public struct CTR: StreamMode {
     self.counter = counter
   }
 
-  public func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker {
+  public func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock, encryptionOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker {
     if self.iv.count != blockSize {
       throw Error.invalidInitializationVector
     }

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

@@ -22,7 +22,7 @@ public struct ECB: BlockMode {
   public init() {
   }
 
-  public func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker {
+  public func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock, encryptionOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker {
     ECBModeWorker(blockSize: blockSize, cipherOperation: cipherOperation)
   }
 }

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

@@ -64,7 +64,7 @@ public final class GCM: BlockMode {
     self.authenticationTag = authenticationTag
   }
 
-  public func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker {
+  public func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock, encryptionOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker {
     if self.iv.isEmpty {
       throw Error.invalidInitializationVector
     }

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

@@ -0,0 +1,363 @@
+//
+//  CryptoSwift
+//
+//  Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin@krzyzanowskim.com>
+//  This software is provided 'as-is', without any express or implied warranty.
+//
+//  In no event will the authors be held liable for any damages arising from the use of this software.
+//
+//  Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
+//
+//  - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required.
+//  - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+//  - This notice may not be removed or altered from any source or binary distribution.
+//
+
+//  The OCB Authenticated-Encryption Algorithm
+//  https://tools.ietf.org/html/rfc7253
+//
+
+public final class OCB: BlockMode {
+
+  public enum Mode {
+    /// In combined mode, the authentication tag is directly appended to the encrypted message. This is usually what you want.
+    case combined
+    /// Some applications may need to store the authentication tag and the encrypted message at different locations.
+    case detached
+  }
+
+  public let options: BlockModeOption = [.initializationVectorRequired]
+
+  public enum Error: Swift.Error {
+    case invalidNonce
+    case additionalAuthenticatedDataNotSupportedYet
+    case fail
+  }
+
+  private let N: Array<UInt8>
+  private let additionalAuthenticatedData: Array<UInt8>?
+  private let mode: Mode
+
+  /// Length of authentication tag, in bytes.
+  /// For encryption, the value is given as init parameter.
+  /// For decryption, the length of given authentication tag is used.
+  private let tagLength: Int
+
+  // `authenticationTag` nil for encryption, known tag for decryption
+  /// For encryption, the value is set at the end of the encryption.
+  /// For decryption, this is a known Tag to validate against.
+  public var authenticationTag: Array<UInt8>?
+
+  // encrypt
+  public init(nonce N: Array<UInt8>, additionalAuthenticatedData: Array<UInt8>? = nil, tagLength: Int = 16, mode: Mode = .detached) {
+    self.N = N
+    self.additionalAuthenticatedData = additionalAuthenticatedData
+    self.mode = mode
+    self.tagLength = tagLength
+  }
+
+  // decrypt
+  public convenience init(nonce N: Array<UInt8>, authenticationTag: Array<UInt8>, additionalAuthenticatedData: Array<UInt8>? = nil, mode: Mode = .detached) {
+    self.init(nonce: N, additionalAuthenticatedData: additionalAuthenticatedData, tagLength: authenticationTag.count, mode: mode)
+    self.authenticationTag = authenticationTag
+  }
+
+  public func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock, encryptionOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker {
+    if self.N.isEmpty || self.N.count > 15 {
+      throw Error.invalidNonce
+    }
+    if self.additionalAuthenticatedData != nil {
+      throw Error.additionalAuthenticatedDataNotSupportedYet
+    }
+
+    let worker = OCBModeWorker(N: N.slice, aad: self.additionalAuthenticatedData?.slice, expectedTag: self.authenticationTag, tagLength: self.tagLength, mode: self.mode, cipherOperation: cipherOperation, encryptionOperation: encryptionOperation)
+    worker.didCalculateTag = { [weak self] tag in
+      self?.authenticationTag = tag
+    }
+    return worker
+  }
+}
+
+// MARK: - Worker
+
+final class OCBModeWorker: BlockModeWorker, FinalizingEncryptModeWorker, FinalizingDecryptModeWorker {
+
+  let cipherOperation: CipherOperationOnBlock
+  var hashOperation: CipherOperationOnBlock!
+
+  // Callback called when authenticationTag is ready
+  var didCalculateTag: ((Array<UInt8>) -> Void)?
+
+  private let tagLength: Int
+
+  let blockSize = 16 // 128 bit
+  var additionalBufferSize: Int
+  private let mode: OCB.Mode
+
+  // Additional authenticated data
+  private let aad: ArraySlice<UInt8>?
+  // Known Tag used to validate during decryption
+  private var expectedTag: Array<UInt8>?
+
+  /*
+   * KEY-DEPENDENT
+   */
+  // NOTE: elements are lazily calculated
+  private var l = [Array<UInt8>]()
+  private var lAsterisk: Array<UInt8>
+  private var lDollar: Array<UInt8>
+
+
+  /*
+   * PER-ENCRYPTION/DECRYPTION
+   */
+  private var mainBlockCount: UInt64
+  private var offsetMain: Array<UInt8>
+  private var checksum: Array<UInt8>
+  private var sum: Array<UInt8>
+
+  init(N: ArraySlice<UInt8>, aad: ArraySlice<UInt8>? = nil, expectedTag: Array<UInt8>? = nil, tagLength: Int, mode: OCB.Mode, cipherOperation: @escaping CipherOperationOnBlock, encryptionOperation: @escaping CipherOperationOnBlock) {
+
+    self.cipherOperation = cipherOperation
+    self.hashOperation = encryptionOperation
+    self.mode = mode
+    self.aad = aad
+    self.expectedTag = expectedTag
+    self.tagLength = tagLength
+
+    if mode == .combined {
+      self.additionalBufferSize = tagLength
+    } else {
+      self.additionalBufferSize = 0
+    }
+
+    /*
+     * KEY-DEPENDENT INITIALIZATION
+     */
+
+    let zeros = Array<UInt8>(repeating: 0, count: self.blockSize)
+    lAsterisk = hashOperation(zeros.slice)!         /// L_* = ENCIPHER(K, zeros(128))
+    lDollar = double(lAsterisk)                     /// L_$ = double(L_*)
+    l.append(double(lDollar))                       /// L_0 = double(L_$)
+
+
+    /*
+     * NONCE-DEPENDENT AND PER-ENCRYPTION/DECRYPTION INITIALIZATION
+     */
+
+    /// Nonce = num2str(TAGLEN mod 128,7) || zeros(120-bitlen(N)) || 1 || N
+    var nonce = Array<UInt8>(repeating: 0, count: blockSize)
+    nonce[(nonce.count - N.count)...] = N
+    nonce[0] = UInt8(tagLength) << 4
+    nonce[blockSize - 1 - N.count] |= 1
+
+    /// bottom = str2num(Nonce[123..128])
+    let bottom = nonce[15] & 0x3F
+
+    /// Ktop = ENCIPHER(K, Nonce[1..122] || zeros(6))
+    nonce[15] &= 0xC0
+    let Ktop = hashOperation(nonce.slice)!
+
+    /// Stretch = Ktop || (Ktop[1..64] xor Ktop[9..72])
+    let Stretch = Ktop + xor(Ktop[0..<8], Ktop[1..<9])
+
+    /// Offset_0 = Stretch[1+bottom..128+bottom]
+    var offsetMAIN_0 = Array<UInt8>(repeating: 0, count: blockSize)
+    let bits = bottom % 8
+    let bytes = Int(bottom / 8)
+    if (bits == 0) {
+      offsetMAIN_0[0..<blockSize] = Stretch[bytes..<(bytes + blockSize)]
+    } else {
+      for i in 0..<blockSize {
+        let b1 = Stretch[bytes + i];
+        let b2 = Stretch[bytes + i + 1];
+        offsetMAIN_0[i] = ((b1 << bits) | (b2 >> (8 - bits)));
+      }
+    }
+
+    self.mainBlockCount = 0;
+
+    self.offsetMain = Array<UInt8>(offsetMAIN_0.slice)
+    self.checksum = Array<UInt8>(repeating: 0, count: blockSize) /// Checksum_0 = zeros(128)
+    self.sum = Array<UInt8>(repeating: 0, count: blockSize)
+  }
+
+  /// L_i = double(L_{i-1}) for every integer i > 0
+  func getLSub(_ n: Int) -> Array<UInt8> {
+    while n >= l.count {
+      l.append(double(l.last!))
+    }
+    return l[n]
+  }
+
+  func computeTag() -> Array<UInt8> {
+    ///  Tag = ENCIPHER(K, Checksum_* xor Offset_* xor L_$) xor HASH(K,A)
+    return xor(hashOperation(xor(xor(checksum, offsetMain).slice, lDollar))!, sum)
+  }
+
+  func encrypt(block plaintext: ArraySlice<UInt8>) -> Array<UInt8> {
+
+    if (plaintext.count == blockSize) {
+      return processBlock(block: plaintext, forEncryption: true)
+    } else {
+      return processFinalBlock(block: plaintext, forEncryption: true)
+    }
+  }
+
+  func finalize(encrypt ciphertext: ArraySlice<UInt8>) throws -> ArraySlice<UInt8> {
+
+    let tag = computeTag()
+
+    didCalculateTag?(tag)
+
+    switch mode {
+    case .combined:
+      return ciphertext + tag
+    case .detached:
+      return ciphertext
+    }
+  }
+
+  func decrypt(block ciphertext: ArraySlice<UInt8>) -> Array<UInt8> {
+
+    if ciphertext.count == blockSize {
+      return processBlock(block: ciphertext, forEncryption: false)
+    } else {
+      return processFinalBlock(block: ciphertext, forEncryption: false)
+    }
+  }
+
+  func finalize(decrypt plaintext: ArraySlice<UInt8>) throws -> ArraySlice<UInt8> {
+    // do nothing
+    plaintext
+  }
+
+  private func processBlock(block: ArraySlice<UInt8>, forEncryption: Bool) -> Array<UInt8> {
+
+    /*
+     * OCB-ENCRYPT/OCB-DECRYPT: Process any whole blocks
+     */
+
+    mainBlockCount += 1
+
+    /// Offset_i = Offset_{i-1} xor L_{ntz(i)}
+    offsetMain = xor(offsetMain, getLSub(ntz(mainBlockCount)));
+
+    /// C_i = Offset_i xor ENCIPHER(K, P_i xor Offset_i)
+    /// P_i = Offset_i xor DECIPHER(K, C_i xor Offset_i)
+    var mainBlock = Array<UInt8>(block)
+    mainBlock = xor(mainBlock, offsetMain);
+    mainBlock = cipherOperation(mainBlock.slice)!;
+    mainBlock = xor(mainBlock, offsetMain);
+
+    /// Checksum_i = Checksum_{i-1} xor P_i
+    if forEncryption {
+      checksum = xor(checksum, block);
+    } else {
+      checksum = xor(checksum, mainBlock);
+    }
+
+    return mainBlock
+  }
+
+
+  private func processFinalBlock(block: ArraySlice<UInt8>, forEncryption: Bool) -> Array<UInt8> {
+
+    let out: Array<UInt8>
+
+    if block.count == 0 {
+      /// C_* = <empty string>
+      /// P_* = <empty string>
+      out = []
+
+    } else {
+
+      /// Offset_* = Offset_m xor L_*
+      offsetMain = xor(offsetMain, lAsterisk);
+
+      /// Pad = ENCIPHER(K, Offset_*)
+      let Pad = hashOperation(offsetMain.slice)!
+
+      /// C_* = P_* xor Pad[1..bitlen(P_*)]
+      /// P_* = C_* xor Pad[1..bitlen(C_*)]
+      out = xor(block, Pad[0..<block.count])
+
+      /// Checksum_* = Checksum_m xor (P_* || 1 || zeros(127-bitlen(P_*)))
+      let plaintext = forEncryption ? block : out.slice
+      var plaintextExtended = plaintext + Array<UInt8>(repeating: 0, count: blockSize - plaintext.count)
+      plaintextExtended[plaintext.count] |= 0x80
+      checksum = xor(checksum, plaintextExtended)
+    }
+    return out
+  }
+
+
+  // The authenticated decryption operation has five inputs: K, IV , C, A, and T. It has only a single
+  // output, either the plaintext value P or a special symbol FAIL that indicates that the inputs are not
+  // authentic.
+  @discardableResult
+  func willDecryptLast(bytes ciphertext: ArraySlice<UInt8>) throws -> ArraySlice<UInt8> {
+    // Validate tag
+    switch self.mode {
+    case .combined:
+      // overwrite expectedTag property used later for verification
+      self.expectedTag = Array(ciphertext.suffix(self.tagLength))
+      return ciphertext[ciphertext.startIndex..<ciphertext.endIndex.advanced(by: -Swift.min(tagLength, ciphertext.count))]
+    case .detached:
+      return ciphertext
+    }
+  }
+
+  func didDecryptLast(bytes plaintext: ArraySlice<UInt8>) throws -> ArraySlice<UInt8> {
+    // Calculate MAC tag.
+    let computedTag = computeTag()
+
+    // Validate tag
+    guard let expectedTag = self.expectedTag, computedTag == expectedTag else {
+      throw OCB.Error.fail
+    }
+
+    return plaintext
+  }
+}
+
+// MARK: - Local utils
+
+private func ntz(_ x: UInt64) -> Int {
+  if x == 0 {
+    return 64;
+  }
+
+  var xv = x
+  var n = 0;
+  while ((xv & 1) == 0) {
+    n += 1
+    xv = xv >> 1;
+  }
+  return n;
+}
+
+private func double(_ block: Array<UInt8>) -> Array<UInt8> {
+  var ( carry,  result) = shiftLeft(block);
+
+  /*
+   * NOTE: This construction is an attempt at a constant-time implementation.
+   */
+  result[15] ^= (0x87 >> ((1 - carry) << 3));
+
+  return result;
+}
+
+private func shiftLeft(_ block: Array<UInt8>) -> (UInt8, Array<UInt8>)
+{
+  var output = Array<UInt8>(repeating: 0, count: block.count)
+
+  var bit:UInt8 = 0;
+
+  for i in 0..<block.count {
+    let b = block[block.count - 1 - i];
+    output[block.count - 1 - i] = ((b << 1) | bit);
+    bit = (b >> 7) & 1;
+  }
+  return (bit, output);
+}

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

@@ -29,7 +29,7 @@ public struct OFB: BlockMode {
     self.iv = iv
   }
 
-  public func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker {
+  public func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock, encryptionOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker {
     if self.iv.count != blockSize {
       throw Error.invalidInitializationVector
     }

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

@@ -29,7 +29,7 @@ public struct PCBC: BlockMode {
     self.iv = iv
   }
 
-  public func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker {
+  public func worker(blockSize: Int, cipherOperation: @escaping CipherOperationOnBlock, encryptionOperation: @escaping CipherOperationOnBlock) throws -> CipherModeWorker {
     if self.iv.count != blockSize {
       throw Error.invalidInitializationVector
     }

+ 3 - 3
Sources/CryptoSwift/Blowfish.swift

@@ -329,12 +329,12 @@ public final class Blowfish {
   }
 
   private func setupBlockModeWorkers() throws {
-    self.encryptWorker = try self.blockMode.worker(blockSize: Blowfish.blockSize, cipherOperation: self.encrypt)
+    self.encryptWorker = try self.blockMode.worker(blockSize: Blowfish.blockSize, cipherOperation: self.encrypt, encryptionOperation: self.encrypt)
 
     if self.blockMode.options.contains(.useEncryptToDecrypt) {
-      self.decryptWorker = try self.blockMode.worker(blockSize: Blowfish.blockSize, cipherOperation: self.encrypt)
+        self.decryptWorker = try self.blockMode.worker(blockSize: Blowfish.blockSize, cipherOperation: self.encrypt, encryptionOperation: self.encrypt)
     } else {
-      self.decryptWorker = try self.blockMode.worker(blockSize: Blowfish.blockSize, cipherOperation: self.decrypt)
+        self.decryptWorker = try self.blockMode.worker(blockSize: Blowfish.blockSize, cipherOperation: self.decrypt, encryptionOperation: self.encrypt)
     }
   }
 

+ 101 - 0
Tests/CryptoSwiftTests/AESOCBTests.swift

@@ -0,0 +1,101 @@
+////  CryptoSwift
+//
+//  Copyright (C) 2014-__YEAR__ Marcin Krzyżanowski <marcin@krzyzanowskim.com>
+//  This software is provided 'as-is', without any express or implied warranty.
+//
+//  In no event will the authors be held liable for any damages arising from the use of this software.
+//
+//  Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
+//
+//  - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required.
+//  - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+//  - This notice may not be removed or altered from any source or binary distribution.
+//
+
+import XCTest
+@testable import CryptoSwift
+
+class OCBTests: XCTestCase {
+
+
+  struct TestFixture {
+    let K: Array<UInt8>
+    let N: Array<UInt8>
+    let P: Array<UInt8>
+    let C: Array<UInt8>
+  }
+
+  func testAESOCBWithRFC7253Tests() {
+
+    var fixtures = [
+      TestFixture(K: Array<UInt8>(hex: "000102030405060708090A0B0C0D0E0F"),
+                  N: Array<UInt8>(hex: "BBAA99887766554433221100"),
+                  P: Array<UInt8>(hex: ""),
+                  C: Array<UInt8>(hex: "785407BFFFC8AD9EDCC5520AC9111EE6")),
+
+      TestFixture(K: Array<UInt8>(hex: "000102030405060708090A0B0C0D0E0F"),
+                  N: Array<UInt8>(hex: "BBAA99887766554433221103"),
+                  P: Array<UInt8>(hex: "0001020304050607"),
+                  C: Array<UInt8>(hex: "45DD69F8F5AAE72414054CD1F35D82760B2CD00D2F99BFA9")),
+
+      TestFixture(K: Array<UInt8>(hex: "000102030405060708090A0B0C0D0E0F"),
+                  N: Array<UInt8>(hex: "BBAA99887766554433221106"),
+                  P: Array<UInt8>(hex: "000102030405060708090A0B0C0D0E0F"),
+                  C: Array<UInt8>(hex: "5CE88EC2E0692706A915C00AEB8B2396F40E1C743F52436BDF06D8FA1ECA343D")),
+
+      TestFixture(K: Array<UInt8>(hex: "000102030405060708090A0B0C0D0E0F"),
+                  N: Array<UInt8>(hex: "BBAA99887766554433221109"),
+                  P: Array<UInt8>(hex: "000102030405060708090A0B0C0D0E0F1011121314151617"),
+                  C: Array<UInt8>(hex: "221BD0DE7FA6FE993ECCD769460A0AF2D6CDED0C395B1C3CE725F32494B9F914D85C0B1EB38357FF")),
+
+      TestFixture(K: Array<UInt8>(hex: "000102030405060708090A0B0C0D0E0F"),
+                  N: Array<UInt8>(hex: "BBAA9988776655443322110C"),
+                  P: Array<UInt8>(hex: "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"),
+                  C: Array<UInt8>(hex: "2942BFC773BDA23CABC6ACFD9BFD5835BD300F0973792EF46040C53F1432BCDFB5E1DDE3BC18A5F840B52E653444D5DF")),
+
+      TestFixture(K: Array<UInt8>(hex: "000102030405060708090A0B0C0D0E0F"),
+                  N: Array<UInt8>(hex: "BBAA9988776655443322110F"),
+                  P: Array<UInt8>(hex: "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627"),
+                  C: Array<UInt8>(hex: "4412923493C57D5DE0D700F753CCE0D1D2D95060122E9F15A5DDBFC5787E50B5CC55EE507BCB084E479AD363AC366B95A98CA5F3000B1479")),
+    ]
+
+    func testEncrypt(fixture: TestFixture) -> Bool {
+      let ocb = OCB(nonce: fixture.N, mode: .combined)
+      let aes = try! AES(key: fixture.K, blockMode: ocb, padding: .noPadding)
+      let encrypted = try! aes.encrypt(fixture.P)
+      if encrypted != fixture.C {
+        return false
+      }
+      return true
+    }
+
+    func testDecrypt(fixture: TestFixture) -> Bool {
+      let ocb = OCB(nonce: fixture.N, mode: .combined)
+      let aes = try! AES(key: fixture.K, blockMode: ocb, padding: .noPadding)
+      let plaintext = try! aes.decrypt(fixture.C)
+      if plaintext != fixture.P {
+        return false
+      }
+      return true
+    }
+
+    func testInvalidTag(fixture: TestFixture) -> Bool {
+      let ocb = OCB(nonce: fixture.N, mode: .combined)
+      let aes = try! AES(key: fixture.K, blockMode: ocb, padding: .noPadding)
+      var C_ = fixture.C.slice
+      C_[C_.count - 1] ^= 0x01
+      let plaintext = try? aes.decrypt(C_)
+      return plaintext == nil
+    }
+
+    for (i, fixture) in fixtures.enumerated() {
+      XCTAssertTrue(testEncrypt(fixture: fixture), "Encryption failed")
+      XCTAssertTrue(testDecrypt(fixture: fixture), "(\(i) - Decryption failed.")
+      XCTAssertTrue(testInvalidTag(fixture: fixture), "(\(i) - Invalid Tag verification failed.")
+    }
+  }
+
+  static let allTests = [
+    ("testAESOCBWithRFC7253Tests", testAESOCBWithRFC7253Tests),
+  ]
+}