第一次接触文件加密时,我也被各种专业术语吓到过。但后来发现,用最基本的ASCII码配合简单数学方程,就能做出实用的加密工具。这个方案特别适合刚学完C语言文件操作的同学练手,既能巩固基础知识,又能做出看得见摸得着的成果。
ASCII码本质上是字符的"身份证号码"。比如字母'A'对应65,'a'对应97。加密的原理就是把这些数字通过数学公式变形,比如用f(x)=2x²+3这样的方程处理。当数字变得面目全非,对应的字符也就成了乱码。解密时只需要反向计算就能还原。
实际开发中会遇到几个典型问题:首先是文本模式和二进制模式的区别。文本模式会处理换行符转换,而二进制模式则是原样读写。我曾在Windows平台调试时,因为没注意这个区别导致文件位置错乱,最后用fseek怎么都定位不准。后来改用二进制模式统一处理才解决。
另一个坑是汉字等双字节字符。它们的ASCII码是负数,比如"中"字用两个负数表示。如果直接套用加密方程,解密时平方根运算会丢失符号信息。我的解决方案是用额外数组标记汉字位置,解密时自动补上负号。
先来看基础结构体设计。我用student结构存储学号、姓名等个人信息,这样比分散变量更清晰。内存分配要预留足够空间,特别是中文内容可能占更多字节。曾经因为malloc太小导致写入越界,程序随机崩溃,调试了半天才发现。
c复制typedef struct student {
char stn[SIZE]; // 学号
char fn[SIZE]; // 名
char ln[SIZE]; // 姓
char gen[SIZE]; // 性别
char maj[SIZE]; // 专业
char aod[SIZE]; // 宿舍地址
char nor[SIZE]; // 室友名单
} student;
文件操作有个关键细节:写入个人信息后要用ftell记录文件指针位置。因为后面要追加加密内容,而加密内容是用二进制方式写入的整数数组。如果不记录位置,解密时无法准确定位加密数据的起始点。这个坑我踩过,导致解密时总读到垃圾数据。
加密函数的核心是遍历每个字符,对其ASCII码应用自定义方程。这里有个优化点:直接使用char类型运算可能会溢出,所以我转用int数组存储中间结果。比如用f(x)=3x²+5时,字母'A'(65)会变成36565+5=12680,远超char的范围。
c复制void encrypt(char* p1, int* p2, int a, int b, int* size, int* countc) {
int x, count = 0;
char* p = p1;
int* q = p2;
while (*p != '\0') {
x = *p;
*q = a * x * x + b; // 核心加密方程
if (x < 0) *(countc + count) = 1; // 标记汉字
else *(countc + count) = 0;
p++; q++; count++;
}
*size = count;
}
处理中文时要特别注意:汉字的ASCII码是负数,比如"你好"可能对应-70,-90等。加密后虽然数值变了,但解密时需要知道原始字符是否是汉字。我的解决方案是用countc数组做标记,1表示该位置原是汉字,解密时自动取负。
解密时首先要以二进制模式重新打开文件。这里容易犯的错误是忘记用fseek跳转到加密数据起始位置。之前我直接读取,结果把之前写入的个人信息也读出来了,解密自然失败。正确的做法是:
c复制fseek(pf, length, SEEK_SET); // 跳转到之前记录的位置
fread(fstr_int, sizeof(int), size, pf); // 读取加密数据
读取长度必须与加密时写入的长度一致。我有次测试时误传了错误的size值,导致多读了数据,程序直接段错误。现在会在加密阶段就打印size值,解密时再次确认,避免这类问题。
解密函数要处理的关键问题是平方根运算后的符号确定。对于普通ASCII字符,直接计算即可。但汉字需要根据之前的标记恢复负号:
c复制void decrypt(char* p1, int* p2, int a, int b, int size, int* countc) {
int x, count = 0;
char* p = p1;
int* q = p2;
while (count < size) {
x = *q;
*p = sqrt((x - b) / a); // 核心解密计算
if (*(countc + count)) *p = -*p; // 恢复汉字负号
p++; q++; count++;
}
*p = '\0'; // 字符串终止符
}
测试发现当加密方程参数选择不当时,可能出现(x-b)/a为负数的情况,导致sqrt报错。后来我增加了参数校验,要求a必须为正数,且加密时检查中间结果是否有效。这也提醒我们:防御性编程很重要,不能假设用户输入总是合理的。
动态内存分配是C项目的重点难点。我的习惯是:
c复制address = (char*)malloc(1000); // 路径缓冲区
str = (char*)malloc(10000); // 原始字符串
fstr = (char*)malloc(10000); // 解密字符串
// ...使用过程...
free(str); str = NULL; // 规范释放
曾经遇到过一个隐蔽的bug:加密内容写入文件后,没有立即关闭文件就直接尝试读取。在Windows上这会导致读取失败,因为写入缓冲可能还未真正写入磁盘。后来养成习惯,关键操作后都加fflush或fclose确保数据落地。
基础版本虽然能用,但安全性有限。可以考虑以下改进:
c复制// 改进版加密示例
int salt = rand() % 100;
*q = a * (x + salt) * (x + salt) + b;
实际测试发现,简单的二次方程加密,在知道算法的情况下容易被破解。后来我改为在程序启动时随机生成a、b参数,并将参数加密后存入文件头部,这样不同文件的加密方式都不同,安全性显著提高。
文件操作时要注意跨平台兼容性。比如Windows和Linux的换行符不同,二进制模式可以避免这个问题。还有一次在Mac上测试时,发现文件路径的斜杠方向不对,导致无法创建文件。现在统一用正斜杠"/",在所有平台都能正常工作。