是否可以使用 nodejs 加密在随机位置解密?
Is it possible to decipher at random position with nodejs crypto?
我的理解是,理论上,CTR 模式下的 AES 块密码允许解密大文件的任何位置,而无需读取整个文件。
但是,我不知道如何使用 nodejs 加密模块执行此操作。我可以为 Decipher.update
方法提供虚拟块,直到我到达我感兴趣的部分,此时我会提供从文件中读取的实际数据,但这将是一个糟糕的 hack,低效且脆弱,因为我需要知道块大小。
有没有办法用加密模块做到这一点,如果没有,我可以使用什么模块?
I could feed the Decipher.update method with dummy blocks until I get to the part I'm interested in
正如@Artjom 已经评论过的,假设使用 CTR 模式,您不需要提供文件开头或任何虚拟块。你可以直接输入你感兴趣的密文。(使用AES开始128位的块大小)
参见CTR mode of operation,你只需要将IV计数器设置为密文的起始块,只提供你想要解密的加密文件的一部分(你可能需要提供的虚拟字节如果需要,开始块)
示例:
您需要使用 AES 从位置 1048577 解密文件,它是块 65536 (1048577/16) 加 1 个字节。因此,您将 IV 设置为 nonce|65536
,解密虚拟 1 个字节(移动到 16*65536+1 的位置),然后您可以从您感兴趣的文件部分提供密文
我找到了解决这个问题的不同方法:
方法一:点击率模式
此答案基于@ArtjomB。和@gusto2 评论和回答,这真的给了我解决方案。但是,这是一个带有工作代码示例的新答案,它还显示了实现细节(例如,IV 必须递增为大端数)。
想法很简单:要从 n
块的偏移量开始解密,只需将 IV 增加 n
。每个块是 16 个字节。
import crypto = require('crypto');
let key = crypto.randomBytes(16);
let iv = crypto.randomBytes(16);
let message = 'Hello world! This is test message, designed to be encrypted and then decrypted';
let messageBytes = Buffer.from(message, 'utf8');
console.log(' clear text: ' + message);
let cipher = crypto.createCipheriv('aes-128-ctr', key, iv);
let cipherText = cipher.update(messageBytes);
cipherText = Buffer.concat([cipherText, cipher.final()]);
// this is the interesting part: we just increment the IV, as if it was a big 128bits unsigned integer. The IV is now valid for decrypting block n°2, which corresponds to byte offset 32
incrementIV(iv, 2); // set counter to 2
let decipher = crypto.createDecipheriv('aes-128-ctr', key, iv);
let decrypted = decipher.update(cipherText.slice(32)); // we slice the cipherText to start at byte 32
decrypted = Buffer.concat([decrypted, decipher.final()]);
let decryptedMessage = decrypted.toString('utf8');
console.log('decrypted message: ' + decryptedMessage);
该程序将打印:
clear text: Hello world! This is test message, designed to be encrypted and then decrypted
decrypted message: e, designed to be encrypted and then decrypted
正如预期的那样,解密的消息移动了 32 个字节。
最后,这里是 incrementIV 实现:
function incrementIV(iv: Buffer, increment: number) {
if(iv.length !== 16) throw new Error('Only implemented for 16 bytes IV');
const MAX_UINT32 = 0xFFFFFFFF;
let incrementBig = ~~(increment / MAX_UINT32);
let incrementLittle = (increment % MAX_UINT32) - incrementBig;
// split the 128bits IV in 4 numbers, 32bits each
let overflow = 0;
for(let idx = 0; idx < 4; ++idx) {
let num = iv.readUInt32BE(12 - idx*4);
let inc = overflow;
if(idx == 0) inc += incrementLittle;
if(idx == 1) inc += incrementBig;
num += inc;
let numBig = ~~(num / MAX_UINT32);
let numLittle = (num % MAX_UINT32) - numBig;
overflow = numBig;
iv.writeUInt32BE(numLittle, 12 - idx*4);
}
}
方法二:CBC模式
由于CBC使用前面的密文块作为IV,并且在解密阶段所有密文块都是已知的,所以你没有什么特别要做的,你可以在流的任何一点解密。唯一的问题是,您解密的第一个块将是垃圾,但接下来的就可以了。所以你只需要在你真正想要解密的部分之前开始一个块。
我的理解是,理论上,CTR 模式下的 AES 块密码允许解密大文件的任何位置,而无需读取整个文件。
但是,我不知道如何使用 nodejs 加密模块执行此操作。我可以为 Decipher.update
方法提供虚拟块,直到我到达我感兴趣的部分,此时我会提供从文件中读取的实际数据,但这将是一个糟糕的 hack,低效且脆弱,因为我需要知道块大小。
有没有办法用加密模块做到这一点,如果没有,我可以使用什么模块?
I could feed the Decipher.update method with dummy blocks until I get to the part I'm interested in
正如@Artjom 已经评论过的,假设使用 CTR 模式,您不需要提供文件开头或任何虚拟块。你可以直接输入你感兴趣的密文。(使用AES开始128位的块大小)
参见CTR mode of operation,你只需要将IV计数器设置为密文的起始块,只提供你想要解密的加密文件的一部分(你可能需要提供的虚拟字节如果需要,开始块)
示例:
您需要使用 AES 从位置 1048577 解密文件,它是块 65536 (1048577/16) 加 1 个字节。因此,您将 IV 设置为 nonce|65536
,解密虚拟 1 个字节(移动到 16*65536+1 的位置),然后您可以从您感兴趣的文件部分提供密文
我找到了解决这个问题的不同方法:
方法一:点击率模式
此答案基于@ArtjomB。和@gusto2 评论和回答,这真的给了我解决方案。但是,这是一个带有工作代码示例的新答案,它还显示了实现细节(例如,IV 必须递增为大端数)。
想法很简单:要从 n
块的偏移量开始解密,只需将 IV 增加 n
。每个块是 16 个字节。
import crypto = require('crypto');
let key = crypto.randomBytes(16);
let iv = crypto.randomBytes(16);
let message = 'Hello world! This is test message, designed to be encrypted and then decrypted';
let messageBytes = Buffer.from(message, 'utf8');
console.log(' clear text: ' + message);
let cipher = crypto.createCipheriv('aes-128-ctr', key, iv);
let cipherText = cipher.update(messageBytes);
cipherText = Buffer.concat([cipherText, cipher.final()]);
// this is the interesting part: we just increment the IV, as if it was a big 128bits unsigned integer. The IV is now valid for decrypting block n°2, which corresponds to byte offset 32
incrementIV(iv, 2); // set counter to 2
let decipher = crypto.createDecipheriv('aes-128-ctr', key, iv);
let decrypted = decipher.update(cipherText.slice(32)); // we slice the cipherText to start at byte 32
decrypted = Buffer.concat([decrypted, decipher.final()]);
let decryptedMessage = decrypted.toString('utf8');
console.log('decrypted message: ' + decryptedMessage);
该程序将打印:
clear text: Hello world! This is test message, designed to be encrypted and then decrypted decrypted message: e, designed to be encrypted and then decrypted
正如预期的那样,解密的消息移动了 32 个字节。
最后,这里是 incrementIV 实现:
function incrementIV(iv: Buffer, increment: number) {
if(iv.length !== 16) throw new Error('Only implemented for 16 bytes IV');
const MAX_UINT32 = 0xFFFFFFFF;
let incrementBig = ~~(increment / MAX_UINT32);
let incrementLittle = (increment % MAX_UINT32) - incrementBig;
// split the 128bits IV in 4 numbers, 32bits each
let overflow = 0;
for(let idx = 0; idx < 4; ++idx) {
let num = iv.readUInt32BE(12 - idx*4);
let inc = overflow;
if(idx == 0) inc += incrementLittle;
if(idx == 1) inc += incrementBig;
num += inc;
let numBig = ~~(num / MAX_UINT32);
let numLittle = (num % MAX_UINT32) - numBig;
overflow = numBig;
iv.writeUInt32BE(numLittle, 12 - idx*4);
}
}
方法二:CBC模式
由于CBC使用前面的密文块作为IV,并且在解密阶段所有密文块都是已知的,所以你没有什么特别要做的,你可以在流的任何一点解密。唯一的问题是,您解密的第一个块将是垃圾,但接下来的就可以了。所以你只需要在你真正想要解密的部分之前开始一个块。