         }
         
         return data
+    }
+}
+
+/// Password-based decryptor that can handle any supported format.
+@objc(RNDecryptor)
+public final class Decryptor : NSObject, CryptorType {
+    private var decryptors: [VersionedDecryptorType.Type] = [DecryptorV3.self]
+
+    private var buffer = NSMutableData()
+    private var decryptor: CryptorType?
+    private let password: String
+
+    /// Creates and returns a cryptor.
+    ///
+    /// - parameter password: Non-empty password string. This will be interpretted as UTF-8.
+    public init(password: String) {
+        assert(password != "")
+        self.password = password
+    }
+
+    /// Decrypt data using password and return decrypted data, invalidating decryptor. Throws if
+    /// password is incorrect or ciphertext is in the wrong format.
+    /// - throws `RNCryptorError`
+    public func decryptData(data: NSData) throws -> NSData {
+        return try oneshot(data)
+    }
+
+    /// Updates cryptor with data and returns processed data.
+    ///
+    /// - parameter data: Data to process. May be empty.
+    /// - throws: `RNCryptorError`
+    /// - returns: Processed data. May be empty.
+    public func updateWithData(data: NSData) throws -> NSData {
+        if let d = decryptor {
+            return try d.updateWithData(data)
+        }
+
+        buffer.appendData(data)
+
+        let toCheck:[VersionedDecryptorType.Type]
+        (toCheck, decryptors) = decryptors.splitPassFail{ self.buffer.length >= $0.preambleSize }
+
+        for decryptorType in toCheck {
+            if decryptorType.canDecrypt(buffer.bytesView[0..<decryptorType.preambleSize]) {
+                let d = decryptorType.init(password: password)
+                decryptor = d
+                let result = try d.updateWithData(buffer)
+                buffer.length = 0
+                return result
+            }
+        }
+
+        guard !decryptors.isEmpty else { throw RNCryptorError.UnknownHeader }
+        return NSData()
+    }
+
+    /// Returns trailing data and invalidates the cryptor.
+    ///
+    /// - throws: `RNCryptorError`
+    /// - returns: Trailing data
+    public func finalData() throws -> NSData {
+        guard let d = decryptor else {
+            throw RNCryptorError.UnknownHeader
+        }
+        return try d.finalData()
+    }
+}
+
+/// Format version 3 encryptor. Use this to ensure a specific format verison
+/// or when using keys (which are inherrently versions-specific). To use
+/// "the latest encryptor" with a password, use `Encryptor` instead.
+@objc(RNEncryptorV3)
+public final class EncryptorV3 : NSObject, CryptorType {
+    /// Creates and returns an encryptor.
+    ///
+    /// - parameter password: Non-empty password string. This will be interpretted as UTF-8.
+    public convenience init(password: String) {
+        self.init(
+            password: password,
+            encryptionSalt: RNCryptor.randomDataOfLength(V3.saltSize),
+            hmacSalt: RNCryptor.randomDataOfLength(V3.saltSize),
+            iv: RNCryptor.randomDataOfLength(V3.ivSize))
+    }
+
+    /// Creates and returns an encryptor using keys.
+    ///
+    /// - Attention: This method requires some expertise to use correctly.
+    ///              Most users should use `init(password:)` which is simpler
+    ///              to use securely.
+    ///
+    /// Keys should not be generated directly from strings (`.dataUsingEncoding()` or similar).
+    /// Ideally, keys should be random (`Cryptor.randomDataOfLength()` or some other high-quality
+    /// random generator. If keys must be generated from strings, then use `FormatV3.keyForPassword(salt:)`
+    /// with a random salt, or just use password-based encryption (that's what it's for).
+    ///
+    /// - parameters:
+    ///     - encryptionKey: AES-256 key. Must be exactly FormatV3.keySize (kCCKeySizeAES256, 32 bytes)
+    ///     - hmacKey: HMAC key. Must be exactly FormatV3.keySize (kCCKeySizeAES256, 32 bytes)
+    public convenience init(encryptionKey: NSData, hmacKey: NSData) {
+        self.init(encryptionKey: encryptionKey, hmacKey: hmacKey, iv: RNCryptor.randomDataOfLength(V3.ivSize))
+    }
+
+    /// Takes a data, returns a processed data, and invalidates the cryptor.
+    public func encryptData(data: NSData) -> NSData {
+        return try! oneshot(data)
+    }
+
+    /// Updates cryptor with data and returns encrypted data.
+    ///
+    /// - parameter data: Data to process. May be empty.
+    /// - returns: Processed data. May be empty.
+    public func updateWithData(data: NSData) -> NSData {
+        // It should not be possible for this to fail during encryption
+        return try! handle(engine.updateWithData(data))
+    }
+
+    /// Returns trailing data and invalidates the cryptor.
+    ///
+    /// - returns: Trailing data
+    public func finalData() -> NSData {
+        let result = NSMutableData(data: try! handle(engine.finalData()))
+        result.appendData(hmac.finalData())
+        return result
+    }
+
+    private var engine: Engine
+    private var hmac: HMACV3
+
+    private var pendingHeader: NSData?
+
+    private init(encryptionKey: NSData, hmacKey: NSData, iv: NSData, header: NSData) {
+        precondition(encryptionKey.length == V3.keySize)
+        precondition(hmacKey.length == V3.keySize)
+        precondition(iv.length == V3.ivSize)
+        hmac = HMACV3(key: hmacKey)
+        engine = Engine(operation: .Encrypt, key: encryptionKey, iv: iv)
+        pendingHeader = header
+    }
+
+    // Expose random numbers for testing
+    internal convenience init(encryptionKey: NSData, hmacKey: NSData, iv: NSData) {
+        let preamble = [V3.formatVersion, UInt8(0)]
+        let header = NSMutableData(bytes: preamble, length: preamble.count)
+        header.appendData(iv)
+        self.init(encryptionKey: encryptionKey, hmacKey: hmacKey, iv: iv, header: header)
+    }
+
+    // Expose random numbers for testing
+    internal convenience init(password: String, encryptionSalt: NSData, hmacSalt: NSData, iv: NSData) {
+        let encryptionKey = V3.keyForPassword(password, salt: encryptionSalt)
+        let hmacKey = V3.keyForPassword(password, salt: hmacSalt)
+
+        // TODO: This chained-+ is very slow to compile in Swift 2b5 (http://www.openradar.me/21842206)
+        // let header = [V3.version, UInt8(1)] + encryptionSalt + hmacSalt + iv
+        let preamble = [V3.formatVersion, UInt8(1)]
+        let header = NSMutableData(bytes: preamble, length: preamble.count)
+        header.appendData(encryptionSalt)
+        header.appendData(hmacSalt)
+        header.appendData(iv)
+
+        self.init(encryptionKey: encryptionKey, hmacKey: hmacKey, iv: iv, header: header)
+    }
+
+    private func handle(data: NSData) -> NSData {
+        var result: NSData
+        if let ph = pendingHeader {
+            let accum = NSMutableData(data: ph)
+            pendingHeader = nil
+            accum.appendData(data)
+            result = accum
+        } else {
+            result = data
+        }
+        hmac.updateWithData(result)
+        return result
+    }
+}
+
+/// Format version 3 decryptor. This is required in order to decrypt
+/// using keys (since key configuration is version-specific). For password
+/// decryption, `Decryptor` is generally preferred, and will call this
+/// if appropriate.
+@objc(RNDecryptorV3)
+public final class DecryptorV3: NSObject, VersionedDecryptorType {
+    /// Creates and returns a decryptor.
+    ///
+    /// - parameter password: Non-empty password string. This will be interpretted as UTF-8.
+    public init(password: String) {
+        credential = .Password(password)
+    }
+
+    /// Creates and returns a decryptor using keys.
+    ///
+    /// - parameters:
+    ///     - encryptionKey: AES-256 key. Must be exactly FormatV3.keySize (kCCKeySizeAES256, 32 bytes)
+    ///     - hmacKey: HMAC key. Must be exactly FormatV3.keySize (kCCKeySizeAES256, 32 bytes)
+    public init(encryptionKey: NSData, hmacKey: NSData) {
+        precondition(encryptionKey.length == V3.keySize)
+        precondition(hmacKey.length == V3.hmacSize)
+        credential = .Keys(encryptionKey: encryptionKey, hmacKey: hmacKey)
+    }
+
+    public func decryptData(data: NSData) throws -> NSData {
+        return try oneshot(data)
+    }
+
+    public func updateWithData(data: NSData) throws -> NSData {
+        if let e = decryptorEngine {
+            return try e.updateWithData(data)
+        }
+
+        buffer.appendData(data)
+        guard buffer.length >= requiredHeaderSize else {
+            return NSData()
+        }
+
+        let e = try createEngineWithCredential(credential, header: buffer.bytesView[0..<requiredHeaderSize])
+        decryptorEngine = e
+        let body = buffer.bytesView[requiredHeaderSize..<buffer.length]
+        buffer.length = 0
+        return try e.updateWithData(body)
+    }
+
+    public func finalData() throws -> NSData {
+        guard let result = try decryptorEngine?.finalData() else {
+            throw RNCryptorError.MessageTooShort
+        }
+        return result
+    }
+
+    static let preambleSize = 1
+    static func canDecrypt(preamble: NSData) -> Bool {
+        assert(preamble.length >= 1)
+        return preamble.bytesView[0] == 3
+    }
+
+    var requiredHeaderSize: Int {
+        switch credential {
+        case .Password(_): return V3.passwordHeaderSize
+        case .Keys(_, _): return V3.keyHeaderSize
+        }
+    }
+
+    private var buffer = NSMutableData()
+    private var decryptorEngine: DecryptorEngineV3?
+    private let credential: Credential
+
+    private func createEngineWithCredential(credential: Credential, header: NSData) throws -> DecryptorEngineV3 {
+        switch credential {
+        case let .Password(password):
+            return try createEngineWithPassword(password, header: header)
+        case let .Keys(encryptionKey, hmacKey):
+            return try createEngineWithKeys(encryptionKey: encryptionKey, hmacKey: hmacKey, header: header)
+        }
+    }
+
+    private func createEngineWithPassword(password: String, header: NSData) throws -> DecryptorEngineV3 {
+        assert(password != "")
+        precondition(header.length == V3.passwordHeaderSize)
+
+        guard DecryptorV3.canDecrypt(header) else {
+            throw RNCryptorError.UnknownHeader
+        }
+
+        guard header.bytesView[1] == 1 else {
+            throw RNCryptorError.InvalidCredentialType
+        }
+
+        let encryptionSalt = header.bytesView[2...9]
+        let hmacSalt = header.bytesView[10...17]
+        let iv = header.bytesView[18...33]
+
+        let encryptionKey = V3.keyForPassword(password, salt: encryptionSalt)
+        let hmacKey = V3.keyForPassword(password, salt: hmacSalt)
+
+        return DecryptorEngineV3(encryptionKey: encryptionKey, hmacKey: hmacKey, iv: iv, header: header)
+    }
+
+    private func createEngineWithKeys(encryptionKey encryptionKey: NSData, hmacKey: NSData, header: NSData) throws -> DecryptorEngineV3 {
+        precondition(header.length == V3.keyHeaderSize)
+        precondition(encryptionKey.length == V3.keySize)
+        precondition(hmacKey.length == V3.keySize)
+
+        guard DecryptorV3.canDecrypt(header) else {
+            throw RNCryptorError.UnknownHeader
+        }
+
+        guard header.bytesView[1] == 0 else {
+            throw RNCryptorError.InvalidCredentialType
+        }
+
+        let iv = header.bytesView[2..<18]
+        return DecryptorEngineV3(encryptionKey: encryptionKey, hmacKey: hmacKey, iv: iv, header: header)
+    }
+}
+
+private let CCErrorDomain = "com.apple.CommonCrypto"
+
+internal enum CryptorOperation: CCOperation {
+    case Encrypt = 0 // CCOperation(kCCEncrypt)
+    case Decrypt = 1 // CCOperation(kCCDecrypt)
+}
+
+internal final class Engine {
+    private let cryptor: CCCryptorRef
+    private var buffer = NSMutableData()
+
+    init(operation: CryptorOperation, key: NSData, iv: NSData) {
+        var cryptorOut = CCCryptorRef()
+        let result = CCCryptorCreate(
+            operation.rawValue,
+            CCAlgorithm(kCCAlgorithmAES128), CCOptions(kCCOptionPKCS7Padding),
+            key.bytes, key.length,
+            iv.bytes,
+            &cryptorOut
+        )
+        cryptor = cryptorOut
+
+        // It is a programming error to create us with illegal values
+        // This is an internal class, so we can constrain what is sent to us.
+        // If this is ever made public, it should throw instead of asserting.
+        assert(result == CCCryptorStatus(kCCSuccess))
+    }
+
+    deinit {
+        if cryptor != CCCryptorRef() {
+            CCCryptorRelease(cryptor)
+        }
+    }
+
+    func sizeBufferForDataOfLength(length: Int) -> Int {
+        let size = CCCryptorGetOutputLength(cryptor, length, true)
+        buffer.length = size
+        return size
+    }
+
+    func updateWithData(data: NSData) throws -> NSData {
+        let outputLength = sizeBufferForDataOfLength(data.length)
+        var dataOutMoved: Int = 0
+
+        var result: CCCryptorStatus = CCCryptorStatus(kCCUnimplemented)
+
+        result = CCCryptorUpdate(
+            cryptor,
+            data.bytes, data.length,
+            buffer.mutableBytes, outputLength,
+            &dataOutMoved)
+
+        // The only error returned by CCCryptorUpdate is kCCBufferTooSmall, which would be a programming error
+        assert(result == CCCryptorStatus(kCCSuccess))
+
+        buffer.length = dataOutMoved
+        return buffer
+    }
+
+    func finalData() throws -> NSData {
+        let outputLength = sizeBufferForDataOfLength(0)
+        var dataOutMoved: Int = 0
+
+        let result = CCCryptorFinal(
+            cryptor,
+            buffer.mutableBytes, outputLength,
+            &dataOutMoved
+        )
+
+        guard result == CCCryptorStatus(kCCSuccess) else {
+            throw NSError(domain: CCErrorDomain, code: Int(result), userInfo: nil)
+        }
+        
+        buffer.length = dataOutMoved
+        return buffer
+    }
+}
+
+internal typealias V3 = FormatV3
+
+private enum Credential {
+    case Password(String)
+    case Keys(encryptionKey: NSData, hmacKey: NSData)
+}
+
+
+private final class DecryptorEngineV3 {
+    private let buffer = OverflowingBuffer(capacity: V3.hmacSize)
+    private var hmac: HMACV3
+    private var engine: Engine
+
+    init(encryptionKey: NSData, hmacKey: NSData, iv: NSData, header: NSData) {
+        precondition(encryptionKey.length == V3.keySize)
+        precondition(hmacKey.length == V3.hmacSize)
+        precondition(iv.length == V3.ivSize)
+
+        hmac = HMACV3(key: hmacKey)
+        hmac.updateWithData(header)
+        engine = Engine(operation: .Decrypt, key: encryptionKey, iv: iv)
+    }
+
+    func updateWithData(data: NSData) throws -> NSData {
+        let overflow = buffer.updateWithData(data)
+        hmac.updateWithData(overflow)
+        return try engine.updateWithData(overflow)
+    }
+
+    func finalData() throws -> NSData {
+        let result = try engine.finalData()
+        let hash = hmac.finalData()
+        if !isEqualInConsistentTime(trusted: hash, untrusted: buffer.finalData()) {
+            throw RNCryptorError.HMACMismatch
+        }
+        return result
+    }
+}
+
+private final class HMACV3 {
+    var context: CCHmacContext = CCHmacContext()
+
+    init(key: NSData) {
+        CCHmacInit(
+            &context,
+            CCHmacAlgorithm(kCCHmacAlgSHA256),
+            key.bytes,
+            key.length
+        )
+    }
+
+    func updateWithData(data: NSData) {
+        CCHmacUpdate(&context, data.bytes, data.length)
+    }
+    
+    func finalData() -> NSData {
+        let hmac = NSMutableData(length: V3.hmacSize)!
+        CCHmacFinal(&context, hmac.mutableBytes)
+        return hmac
     }
 }
 

    static let hmacSize = Int(CC_SHA256_DIGEST_LENGTH)
    static let keyHeaderSize = 1 + 1 + kCCBlockSizeAES128
    static let passwordHeaderSize = 1 + 1 + 8 + 8 + kCCBlockSizeAES128


}

/// Errors thrown by `CryptorType`

        }
        return (pass, fail)
    }
}