是否有可能使用nodejs加密解密随机位置?

时间:2018-04-21 08:53:43

标签: node.js encryption aes random-access node-crypto

我的理解是,CTR模式下的AES分组密码在理论上允许解密大文件的任何位置,而无需读取整个文件。

但是,我没有看到如何使用nodejs加密模块执行此操作。我可以用虚拟块提供Decipher.update方法,直到我到达我感兴趣的部分,此时我将提供从文件中读取的实际数据,但这将是一个非常糟糕的黑客,低效和脆弱因为我需要知道块大小。

有没有办法用加密模块做,如果没有,我可以用什么模块?

2 个答案:

答案 0 :(得分:1)

  

我可以使用虚拟块提供Decipher.update方法,直到我到达我感兴趣的部分

正如@Artjom已经评论过,假设使用CTR模式,您不需要提供文件的开头或任何虚拟块。您可以直接输入您感兴趣的密文。(使用AES启动128位的块大小)

请参阅CTR mode of operation,您只需要将IV计数器设置为密文的起始块,只提供要解密的加密文件的一部分(您可能需要输入起始块的虚拟字节)如果需要的话)

示例:

你需要解密位置1048577的文件,使用AES它的块65536(1048577/16)加上1个字节。因此,您将IV设置为nonce|65536,解密虚拟1字节(移动到位置为16 * 65536 + 1)然后您可以从您感兴趣的文件部分提供密文

答案 1 :(得分:1)

我找到了解决这个问题的不同方法:

方法1:点击率模式

此答案基于@ArtjomB。和@ gusto2评论和回答,这真的给了我解决方案。但是,这是一个带有工作代码示例的新答案,它还显示了实现细节(例如,IV必须以Big Endian数字递增)。

这个想法很简单:要从n块的偏移开始解密,你只需将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个字节。

最后,这是增量IV实现:

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);
    }
}

方法2:CBC模式

由于CBC使用以前的密文块作为IV,并且在解密阶段已知所有密文块,您没有任何特别要做的事情,您可以在任何时候解密的流。唯一的问题是你解密的第一个块将是垃圾,但下一个块将没问题。因此,您只需要在实际要解密的部分之前启动一个块。