我们正在尝试在Javascript(浏览器)中构建大文件的流解密。由于加密本身不支持流解密。微妙的是,我们正在逐块进行。这应该可以通过提供前一个块作为当前块的iv。
但是看起来Crypto.subtle总是期望在块的末尾有一个填充。(在这里确认:What padding does window.crypto.subtle.encrypt use for AES-CBC)。这至少会让实现感觉很奇怪。
有没有人有什么例子或者想法,如何在javascript中做到这一点,同时仍然使用本地API的?
我们得出了这样的结论:
const padding = new Uint8Array(16).fill(16);
const chunkSize = 16;
let index = 0;
const chunks = [];
let prevChunk = null;
do {
const chunk = ciphertext.slice(index, index + chunkSize);
// Encrypt padding with the chunk as iv
const paddingCypher = await crypto.subtle.encrypt({ name: 'AES-CBC', iv: chunk }, key, padding);
const encryptedPadding = (new Uint8Array(paddingCypher)).slice(0, 16);
const decrypted = await crypto.subtle.decrypt({ name: 'AES-CBC', iv: prevChunk || subtleIv}, subtleKey, mergeByteArrays([chunk, encryptedPadding]));
chunks.push(new Uint8Array(decrypted));
prevChunk = chunk;
index += chunkSize;
} while (index < length - chunkSize);
// Different for last chunk as that already has the padding
const lastChunk = await decrypt(
prevChunk || subtleIv,
subtleKey,
ciphertext.slice(index, index + chunkSize)
);
chunks.push(new Uint8Array(lastChunk));
var mergedChunks = mergeByteArrays(chunks);
// Decode and output
var fullyDecrypted = String.fromCharCode.apply(null, mergedChunks as any);
console.log(fullyDecrypted);
2条答案
按热度按时间vmpqdwk31#
如果你可以切换模式,另一个答案中建议的CTR模式是更方便的方法,因为WebCrypto API(像大多数库一样)会自动禁用流密码模式(如CTR)的填充,而它会自动启用块密码模式(如CBC)。
但是,如果您无法更改模式,并且仍然希望使用WebCrypto API,则必须在解密之前将带有加密填充的密文块添加到每个密文块中,如发布的代码中所示,因为WebCrypto API不允许对CBC等块密码模式禁用PKCS#7填充。
发布的代码可以进行优化:当前代码使用16字节作为块大小。此外,为了确定要附加的密文块,对每个密文块加密完整的填充块,由于填充而导致2块密文,其中第二个块必须被丢弃。
对于优化:
以下实现考虑了这两种情况:
mqkwyuun2#
简单解决方案:使用CTR模式。这不会填充,你可以简单地计算下一个块的计数器。你可以在
AesCtrParams
中指示整个(无符号大端)计数器,所以你应该是黄金的。计数器需要为每16字节的数据块增加1。如果你聪明的话,你当然可以让你的块成为16字节的倍数。请注意,没有密码提供与使用传输模式安全性/ TLS相同的安全性。
请记住,CTR模式是未经身份验证的,尽管填充oracle攻击在CTR上是不可能的(显然),但由此产生的密文容易受到位翻转攻击,因此,明文oracle也容易受到攻击。因此,您可能需要在nonce和密文上包含一个HMAC身份验证标签,并在使用解密数据之前验证该标签。
因此,如果您想在服务器端存储数据(“静态数据”),则在客户端使用CBC或CTR进行加密非常有用。