首页 > 解决方案 > nodejs - 包含带有加密文件数据的 iv

问题描述

我正在重构此代码以加密文件以将其放入类中。

    async encryptData(file, password){  
        this.salt = crypto.randomBytes(32);
        this.key = crypto.scryptSync(password, this.salt, 32);
        //this.buffer = await fs.readFile(file.tempFilePath);
        this.base64 = dataURI.getBase64DataURI(file.data, file.mimetype);
        this.iv = crypto.randomBytes(16);
        this.cipher = crypto.createCipheriv('aes-256-gcm', this.key, this.iv);
        this.encryptedData = Buffer.concat([this.cipher.update(this.base64, 'utf8'), this.cipher.final()]);
        this.output = `${this.iv.toString('hex')}:${this.encryptedData.toString('hex')}`;
        //fs.writeFile(...)
    }

目标是在将文件转换为 base64 后使用一个库加密文件,该库将保留稍后用于解密并将文件保存为原始格式的 mime 类型。在我的最后一行代码中,我有一个“输出”变量,它将创建一个包含iv加密数据的字符串。有没有更好的方法来包含iv数据,这样我就可以避免使用该toString()功能?如果可能的话,当我需要解密文件时如何获取 iv 和数据?

更新:

经过一些测试并在答案中删除base64文件编码的建议之后,我以这种方式重构了代码。似乎工作正常,但任何改进它的建议将不胜感激。

    async runServer(){
        this.app.post('/encrypt', async (req, res) => {
            let password = req.body.password;
            for(let file in req.files){
                await this.encryptData(req.files[file], password);
            }
            //res.send({});
        });
    
        this.app.post('/decrypt', async (req, res) => {
            let password = req.body.password;
            for(let file in req.files){
                await this.decryptData(req.files[file], password);
            }
            //res.send({});
        });
    }
    
    async encryptData(file, password){  
        this.salt = crypto.randomBytes(32);
        this.key = crypto.scryptSync(password, this.salt, 32);
        this.iv = crypto.randomBytes(16);
        this.cipher = crypto.createCipheriv('aes-256-gcm', this.key, this.iv);
        this.encryptedData = Buffer.concat([this.salt, this.iv, this.cipher.update(file.data), this.cipher.final()]);
        this.output = path.format({dir: this.tmpDir, base: file.name});
        await fs.writeFile(this.output, this.encryptedData);
    }

    async decryptData(file, password){
        this.salt = file.data.slice(0, 32);
        this.key = crypto.scryptSync(password, Buffer.from(this.salt, 'binary'), 32);
        this.iv = file.data.slice(32, 48);
        this.encryptedData = file.data.slice(48);
        this.decipher = crypto.createDecipheriv('aes-256-gcm', this.key, this.iv);
        this.decryptedData = this.decipher.update(this.encryptedData);
        this.output = path.format({dir: this.tmpDir, base: file.name});
        await fs.writeFile(this.output, this.decryptedData);
    }

更新 1:

正如建议的那样,我已经实现了 GCM required 标签。如果我没记错的话,它将有 16 个字节的长度。我不确定我需要在哪里将它传递到解密过程以及​​如何提取它。

//file encryption 
this.encryptedData = Buffer.concat([this.salt, this.iv, this.cipher.update(file.data), this.cipher.final(), this.cipher.getAuthTag()]);

//file decryption
this.salt = file.data.slice(0, 32);
this.key = crypto.scryptSync(password, Buffer.from(this.salt, 'binary'), 32);
this.iv = file.data.slice(32, 48);
//How I extract the GCM tag at the end of the data?
//this.tag = file.data.slice(48, 64);
this.encryptedData = file.data.slice(48);
//Where I should pass the extracted GCM tag?
this.decipher = crypto.createDecipheriv('aes-256-gcm', this.key, this.iv);
this.decryptedData = Buffer.concat([this.decipher.update(this.encryptedData), this.decipher.final()]);

标签: javascriptnode.jsencryption

解决方案


通常,IV 和密文在二进制级别连接。如果需要转换为字符串,请使用合适的二进制到文本编码,例如 Base64:

var encryptedData = Buffer.concat([iv, encryptedData]).toString('base64')
console.log(encryptedData)

IV 不是秘密的,因此可以在未加密的情况下发送。此外,:不需要分隔符(如 ),因为 IV 具有块大小的长度(AES 为 16 字节),因此分隔标准已知:

var encryptedDataBuffer = Buffer.from(encryptedData, 'base64')
var iv = encryptedDataBuffer.slice(0, 16)
var ciphertext = encryptedDataBuffer.slice(16)

十六进制也可以用作二进制到文本的编码(但这在 50% 时的效率低于 Base64 在 75% 时的效率)。如果密文必须是 URL 安全的,则可以应用Base64url而不是 Base64,或者可以执行URL 编码。

由于您使用的是 GCM,因此还必须考虑(非秘密)身份验证标签。您的代码中似乎缺少这点。该标签由 确定cipher.getAuthTag(),是解密所需的(更准确地说是在解密期间进行身份验证),通常附加到密文:iv|ciphertext|tag。标签的分离是可行的,因为标签的长度是已知的(默认为 16 字节)。

此外,对于每个密钥生成,都应生成一个新的随机(非秘密)盐,并将其类似地连接起来:salt|iv|ciphertext|tag.

顺便说一句,加密文件的Base64编码一般是不需要的,实际上二进制数据是可以加密的。Base64 编码只会增加数据量。


推荐阅读