加密工具处理PNG等二进制文件失效,求正确读取方式
搞定PNG加密的正确姿势:别用文本方式读二进制!
嘿,我懂你遇到的坑——处理TXT文件时加密解密都顺得很,一碰PNG就生成打不开的损坏文件,对比原文件还发现内容不对。问题出在你用UTF-8编码读取二进制文件上,这完全搞错了文件类型的处理逻辑!
为什么UTF-8读取PNG会坏?
UTF-8是给文本用的编码,它会把字节序列尝试解析成人类可读的字符。但PNG这类二进制文件里,有大量字节组合根本不是有效的UTF-8编码——Node.js碰到这种情况时,会自动把无效字节替换成「�」(Unicode替换字符),这直接就把原始的二进制信息弄丢了!写回去的文件自然和原文件不一样,系统当然认不出来是PNG。
而文本文件的内容本身是符合UTF-8规则的,所以用UTF-8读没问题,但二进制文件必须用原始字节模式处理。
正确的处理方案
下面是调整后的代码,专门适配二进制文件:
1. 修改读取流,获取原始Buffer
把读取流的encoding: 'UTF-8'删掉,让Node.js返回原始的Buffer(每个chunk就是单个字节的Buffer,因为你设了highWaterMark:1):
const reader = fs.createReadStream('./your-image.png', { flag: 'r', highWaterMark: 1 });
2. 重写二进制转换函数
原来的toBin和toStr是给字符串设计的,现在要换成直接操作字节的版本:
// 把单个字节(0-255的整数)转成8位二进制字符串 function byteToBin(byte) { // padStart(8, '0')确保不足8位时前面补零,保证是标准的8位二进制 return byte.toString(2).padStart(8, '0'); } // 把8位二进制字符串转回单个字节 function binToByte(binStr) { return parseInt(binStr, 2); }
3. 调整流处理逻辑
现在chunk是Buffer,直接取它的第一个元素就是字节的数值,然后用上面的函数处理:
reader.on('data', function (chunk) { // 从Buffer里取出单个字节的数值 const originalByte = chunk[0]; // 转成8位二进制 const originalBin = byteToBin(originalByte); // 用你原来的加密函数处理(比如swap或者reverse) const encryptedBin = swap(originalBin); // 转回字节数值 const encryptedByte = binToByte(encryptedBin); // 用Buffer包裹字节,写入文件(必须用Buffer,不能直接写数字) writer.write(Buffer.from([encryptedByte])); // 控制台打印对比(可选,方便调试) console.log(`原字节(十六进制): 0x${originalByte.toString(16).padStart(2, '0')} → 原二进制: ${originalBin} → 加密后二进制: ${encryptedBin} → 加密后字节: 0x${encryptedByte.toString(16).padStart(2, '0')}`); });
4. 写入流也保持二进制模式
写入流不需要指定编码,默认就是二进制模式:
const writer = fs.createWriteStream('./encrypted-image.png', { flag: 'w' });
完整可运行的示例代码
const fs = require('fs'); // 你的加密函数保持不变 function swap(bin) { return bin.slice(4, 8) + bin.slice(0, 4); } function reverse(bin) { return bin.split("").reverse().join(""); } // 适配二进制的转换函数 function byteToBin(byte) { return byte.toString(2).padStart(8, '0'); } function binToByte(binStr) { return parseInt(binStr, 2); } // 处理PNG文件 const inputPath = './test.png'; const outputPath = './encrypted-test.png'; const reader = fs.createReadStream(inputPath, { flag: 'r', highWaterMark: 1 }); const writer = fs.createWriteStream(outputPath, { flag: 'w' }); reader.on('data', (chunk) => { const originalByte = chunk[0]; const originalBin = byteToBin(originalByte); // 这里可以替换成你想用的加密函数,比如reverse(originalBin) const encryptedBin = swap(originalBin); const encryptedByte = binToByte(encryptedBin); writer.write(Buffer.from([encryptedByte])); }); reader.on('end', () => { writer.end(); console.log(`加密完成!输出文件: ${outputPath}`); }); // 处理错误 reader.on('error', (err) => console.error('读取文件失败:', err)); writer.on('error', (err) => console.error('写入文件失败:', err));
用这个代码处理PNG,就能保证每个字节的原始信息都被正确处理,加密后的文件解密后就能正常打开啦。
内容的提问来源于stack exchange,提问作者RisksY
相关产品推荐
相关产品推荐

