返回课程

完整的C语言加密解密软件

前面几篇文章为软件的编写做了铺垫,这里给出完整的软件源码,大家可以下载下来自己学习和编译,下载地址在本文最后。

一. 软件简介

文件加密解密是常用的一个功能,可以很好的保护你的隐私内容;本软件简单的实现了文件加密解密的功能。

为了防止别人随意使用软件,软件本身有一个密码,必须输入密码才能使用软件。

软件的功能有:
  • 加密文件:打开需要加密的文件,输入密钥,将加密的文件保存;
  • 解密文件:打开需要解密的文件,输入密钥,将解密的文件保存;
  • 修改密码:可以临时修改软件的使用密码,但是软件重启后会被重置;
  • 退出程序:完成工作后退出。

二. 软件使用流程

1) 启动软件,输入密码

软件启动后,必须输入密码才能使用(默认密码为 123456)。用户有3次输入密码的机会,连续3次输入错误软件将自动退出。截图如下: 

图1  软件启动时校验密码
 

2) 密码校验正确,显示功能菜单

密码校验完成后,会显示功能菜单,让用户选择要进行的操作,如下图所示:
 
图2  软件菜单

3) 用户选择要进行的操作

①文件加密
输入a并回车,进入文件加密功能,要求用户输入要加密的文件名、密钥、保存加密内容的文件。
 
在D盘下创建文件demo.txt,输入一些文本,对该文件进行加密。加密成功后,用记事本打开加密的文件,看到的全部是乱码,截图如下:
 
图3  加密成功
 
如果输入的文件路径错误,或者打开/创建文件失败,将导致加密失败,截图如下:
 
图4  加密失败
 
注意:文件名要包含路径,只有文件名表示当前程序所在文件夹下的文件。
 
②文件解密
回到主菜单,输入b并回车,进入文件解密功能,同样要求用户输入要解密的文件、密钥和保存解密内容的文件。输入上次加密的文件,将解密后的文件保存到demo_decode.txt,解密成功后打开demo_decode.txt,可以看到被还原的具有可读性的内容。截图如下:
 
图5  解密文件成功
 
③修改密码
回到主菜单,输入c并回车,进入密码修改功能。修改密码前要求用户输入原来的密码,并两次输入新密码,保证不会误输入。截图如下:
 
图6  修改密码成功
 
④退出系统
回到主菜单,输入z退出软件。

软件下载地址:http://pan.baidu.com/s/1mgl2DF6     提取密码:v41l

读取文件内容并加密,然后保存

最后,让我们来看看文件加密的具体细节,如何从文件中读取内容,如何加密,如何将加密后的内容保存到新文件。

请先看下面的代码:
#include <stdio.h>  // 标准输入输出函数
#include <stdlib.h>  // 标准库函数
#include <string.h>  // 字符串处理函数

int encryptFile(char *sourcefile, char *secretKey, char *targetFile);

int main(){
    char sourcefile[30],  // 加密的文件名
            targetFile[30],  // 加密后要保存的文件名
            secretKey[21];  // 文件加密的密钥

    printf("输入要加密的文件名(含路径):");
    scanf("%s", sourcefile);
    printf("输入密钥:");  //密钥是用户自己定义的,可以随意给需要加密的文件添加密钥
    scanf("%s", secretKey);
    printf("加密后的文件名(含路径):");  //给加密后的文件命名,并保存
    scanf("%s",targetFile);
    if( encryptFile(sourcefile, secretKey, targetFile) ){
        printf("恭喜你,文件[%s]加密成功,保存在[%s]。\n", sourcefile, targetFile);
    }
}

/**
* 加密文件
*
* @param   sourcefile    要加密的文件名
* @param   secretKey     密钥
* @param   targetFile    加密后要保存的文件名
*
* @return  加密成功或失败的数字表示
     0:加密失败
     1:加密成功
**/
int encryptFile(char *sourcefile, char *secretKey, char *targetFile){
    FILE *fpSource, *fpTarget;  // 要打开的文件的指针
    char buffer[21];  // 缓冲区,用于存放从文件读取的数据
    int readCount,  // 每次从文件中读取的字节数
        keyLen = strlen(secretKey),  // 密钥的长度
        i;  // 循环次数

    // 以二进制方式读取/写入文件
    fpSource = fopen(sourcefile, "rb");
    if(fpSource==NULL){
        printf("文件[%s]打开失败,请检查文件路径和名称是否输入正确!\n", sourcefile);
        return 0;
    }
    fpTarget = fopen(targetFile, "wb");
    if(fpTarget==NULL){
        printf("文件[%s]创建/写入失败!请检查文件路径和名称是否输入正确!\n", fpTarget);
        return 0;
    }

    // 不断地从文件中读取 keyLen 长度的数据,保存到buffer,直到文件结束
    while( (readCount=fread(buffer, 1, keyLen, fpSource)) > 0 ){
        // 对buffer中的数据逐字节进行异或运算
        for(i=0; i<readCount; i++){
            buffer[i] ^= secretKey[i];
        }
        // 将buffer中的数据写入文件
        fwrite(buffer, 1, readCount, fpTarget);
    }

    fclose(fpSource);
    fclose(fpTarget);

    return 1;
}
运行结果:


这里重点说一下 54 ~ 61行,这是加密的关键代码。

用户输入的密钥保存在 secretKey 变量,密钥的长度为keyLen(最长为20),fpSource为要加密的文件,fpTarget为存放加密内容的文件。代码一次从fpSource文件中读取keyLen个字节的数据,暂存到缓冲区buffer,将buffer中的数据与密钥secretKey逐字节进行异或运算,再将结果写入到fpTarget。
 
这里注意:虽然代码期望从fpSource中读取keyLen个字节,但是到文件结尾时读取的字节数可能小于keyLen,所以应该以实际读取到的字节长度readCount为准:
  • 如果readCount=keyLen,那么secretKey的每个字节都会参与异或运算;
  • 如果readCount<keyLen,那么secretKey只有前readCount个字节参与异或运算。
  • 密钥越长,keyLen越大,破解难度就越大。

说明:这是最粗糙最容易的加密算法,很容易破解,尤其是要加密的数据只有几个字节时,暴利破解几乎不费吹灰之力。例如,当对字符 'a' 进行加密时,即使输入的密码很长,也只有第一个字节有用,一个字节只有2^8=256种字符,遍历这256个字符,总有一个是对的。

较为安全的加密算法将在《算法》中讲解,这里重在教学演示,让大家知道写代码的思路。

设置软件密码,防止他人使用

软件发布后,如果不希望未经授权的用户使用,可以设置软件密码,启动软件后,必须正确输入密码才能进行后续操作。

作为教学演示,这里简单地将密码赋值给一个变量,编译后不能更改,如果想更改,必须重新编译。

用户有3次输入密码的机会,3次输入错误,软件自动退出。通过for循环,连续3次读取用户输入的密码,并与初始密码进行比较,相同则校验通过,否则校验失败。

请看下面的代码:
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

int checkPwd();
void myExit(char *msg);

int  password=123456;  // 软件初始密码

int main(){
    if(!checkPwd()){
        myExit("抱歉,3次输入密码错误。按任意键退出程序...\n");
    }

    myExit("恭喜你,密码检验成功。按任意键退出程序...\n");

    return 0;
}

// 校验密码
// 返回 0 表示校验失败,返回 1 表示校验成功
int checkPwd(){
    int pwd;  //用户输入的密码
    int trytimes;  // 用户尝试输入密码的次数
   
    printf("程序设置了密码,验证通过后才能使用。请输入6位数字密码:");
    // 可以输入三次
    for(trytimes=1; trytimes<=3; trytimes++){
        scanf("%d",&pwd);
        fflush(stdin);
        if(pwd==password){
            return 1;
            break;
        }else if(trytimes==3){
            return 0;
        }else{
            printf("抱歉,密码错误,您还有%d次机会:", 3-trytimes);
        }
    }
}

// 自定义退出程序函数
// msg 为提示语
void myExit(char *msg){
    printf("%s", msg);
    getch();
    exit(1);
}
运行结果:

C语言循环菜单的设计

大家写C语言程序有没有发现一个问题:程序运行一次就结束了,要执行同样的操作必须重新运行程序,这样很麻烦,也很low,像QQ、360这样的软件为什么可以一直运行,重复执行同样的操作呢?

其实,C语言写的程序也可以这样,但是由于纯C很难做出漂亮的界面,只能粗糙的实现一下。

让程序一直运行的秘密就是死循环,即:
while(1){
    // 要执行的操作
}

在加密解密软件中,要实现的功能有:
  • 文件加密;
  • 文件解密;
  • 修改密码(为了防止其他人使用,程序设置了密码,必须输入密码才能使用程序);
  • 退出系统。

相应的主菜单也有这些,请看下面的代码:
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

// 函数原型
void printMenu();
void returnToMenu(char *msg);

int main(){
    char action;
    while(1){
        system("cls");  // 清屏
        printMenu();  // 显示主菜单
        scanf("%c",&action);  // 输入要执行的操作
        fflush(stdin);  // 刷新(清空)stdin缓冲区
        system("cls");
       
        switch(action){
            case 'a':
            case 'A':
                returnToMenu("文件加密成功!");
                break;

            case 'b':
            case 'B':
                returnToMenu("文件解密成功!");
                break;

            case 'c':
            case 'C':
                returnToMenu("更改密码成功!");
                break;

            case 'z':
            case 'Z':
                exit(0);

            default:
                returnToMenu("没有相应的菜单!");
        }

    } 
    return 0;
}

// 打印主菜单
void printMenu(){
    printf("******************* 文本加密解密软件 *******************\n");
    printf("*                                                      *\n");
    printf("*      请从下面的菜单中选择你要进行的操作:            *\n");
    printf("*      a. 文件加密                                     *\n");
    printf("*      b. 文件解密                                     *\n");
    printf("*      c. 更改密码                                     *\n");
    printf("*      z. 退出系统                                     *\n");
    printf("*                                                      *\n");
    printf("********************************************************\n");
}

// 返回主菜单
// msg 为提示语
void returnToMenu(char *msg){
    system("cls");
    printf("%s\n", msg);
    printf("按任意键回到主菜单...\n");
    // getch()读取用户输入的字符,无缓冲无回显
    // 可以优雅地实现让程序“暂停一下”的功能
    getch();
}
运行结果示例:


输入 a 或 A:


按任意键,又回到了上面的菜单(主菜单)。大家可以亲自演示一下这段代码,会有更加直观的动态效果。

上面的程序通过 while(1) 循环,不断执行用户操作,直到用户输入z退出系统。

这里有几点需要说明一下。

1) system() 函数位于stdlib.h头文件,用来执行dos命令,system("cls") 用来清屏,与dos里面的cls命令功能相同。

2) fflush() 函数用来刷新文件缓冲区,fflush(stdin) 用来刷新标准输入缓冲区,对缓冲区不理解的同学请看这里:对C语言输入输出流和缓冲区的理解

这里为什么需要强制刷新缓冲区呢?

scanf("%c",&action); 读取字符时,按下回车键才能结束输入,这时的缓冲区中不仅有我们输入的字符,还有回车换行符(\n)。scanf() 第一次读取我们输入的字符,并将该字符移出缓冲区,但是换行符\n还留在了缓冲区,下次执行scanf()不会等待用户输入,而是直接从缓冲区读取内容,就是\n,然后将\n赋值给action,再通过switch来匹配,这显然不是我们想要的结果。大家可以把fflush(stdin);注释掉,再看看是什么效果。

3) getch() 位于conio.h头文件,用来读取一个字符,无缓冲区,无回显,所以按下任意键(输入一个字符),不会将该键对应的字符显示在屏幕上,也不会到达stdin缓冲区,可以优雅的实现按任意键继续的功能。

与 system("pause") 相比,getch() 没有提示语,如果需要,可以在前面使用printf()输出,实现“私人定制”。

C语言文件加密的原理

文件加密解密是一个常用的功能,如果你不希望让别人看到文件中的内容,可以通过密钥(也称”密码“)将文件的内容加密。比如文本文件(.txt),加密前的内容是能够读懂的,加密后的内容是”乱码“,都是一些奇怪的字符,根本无法阅读。

文件加密解密的原理也很简单,就是使用异或运算。请先看下面的代码:
#include <stdio.h>
#include <stdlib.h>

int main(){
    char plaintext = 'a';  // 明文
    char secretkey = '!';  // 密钥
    char ciphertext = plaintext ^ secretkey;  // 密文
    char decodetext = ciphertext ^ secretkey;  // 解密后的字符
    char buffer[9];

    printf("            char    ASCII\n");
    // itoa()用来将数字转换为字符串,可以设定转换时的基数
    // 这里采用二进制,将字符对应的ascii码转换为二进制
    printf(" plaintext   %c     %7s\n", plaintext, itoa(plaintext, buffer, 2));
    printf(" secretkey   %c     %7s\n", secretkey, itoa(secretkey, buffer, 2));
    printf("ciphertext   %c     %7s\n", ciphertext, itoa(ciphertext, buffer, 2));
    printf("decodetext   %c     %7s\n", decodetext, itoa(decodetext, buffer, 2));

    return 0;
}
运行结果:
            char    ASCII
 plaintext   a     1100001
 secretkey   !      100001
ciphertext   @     1000000
decodetext   a     1100001

看到了吗,plaintext 与 decodetext相同,也就是说,两次异或运算后还是原来的结果。

这就是加密的关键技术:
  • 通过一次异或运算,生成密文,密文没有可读性,与原文风马牛不相及,这就是加密;
  • 密文再经过一次异或运算,就会还原成原文,这就是解密的过程;
  • 加密和解密需要相同的密钥,如果密钥不对,是无法成功解密的。

上面的加密算法称为对称加密算法,加密和解密使用同一个密钥。

如果加密和解密的密钥不同,则称为非对称加密算法。在非对称算法中,加密的密钥称为公钥,解密的密钥称为私钥,只知道公钥是无法解密的,还必须知道私钥。

注意:程序中的 itoa() 并不是一个标准的C函数,它是Windows特有的,了解更多请查看:C语言itoa()函数和atoi()函数详解

C语言入门教程:c语言从入门到精通|C语言程序设计 谭浩强


  • 扫一扫 扫二维码继续学习