CCCrypt 处理文件片段 AES CBC:垃圾到加密解密周期中的文件结尾

CCCrypt working with pieces of files AES CBC: garbage to end of files on encryption-decryption cycle

虽然我能够根据 Zaph 和其他人的一些很好的例子使用 CCCrypt 轻松地进行数据加密和解密 (AES128CBC),但是在使用 CCCrypt 时我遇到了两个奇怪的问题 encrypting/decrypting 个文件。

1) 在加密然后解密文件时,我的文件末尾出现额外的垃圾,并且根据文件的不同而不同。一个原始文件的十六进制转储和加密和解密后的结果有额外的“0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B”另一个文件最后有一个额外的“05 05 05 05 05”。两个结果文件的命令行差异报告 "No newline at end of file"。除了这些问题(以及处理与这些问题相关的文件的问题,如 NSJSONSerialization 拒绝解析)之外,一切似乎都在工作。是什么导致了这个令人沮丧的问题?

2) 为了最终容纳非常大的文件,我将文件分成块,然后使用前一个块的最后 16 个字节作为下一个块的 IV(因为这是 CBC 的工作方式,对吧?)。奇怪的是,在可被密钥大小整除的块上,使用 kCCOption PKCS7Padding 会导致问题。因此,在这些块之间进行解密和加密时,IV 最终会有所不同。当我只是将所有块的选项设置为零时,包括最后一个可能不能被密钥大小整除的块,我得到一个异常。谁能帮我理解这个问题是什么?我只是用一个条件来避免这个问题,但我不明白异常,也许它与问题1有关。

    let fileSize = getFileSizeFromPath(filePath)
    println("filesize = \(fileSize)")
    while file != nil {
        if let inputBuffer = file?.readDataOfLength(oneMegaByte) {
            if inputBuffer.length == 0 {
                file?.closeFile()
                break
            } else {
                println("input buffer length: \(inputBuffer.length)")
                if let outputBuffer = inputBuffer.AES128CBC(key: key, iv: iv, encryptionOp: encrypt) {
                    println("output buffer length: \(outputBuffer.length)")
                    outFile?.writeData(outputBuffer)
                    println("input file offset:\(file!.offsetInFile) output file offset:\(outFile!.offsetInFile)")
                    if encrypt {
                        let range = NSMakeRange((outputBuffer.length - const.keyLength), const.keyLength)
                        iv = outputBuffer.subdataWithRange(range)
                        println("range of iv for next chunk:\(range), iv value: \(iv)")
                    } else {
                        let range = NSMakeRange((inputBuffer.length - const.keyLength), const.keyLength)
                        iv = inputBuffer.subdataWithRange(range)
                        println("range of iv for next chunk:\(range), iv value: \(iv)")
                    }
                } else {
                    file?.closeFile()
                    println("problem encrypting data")
                    break
                }
            }
        } else {
            file?.closeFile()
            break
        }

以及我添加到 NSData 扩展以加密和解密的方法:

func AES128CBC(#key: NSData, iv: NSData, encryptionOp: Bool) -> NSData? {
    if key.length != 16 || iv.length != 16 || key.bytes == iv.bytes {
        return nil
    }

    let data = self
    let dataLength = UInt(data.length)
    let cPtrToData = UnsafePointer<UInt8>(data.bytes)
    let cPtrToIVData = UnsafePointer<UInt8>(iv.bytes)
    let cPtrTokeyData = UnsafePointer<UInt8>(key.bytes)
    let keySize = size_t(kCCKeySizeAES128)

    let buffer: NSMutableData! = NSMutableData(length: Int(dataLength) + kCCBlockSizeAES128)
    if buffer == nil { return nil }
    var cPtrTobuffer = UnsafeMutablePointer<UInt8>(buffer.mutableBytes)
    let bufferSize = size_t(buffer.length)

    var operation: CCOperation
    if encryptionOp {
        operation = UInt32(kCCEncrypt)
    } else {
        operation = UInt32(kCCDecrypt)
    }

    let algoritm: CCAlgorithm = UInt32(kCCAlgorithmAES128)
    var options: CCOptions
    if dataLength % UInt(const.keyLength) != 0 {
        options = UInt32(kCCOptionPKCS7Padding)
    } else {
        options = 0
    }
        var encryptedByteCount: UInt = 0

    var operationResult = CCCrypt(operation, algoritm, options, cPtrTokeyData, keySize, cPtrToIVData, cPtrToData, dataLength, cPtrTobuffer, bufferSize, &encryptedByteCount)

    if UInt32(operationResult) == UInt32(kCCSuccess) {
        println("encrypted / decrypted byte count: \(encryptedByteCount)")
        buffer.length = Int(encryptedByteCount)
        return buffer
    }
    println("Error: \(operationResult)")
    return nil
}

额外的字节是 PKCS#7 padding。加密是基于块的,因此必须在加密时添加字节,然后在解密时删除字节以实现此目的。根据 PKCS#7,额外字节是添加的字节数。这些附加字节:“05 05 05 05 05”表示添加了 5 个字节的填充。

如果指定了 PKCS#7 填充且输入数据恰好是块大小的倍数,则会添加另一个块(并且将全部为填充字节)。这必须发送,否则解密时会出现填充错误。如果您知道加密和解密数据是块大小的倍数,您可以跳过 PKCS#7 填充。在 OP 的情况下,如果中间段都是块大小的倍数,则可以在除最后一个加密段之外的所有加密段上跳过。 AES 使用 128 位(16 字节)块。

感谢Zaph的帮助,让我明白了以上问题。为了清楚起见,我将更直接地回答上述问题。

1) 正如 Zaph 所说,我的解密文件上的额外字节被填充了。它们出现是因为以下违规代码,我已将其删除。

var options: CCOptions
if dataLength % UInt(const.keyLength) != 0 {
    options = UInt32(kCCOptionPKCS7Padding)
} else {
    options = 0
}

如果选择 PKCS7Padding 选项(这就是添加它们的原因),由于 CCCrypt 的输出将始终被密钥长度整除,因此上述代码将测试在解密操作期间永远不会满足的条件。然后 CCCrypt 不会为我去掉填充。由于在我的应用程序中,我主要处理的是文件的子部分,这些子部分始终是密钥大小的固定倍数而不是整个文件,因此我试图使用上述代码删除文件之间不必要的填充,而不考虑解密操作中的后果.这当然更适合在调用我的 NSData 扩展的代码中完成。

2) 根据手册页(没有用于 ios 的手册页,但可以在此处找到 2007 年的旧 mac 手册页:https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/CCCrypt.3cc.html

"if padding is disabled, or when decrypting, the total number of bytes [has] to be aligned to the block size; otherwise CCCryptFinal() will return kCCAlignmentError."

这意味着当我将选项设置为零时(没有设置 PKCS7Padding 选项),最后一个块会导致程序崩溃。

由于我将文件分成多个子部分用于我的加密和解密操作(最终适应非常大的文件大小)我需要使用前一个文件块的最后 16 个字节作为下一个文件块的 IV块(这就是 CBC 的工作原理,通过使用从前一个块的最后 16 个字节生成的 IV 为每个后续密钥大小的块播种(不管上面 Zaph 指出的是使用 128 位还是 256 位密钥大小)。

然而,我未能理解的是,如果选择了 PKCSPadding 选项,CCCrypt 总是在加密时提供填充,即使操作的数据是密钥大小的倍数。 CCCrypt 在文件的每个块的末尾添加 16 个字节,然后我在文件中间写入该填充。糟糕。

我现在使用以下代码调用 encryption/decryption 方法,并在这个更合适的级别丢弃对不 运行 文件末尾的数据的方法调用的填充.

while file != nil {
    if let inputBuffer = file?.readDataOfLength(fileChunkLength) { 
        if inputBuffer.length == 0 {
            file?.closeFile()
            break 
        } else { 
            println("input buffer length: \(inputBuffer.length)") 
            println("IV passed to aes128:\(iv)") 
            if let outputBuffer = inputBuffer.AES128CBC(key: key, iv: iv, encryptionOp: encrypt) { 
                println("output bufferlength: \(outputBuffer.length)")

                if encrypt && file?.offsetInFile < fileSize {
                    // only write the data, and discard the padding when not at the end of the file
                    outFile?.writeData(outputBuffer.subdataWithRange(NSMakeRange(0, fileChunkLength)))
                    println("Bytes written \(fileChunkLength)")
                    // set the iv for the next chunk in encryption ops (no need when file is finished anyway)
                    iv = outputBuffer.subdataWithRange(NSMakeRange(fileChunkLength - ivLength, ivLength))
                } else {
                    outFile?.writeData(outputBuffer)
                    println("Bytes written \(outputBuffer.length)")
                    // set iv for decryption ops (doesn't matter for encryption ops where file is at end)
                    iv = inputBuffer.subdataWithRange(NSMakeRange(inputBuffer.length - ivLength, ivLength))
                }
                println("input file offset:\(file!.offsetInFile) output file offset:\(outFile!.offsetInFile)")

            } else {
                file?.closeFile()
                println("problem encrypting data")
                break
            }
        }
    } else {
        file?.closeFile()
        break

NSData 扩展(上面的第二个代码块,除了删除提到的问题代码外保持不变)