Browse Source

AES.Decryptor implements RandomAccessCryptor. #284.

Marcin Krzyżanowski 9 years ago
parent
commit
fe1e42243c
2 changed files with 73 additions and 3 deletions
  1. 41 0
      CryptoSwiftTests/AESTests.swift
  2. 32 3
      Sources/CryptoSwift/AES.swift

+ 41 - 0
CryptoSwiftTests/AESTests.swift

@@ -235,6 +235,47 @@ final class AESTests: XCTestCase {
         XCTAssertEqual(decrypted, plaintext, "decryption failed")
     }
 
+    // https://github.com/krzyzanowskim/CryptoSwift/pull/290
+    func testAES_decrypt_ctr_seek() {
+        let key:Array<UInt8> = [0x52, 0x72, 0xb5, 0x9c, 0xab, 0x07, 0xc5, 0x01, 0x11, 0x7a, 0x39, 0xb6, 0x10, 0x35, 0x87, 0x02];
+        let iv:Array<UInt8> = [0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]
+        var plaintext:Array<UInt8> = Array<UInt8>(repeating: 0, count: 6000)
+        
+        for i in 0..<plaintext.count / 6 {
+            let s = String(format: "%05d", i).utf8.map {$0}
+            plaintext[i * 6 + 0] = s[0];
+            plaintext[i * 6 + 1] = s[1];
+            plaintext[i * 6 + 2] = s[2];
+            plaintext[i * 6 + 3] = s[3];
+            plaintext[i * 6 + 4] = s[4];
+            plaintext[i * 6 + 5] = "|".utf8.first!;
+        }
+
+        let aes = try! AES(key: key, iv:iv, blockMode: .CTR, padding: NoPadding())
+        let encrypted = try! aes.encrypt(plaintext)
+
+        var decryptor = aes.makeDecryptor()
+        decryptor.seek(to: 2)
+        var part1 = try! decryptor.update(withBytes: Array(encrypted[2..<5]))
+        part1 += try! decryptor.finish()
+        XCTAssertEqual(part1, Array(plaintext[2..<5]), "seek decryption failed")
+
+        decryptor.seek(to: 1000)
+        var part2 = try! decryptor.update(withBytes: Array(encrypted[1000..<1200]))
+        part2 += try! decryptor.finish()
+        XCTAssertEqual(part2, Array(plaintext[1000..<1200]), "seek decryption failed")
+
+        decryptor.seek(to: 5500)
+        var part3 = try! decryptor.update(withBytes: Array(encrypted[5500..<6000]))
+        part3 += try! decryptor.finish()
+        XCTAssertEqual(part3, Array(plaintext[5500..<6000]), "seek decryption failed")
+
+        decryptor.seek(to: 0)
+        var part4 = try! decryptor.update(withBytes: Array(encrypted[0..<80]))
+        part4 += try! decryptor.finish()
+        XCTAssertEqual(part4, Array(plaintext[0..<80]), "seek decryption failed")
+    }
+
 //    func testAES_encrypt_performance() {
 //        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]

+ 32 - 3
Sources/CryptoSwift/AES.swift

@@ -425,12 +425,15 @@ extension AES {
 
 // MARK: Decryptor
 extension AES {
-    public struct Decryptor: UpdatableCryptor {
+    public struct Decryptor: RandomAccessCryptor {
         private var worker: BlockModeWorker
         private let padding: Padding
         private var accumulated = Array<UInt8>()
         private let paddingRequired: Bool
 
+        private var offset: Int = 0
+        private var offsetToRemove: Int = 0
+
         init(aes: AES) {
             self.padding = aes.padding;
 
@@ -446,13 +449,27 @@ extension AES {
         }
 
         mutating public func update(withBytes bytes:Array<UInt8>, isLast: Bool = false) throws -> Array<UInt8> {
-            self.accumulated += bytes
+            // prepend "offset" number of bytes at the begining
+            if self.offset > 0 {
+                self.accumulated += Array<UInt8>(repeating: 0, count: offset) + bytes
+                self.offsetToRemove = offset
+                self.offset = 0
+            } else {
+                self.accumulated += bytes
+            }
 
             var plaintext = Array<UInt8>()
             plaintext.reserveCapacity(self.accumulated.count)
             for chunk in self.accumulated.chunks(size: AES.blockSize) {
                 if (isLast || self.accumulated.count >= AES.blockSize) {
-                    plaintext += worker.decrypt(chunk)
+                    plaintext += self.worker.decrypt(chunk)
+
+                    // remove "offset" from the beginning of first chunk
+                    if self.offsetToRemove > 0 {
+                        plaintext.removeFirst(self.offsetToRemove);
+                        self.offsetToRemove = 0
+                    }
+
                     self.accumulated.removeFirst(chunk.count)
                 }
             }
@@ -463,6 +480,18 @@ extension AES {
 
             return plaintext
         }
+
+        @discardableResult mutating public func seek(to position: Int) -> Bool {
+            guard var worker = self.worker as? RandomAccessBlockModeWorker else {
+                return false
+            }
+
+            worker.counter = UInt(position / AES.blockSize)
+            self.worker = worker
+
+            self.offset = position % AES.blockSize
+            return false
+        }
     }
 }