You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript文件加解密校验和不匹配问题求助

JavaScript文件加解密校验和不匹配问题排查与修复

问题描述

实现了带SHA256校验和的AES-256-CBC文件加解密逻辑,但解密后校验和始终不匹配,同时原UTF-8文本文件加密后编码异常。核心问题表现为:

  • 解密得到的校验和与重新计算的明文校验和完全不一致
  • 加密后文件编码变为utf16le/ANSI,与原文件编码不符

核心问题分析

  1. 加密数据拼接与解密逻辑不匹配
    加密时将加密的文件内容和加密的校验和独立加密后追加写入文件,但解密时错误地将这两部分合并后一起解密,导致数据完全错乱。

  2. 文件读写的编码处理错误

    • 加密时用utf8编码读取文件,会破坏二进制文件或非UTF-8文本的原始字节;解密时用binary编码读取加密文件,会导致Buffer转换异常。
    • 错误假设加密后的校验和长度为64字节,但AES-CBC加密输出是16字节块的倍数,实际长度会随输入变化,直接截取最后64字节必然得到错误数据。
  3. 校验和的加密/解密流程错误
    加密校验和时将十六进制字符串按utf8传入,但解密后直接将二进制Buffer当作字符串对比,未做编码转换。

修复步骤

1. 统一用二进制模式处理文件读写

所有文件操作都直接读取/写入Buffer,避免编码转换破坏原始字节。

2. 明确加密文件的结构

加密文件采用固定结构:[16字节IV] + [4字节加密内容长度] + [加密文件内容] + [加密校验和],确保解密时能准确拆分各部分数据。

3. 独立解密文件内容与校验和

对加密后的文件内容和校验和分别解密,再将解密后的校验和转成字符串与重新计算的校验和对比。

修正后的完整代码

加密函数

const { randomBytes, createCipheriv, createHash } = require('crypto');
const { readFileSync, writeFileSync } = require('fs');

// 注意:AES-256-CBC要求密钥为32字节(64个十六进制字符)
const ENCRYPTION_KEY = 'your-32-byte-hex-key-here';

function encryptAndMoveFile(sourcePath, destinationPath) {
  try {
    const iv = randomBytes(16);
    // 读取原始文件二进制数据,避免编码转换
    const sourceBuffer = readFileSync(sourcePath);

    // 计算原始数据的SHA256校验和(十六进制字符串)
    const checksum = createHash('sha256').update(sourceBuffer).digest('hex');

    // 加密文件内容
    const fileCipher = createCipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY, 'hex'), iv);
    const encryptedData = Buffer.concat([fileCipher.update(sourceBuffer), fileCipher.final()]);

    // 加密校验和(将十六进制字符串转成Buffer后加密)
    const checksumCipher = createCipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY, 'hex'), iv);
    const encryptedChecksum = Buffer.concat([checksumCipher.update(checksum, 'utf8'), checksumCipher.final()]);

    // 构建加密文件:IV + 加密内容长度(4字节大端) + 加密内容 + 加密校验和
    const contentLengthBuffer = Buffer.alloc(4);
    contentLengthBuffer.writeUInt32BE(encryptedData.length, 0);
    const finalBuffer = Buffer.concat([iv, contentLengthBuffer, encryptedData, encryptedChecksum]);

    writeFileSync(destinationPath, finalBuffer);
    console.log('加密完成');
    console.log('IV:', iv.toString('hex'));
    console.log('原始校验和:', checksum);
  } catch (err) {
    console.error('加密错误:', err);
  }
}

解密函数

const { createDecipheriv, createHash } = require('crypto');
const { readFileSync } = require('fs');

const ENCRYPTION_KEY = 'your-32-byte-hex-key-here'; // 与加密密钥一致

function decryptFile(filePath) {
  try {
    // 读取加密文件的二进制Buffer
    const encryptedBuffer = readFileSync(filePath);

    // 拆分各部分数据
    const iv = encryptedBuffer.subarray(0, 16);
    const contentLength = encryptedBuffer.subarray(16, 20).readUInt32BE(0);
    const encryptedData = encryptedBuffer.subarray(20, 20 + contentLength);
    const encryptedChecksum = encryptedBuffer.subarray(20 + contentLength);

    // 解密文件内容
    const dataDecipher = createDecipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY, 'hex'), iv);
    const decryptedDataBuffer = Buffer.concat([dataDecipher.update(encryptedData), dataDecipher.final()]);

    // 解密校验和并转成UTF-8字符串
    const checksumDecipher = createDecipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY, 'hex'), iv);
    const decryptedChecksum = Buffer.concat([checksumDecipher.update(encryptedChecksum), checksumDecipher.final()]).toString('utf8');

    // 重新计算解密后数据的校验和
    const calculatedChecksum = createHash('sha256').update(decryptedDataBuffer).digest('hex');

    console.log('解密IV:', iv.toString('hex'));
    console.log('解密得到的校验和:', decryptedChecksum);
    console.log('重新计算的校验和:', calculatedChecksum);

    if (calculatedChecksum !== decryptedChecksum) {
      throw new Error('文件完整性校验失败:校验和不匹配');
    }

    // 若原文件是文本文件,转成UTF-8字符串;二进制文件直接返回decryptedDataBuffer
    const decryptedContent = decryptedDataBuffer.toString('utf8');
    return decryptedContent;
  } catch (err) {
    console.error('解密错误:', err);
    throw err;
  }
}

关键注意事项

  • 密钥合法性:AES-256-CBC要求密钥为32字节(64个十六进制字符),若密钥长度不符,Node.js会自动截断或抛出错误。
  • 二进制优先:所有文件操作都用二进制模式,避免编码转换导致的字节丢失或错乱。
  • 数据拆分可靠性:通过固定长度的IV和显式的长度字段拆分加密数据,避免依赖固定长度的加密结果(AES加密结果长度是块大小的倍数,会随输入变化)。

内容的提问来源于stack exchange,提问作者Daniel Corona

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 06:43:20