用C++写一个文件加密程序,核心在于理解二进制文件流的操作方式以及基础加密算法的数学本质。加密并不是神秘过程,它只是把原始字节按照预定规则做了可逆变换。C++标准库提供的ifstream和ofstream在以binary模式打开时,可以精确读写每一个字节,这为我们在不依赖第三方库的情况下实现加密打下了基础。

一、为什么要用二进制模式操作文件流
很多初学者用C++读写文件时习惯用文本模式,但在加密场景下必须改用二进制模式。文本模式会在某些系统上做换行符转换,比如Windows下会把换行写成两个字节,读取时又合并,这会破坏原始数据的字节序列,导致加密后再解密出来的文件与源文件不一致。
使用binary模式后,文件中的每一个字节都会原样读入内存,不会做任何解释和转换。我们可以通过read和write方法按块处理数据,既安全又高效。下面这段代码展示了如何以二进制方式打开文件:
#include <fstream>
#include <iostream>
int main() {
// 以二进制读模式打开源文件
std::ifstream in("plain.txt", std::ios::binary);
if (!in) {
std::cerr << "无法打开源文件" << std::endl;
return 1;
}
// 以二进制写模式打开目标文件
std::ofstream out("cipher.bin", std::ios::binary);
if (!out) {
std::cerr << "无法创建输出文件" << std::endl;
return 1;
}
char buffer[1024];
while (in.read(buffer, sizeof(buffer))) {
out.write(buffer, in.gcount());
}
// 处理最后一次不足缓冲区大小的数据
if (in.gcount() > 0) {
out.write(buffer, in.gcount());
}
in.close();
out.close();
return 0;
}
上面的例子虽然只是复制文件,但已经包含了加密程序需要的全部文件流骨架。in.gcount()返回实际读取的字节数,这对于处理文件末尾不完整块非常关键。如果忽略它,可能会把上一次残留的旧数据也写进新文件。
除了基础读写,二进制流还允许我们用seekg和seekp移动文件指针。在更复杂的加密方案中,比如把文件头信息和密文分开存放,就离不开这两个方法。但对于基础加密,顺序读写已经足够。
二、用异或算法实现最基础的加密
异或(XOR)是位运算中的一种,它的特点是:如果A XOR B = C,那么C XOR B = A。这种可逆性正好满足加密和解密使用同一套逻辑的需求。我们把文件中的每个字节与同一个密钥字节做异或,就能得到密文;再次异或同一个密钥就恢复明文。
异或运算速度极快,在C++里直接用^符号即可。下面的示例把前面复制文件的代码改造成加密程序,密钥设定为固定值0x5A:
#include <fstream>
#include <iostream>
int main() {
char key = 0x5A; // 单字节密钥
std::ifstream in("plain.txt", std::ios::binary);
std::ofstream out("cipher.bin", std::ios::binary);
if (!in || !out) {
std::cerr << "文件打开失败" << std::endl;
return 1;
}
char buffer[1024];
std::streamsize count;
while ((count = in.read(buffer, sizeof(buffer)).gcount()) > 0) {
for (std::streamsize i = 0; i < count; ++i) {
buffer[i] = buffer[i] ^ key; // 逐字节异或加密
}
out.write(buffer, count);
}
in.close();
out.close();
std::cout << "加密完成" << std::endl;
return 0;
}
这段程序把每个读出的字节和key做异或,然后直接写入输出文件。解密时只需把cipher.bin作为源文件,用同样的key再跑一遍,就能还原。这种方法的优点是代码极简、性能极高,适合教学和对安全性要求不高的个人文件混淆。
但单字节密钥有明显缺陷:如果攻击者知道密文格式(比如文件头部是PNG签名),就能反推出key。而且相同内容在文件中重复出现时,密文中也会重复出现相同字节,容易暴露规律。因此基础异或通常只作为理解文件流和加密思想的入口。
三、改进密钥:用伪随机流增强安全性
为了克服固定密钥的弱点,可以改用一段密钥流,让每个字节使用不同的密钥值。最简单的方式是用线性同余生成器(LCG)产生伪随机序列,用序列中的值依次异或文件字节。这样即使明文有重复,密文也不再重复。
下面代码演示了一个简单LCG密钥流加密。注意种子可以来自用户输入,解密时必须使用相同种子才能还原:
#include <fstream>
#include <iostream>
// 简单线性同余生成器
unsigned int next_rand(unsigned int& state) {
state = state * 1103515245 + 12345;
return (state >> 16) & 0xFF; // 取高16位中的低8位作为密钥字节
}
int main() {
unsigned int seed = 2024; // 实际使用时可由密码推导
std::ifstream in("plain.txt", std::ios::binary);
std::ofstream out("cipher.bin", std::ios::binary);
if (!in || !out) {
std::cerr << "文件打开失败" << std::endl;
return 1;
}
char buffer[1024];
std::streamsize count;
while ((count = in.read(buffer, sizeof(buffer)).gcount()) > 0) {
for (std::streamsize i = 0; i < count; ++i) {
unsigned char k = static_cast<unsigned char>(next_rand(seed));
buffer[i] = static_cast<char>(static_cast<unsigned char>(buffer[i]) ^ k);
}
out.write(buffer, count);
}
in.close();
out.close();
return 0;
}
这种方案的强度仍然有限,因为LCG本身不是密码学安全的随机数发生器,但比起单字节固定密钥已经好很多。它演示了如何通过改变密钥状态来打乱数据。在实际项目中,若真要保护重要文件,应调用系统提供的加密接口或引入经过审查的加密库。
从文件流角度看,无论密钥多复杂,读写逻辑不变:始终以binary模式打开,用缓冲区分块处理,避免把整个大文件读进内存。这也是C++写加密工具时的通用架构。
四、完整示例与运行注意点
把前面思路整合,我们可以写出一个带简单命令行参数的小程序:第一个参数是源文件,第二个是输出文件,第三个是数字种子。程序根据种子生成密钥流完成加密。解密时交换源和输出并传同样种子即可。
在编译和运行时要留意,若文件大于内存,分块大小可以灵活调整,比如设成4096字节对齐磁盘扇区。另外,用完后务必关闭流,否则缓冲数据可能没刷到磁盘。下面给出整合版框架:
#include <fstream>
#include <iostream>
#include <string>
unsigned int rand_state;
unsigned int next_key() {
rand_state = rand_state * 1103515245 + 12345;
return (rand_state >> 16) & 0xFF;
}
void process(const std::string& in_path, const std::string& out_path, unsigned int seed) {
rand_state = seed;
std::ifstream in(in_path, std::ios::binary);
std::ofstream out(out_path, std::ios::binary);
char buf[4096];
std::streamsize n;
while ((n = in.read(buf, sizeof(buf)).gcount()) > 0) {
for (std::streamsize i = 0; i < n; ++i) {
unsigned char k = static_cast<unsigned char>(next_key());
buf[i] = static_cast<char>(static_cast<unsigned char>(buf[i]) ^ k);
}
out.write(buf, n);
}
}
int main(int argc, char* argv[]) {
if (argc < 4) {
std::cerr << "用法: prog 源文件 输出文件 种子" << std::endl;
return 1;
}
std::string in = argv[1];
std::string out = argv[2];
unsigned int seed = std::stoul(argv[3]);
process(in, out, seed);
std::cout << "处理完成" << std::endl;
return 0;
}
这个例子已经具备实用雏形。你可以把它扩展成先判断文件存在性、增加密码到种子的哈希派生等功能。核心始终没变:用好C++的二进制文件流,配合基础位运算,就能在几十行内实现自己的文件加密工具。
总结来说,C++实现文件加密程序并不神秘。掌握ifstream和ofstream的binary模式、理解异或的可逆性、学会用缓冲区分块读写,就已经解决了大部分工程问题。后续若想提升安全等级,再逐步引入标准加密算法即可。