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 扩展(上面的第二个代码块,除了删除提到的问题代码外保持不变)
虽然我能够根据 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 扩展(上面的第二个代码块,除了删除提到的问题代码外保持不变)