基于QT实现的SSL协议的安全报文发送接收设计
目录
一、设计方案及可行性分析 2
1、任务要求 2
2、设计方案 2
二、详细设计思路 4
1、系统体系结构及技术选择 4
2、关键数据类型的定义 6
3、关键程序流程图 7
1)发送方流程: 7
2)接收方流程: 7
三、设计特色 8
1、国密算法 8
2、数字签名 10
3、数字证书 11
4、证书生成 12
1)生成自签证书和对应私钥的指令 12
2)将DER格式证书转换为PEM格式指令 12
3)将私钥和X509证书合并生PFX证书指令 12
5、QT 12
1)Qt事件 12
2)信号和槽 13
四、源代码及注释 13
代码链接: 14
1、发送端 14
2、接收端 23
五、使用流程 34
1、建立连接 34
2、选择证书 35
3、发送报文 36
4、接收报文 38
5、进行验签 40
6、密码正误判断 40
六、总结报告 41
1、小组贡献排序及依据: 41
2、个人报告20191313戴君熹: 41
1)个人贡献 41
2)遇到的问题及解决办法 41
3)体会及收获 42
4)参考资料 43
3、个人报告20191315郝嘉乐: 43
1)个人贡献 43
2)遇到的问题及解决方法 43
3)体会收获 46
4)参考资料 46
4、个人报告20191323王予涵: 47
1)个人贡献 47
2)遇到的问题及解决办法 47
3)设计体会及收获 48
4)参考资料 48
二、详细设计思路
1、系统体系结构及技术选择
1)C语言
C语言是一门面向过程的、抽象化的通用程序设计语言,广泛应用于底层开发。C语言能以简易的方式编译、处理低级存储器。C语言是仅产生少量的机器语言以及不需要任何运行环境支持便能运行的高效率程序设计语言。尽管C语言提供了许多低级处理的功能,但仍然保持着跨平台的特性,以一个标准规格写出的C语言程序可在包括类似嵌入式处理器以及超级计算机等作业平台的许多计算机平台上进行编译。
2)Openssl
OpenSSL是一个开放源代码的软件库包,应用程序可以使用这个包来进行安全通信,避免窃听,同时确认另一端连接者的身份。这个包广泛被应用在互联网的网页服务器上。
OpenSSL采用C语言作为开发语言,这使得OpenSSL具有优秀的跨平台性能,这对于广大技术人员来说是一件非常美妙的事情,可以在不同的平台使用同样熟悉的东西。OpenSSL支持Linux、Windows、BSD、Mac、VMS等平台,这使得OpenSSL具有广泛的适用性。但习惯C语言总比使用C++重新写一个跟OpenSSL相同功能的软件包轻松不少。
openSSL整个软件包大概可以分成三个主要的功能部分:SSL协议库、应用程序以及密码算法库。OpenSSL的目录结构自然也是围绕这三个功能部分进行规划的。
作为一个基于密码学的安全开发包,OpenSSL提供的功能相当强大和全面,囊括了主要的密码算法、常用的密钥和证书封装管理功能以及SSL协议,并提供了丰富的应用程序供测试或其它目的使用。
3)Linux
Linux,全称GNU/Linux,是一种免费使用和自由传播的类UNIX操作系统,其内核由林纳斯·本纳第克特·托瓦兹于1991年10月5日首次发布,它主要受到Minix和Unix思想的启发,是一个基于POSIX的多用户、多任务、支持多线程和多CPU的操作系统。它能运行主要的Unix工具软件、应用程序和网络协议。它支持32位和64位硬件。Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。Linux有上百种不同的发行版,如基于社区开发的debian、archlinux,和基于商业开发的Red Hat Enterprise Linux、SUSE、Oracle Linux等。
伴随着互联网的发展,Linux得到了来自全世界软件爱好者、组织、公司的支持。它除了在服务器方面保持着强劲的发展势头以外,在个人电脑、嵌入式系统上都有着长足的进步。使用者不仅可以直观地获取该操作系统的实现机制,而且可以根据自身的需要来修改完善Linux,使其最大化地适应用户的需要。
Linux不仅系统性能稳定,而且是开源软件。其核心防火墙组件性能高效、配置简单,保证了系统的安全。在很多企业网络中,为了追求速度和安全,Linux不仅仅是被网络运维人员当作服务器使用,甚至当作网络防火墙,这是Linux的一大亮点。
Linux具有开放源码、没有版权、技术社区用户多等特点,开放源码使得用户可以自由裁剪,灵活性高,功能强大,成本低。尤其系统中内嵌网络协议栈,经过适当的配置就可实现路由器的功能。这些特点使得Linux成为开发路由交换设备的理想开发平台。
4)Microsoft Visual Studio
Microsoft Visual Studio(简称VS)是美国微软公司的开发工具包系列产品。VS是一个基本完整的开发工具集,它包括了整个软件生命周期中所需要的大部分工具,如UML工具、代码管控工具、集成开发环境(IDE)等等。所写的目标代码适用于微软支持的所有平台,包括Microsoft Windows、Windows Mobile、Windows CE、.NET Framework、.NET Compact Framework和Microsoft Silverlight 及Windows Phone。
Visual Studio是最流行的Windows平台应用程序的集成开发环境。最新版本为 Visual Studio 2022 版本。
5)Qt Creator
Qt Creator是一个用于Qt开发的轻量级跨平台集成开发环境。Qt Creator可带来两大关键益处:提供首个专为支持跨平台开发而设计的集成开发环境 (IDE),并确保首次接触Qt框架的开发人员能迅速上手和操作。即使不开发Qt应用程序,Qt Creator也是一个简单易用且功能强大的IDE。
Qt具有优良的跨平台特性,支持下列操作系统: Microsoft Windows 95/98, Microsoft Windows NT, Linux, Solaris, SunOS, HP-UX, Digital UNIX (OSF/1, Tru64), Irix, FreeBSD, BSD/OS, SCO, AIX, OS390,QNX 等等。
Qt的良好封装机制使得 Qt 的模块化程度非常高,可重用性较好,对于用户开发来说是非常方便的。Qt提供了一种称为 signals/slots 的安全类型来替代 callback,这使得各个元件之间的协同工作变得十分简单。
Qt 包括多达 250 个以上的 C++ 类,还提供基于模板的 collections,serialization, file, I/O device,directory management,date/time类。甚至还包括正则表达式的处理功能。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/err.h>
#include <openssl/objects.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/pkcs12.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
int main(int argc, char *argv[])
{
BIO *bio;
int rv;
PKCS12 *p12 = NULL; //保存发送者私钥的PKCS12结构体指针
X509 *RecvCert = NULL; //保存接收者证书的X509结构体指针
EVP_PKEY *pkey = NULL; //保存发送者私钥的EVP_PKEY结构体指针
X509 *SignCert = NULL; //保存发送者证书的X509结构体指针
unsigned char *tmp;
unsigned char recvCertBuf[4096]; //保存接收者证书的数组
unsigned int recvCertBufLen; //接收者证书长度
unsigned char PfxBuf[4096]; //保存发送者P12文件的数组
unsigned int PfxBufLen; //p12文件长度
unsigned char Buffer[4096]; //保存待处理的文件数据的数组
unsigned int BufferLen; //数据长度
unsigned char SessionKey[128]; //加密发送文件的会话密钥
unsigned char CipherSessionKey[256];//会话密钥的密文
unsigned int CipherSessionKeyLen; //会话密钥的密文长度
unsigned char Digest[512]; //待发送文件的摘要值
unsigned int DigestLen; //摘要长度
long unsigned int siglen; //签名长度
EVP_MD_CTX *mdctx; //计算摘要的上下文
FILE *fp; //文件句柄
EVP_PKEY_CTX *Signctx; //rsa签名上下文
unsigned char *sig; //签名值
char err[1024];
OpenSSL_add_all_algorithms();
mdctx = EVP_MD_CTX_new();
//打开接收者证书
fp = fopen(argv[1],"rb");
if(fp==NULL)
{
fprintf(stderr,"File to open %s\n", argv[1]);
return 0;
}
recvCertBufLen = fread(recvCertBuf,1,4096,fp);
fclose(fp);
//打开发送者PFX(p12)文件
fp = fopen(argv[2],"rb");
if(fp==NULL)
{
fprintf(stderr,"File to open %s\n", argv[2]);
return 0;
}
PfxBufLen = fread(PfxBuf,1,4096,fp);
fclose(fp);
//把接收者证书转化为X509结构体
tmp = recvCertBuf;
RecvCert = d2i_X509(NULL,(const unsigned char **)&tmp,recvCertBufLen);
if(RecvCert == NULL)
{
fprintf(stderr,"File to transform %s\n", argv[1]);
return 0;
}
//把P12文件转化为PKCS12结构体
bio = BIO_new(BIO_s_mem());
rv = BIO_write(bio,PfxBuf,PfxBufLen);
p12 = d2i_PKCS12_bio(bio, NULL);
if(p12 ==NULL)
{
X509_free(RecvCert);
BIO_free_all(bio);
fprintf(stderr,"File to transform %s\n", argv[2]);
return 0;
}
BIO_free_all(bio);
char Passwd[1024];
printf("请输入pkcs12证书密码\n");
scanf("%s",Passwd);
//从PKCS12结构体中解析获得私钥和证书
rv = PKCS12_parse(p12, Passwd,&pkey,&SignCert,NULL);
if(rv != 1)
{
X509_free(RecvCert);
PKCS12_free(p12);
printf("密码错误!\n");
return 0;
}
//对待发送文件签名
mdctx = EVP_MD_CTX_new();
EVP_MD_CTX_init(mdctx); //初始化摘要上下文
if(!EVP_SignInit_ex(mdctx, EVP_sha3_256(), NULL)) //设置摘要算法,这里选择SM3
{
EVP_PKEY_free(pkey);
X509_free(SignCert);
X509_free(RecvCert);
PKCS12_free(p12);
printf("初始化摘要失败!\n");
return 0;
}
//打开待发送的文件
fp = fopen(argv[3],"rb");
if(fp == NULL)
{
EVP_PKEY_free(pkey);
X509_free(SignCert);
X509_free(RecvCert);
PKCS12_free(p12);
return 0;
}
//不断循环,以4096字节为单位读取文件,并摘要
for(;;)
{
BufferLen=fread(Buffer,1,4096,fp);//每次读取4096个字节
if(BufferLen <=0)
break;
if(!EVP_SignUpdate(mdctx, Buffer, BufferLen))//摘要
{
EVP_PKEY_free(pkey);
X509_free(SignCert);
X509_free(RecvCert);
PKCS12_free(p12);
fclose(fp);
printf("签名摘要生成失败EVP_SignUpdate\n");
return 0;
}
}
fclose(fp);
if(!EVP_DigestFinal(mdctx,Digest,&DigestLen))//完成摘要,获得摘要值
{
EVP_PKEY_free(pkey);
X509_free(SignCert);
X509_free(RecvCert);
PKCS12_free(p12);
ERR_error_string(ERR_get_error(),err);
printf("签名摘要生成摘要失败EVP_SignFinal %s\n",err);
return 0;
}
//签名开始
Signctx = EVP_PKEY_CTX_new(pkey, NULL /* no engine */);
if (!Signctx)
{
fprintf(stderr,"EVP_PKEY_CTX_new Fail!");
EVP_PKEY_free(pkey);
X509_free(SignCert);
X509_free(RecvCert);
PKCS12_free(p12);
return 0;
}
if (EVP_PKEY_sign_init(Signctx) <= 0)
{
fprintf(stderr,"EVP_PKEY_sign_init Fail!");
EVP_PKEY_free(pkey);
X509_free(SignCert);
X509_free(RecvCert);
PKCS12_free(p12);
return 0;
}
if (EVP_PKEY_CTX_set_rsa_padding(Signctx, RSA_PKCS1_PADDING) <= 0)
{
fprintf(stderr,"EVP_PKEY_CTX_set_rsa_padding Fail!");
EVP_PKEY_free(pkey);
X509_free(SignCert);
X509_free(RecvCert);
PKCS12_free(p12);
return 0;
}
if (EVP_PKEY_CTX_set_signature_md(Signctx, EVP_sha3_256()) <= 0)
{
fprintf(stderr,"EVP_PKEY_CTX_set_signature_md Fail!");
ERR_error_string(ERR_get_error(),err);
printf("%s\n",err);
EVP_PKEY_free(pkey);
X509_free(SignCert);
X509_free(RecvCert);
PKCS12_free(p12);
return 0;
}
if (EVP_PKEY_sign(Signctx, NULL, &siglen, Digest, DigestLen) <= 0)
{
fprintf(stderr,"EVP_PKEY_sign Fail!");
EVP_PKEY_free(pkey);
X509_free(SignCert);
X509_free(RecvCert);
PKCS12_free(p12);
return 0;
}
sig = OPENSSL_malloc(siglen);
if (!sig)
{
fprintf(stderr,"OPENSSL_malloc Fail!");
EVP_PKEY_free(pkey);
X509_free(SignCert);
X509_free(RecvCert);
PKCS12_free(p12);
return 0;
}
if (EVP_PKEY_sign(Signctx, sig, &siglen, Digest, DigestLen) <= 0)
{
fprintf(stderr,"EVP_PKEY_sign Fail!");
EVP_PKEY_free(pkey);
X509_free(SignCert);
X509_free(RecvCert);
PKCS12_free(p12);
return 0;
}
/*unsigned char real_Sign[512];
unsigned int real_Sign_Len;
real_Sign_Len = EVP_PKEY_encrypt_old(real_Sign,Sign,SignLen,pkey);
*/
/*
if(!EVP_SignFinal(mdctx,Sign,&SignLen,pkey))//完成签名,获得签名值
{
EVP_PKEY_free(pkey);
X509_free(SignCert);
X509_free(RecvCert);
PKCS12_free(p12);
ERR_error_string(ERR_get_error(),err);
printf("签名失败EVP_SignFinal %s\n",err);
return 0;
}
*/
//释放密钥和证书以及pkcs12结构体
EVP_PKEY_free(pkey);
X509_free(SignCert);
PKCS12_free(p12);
//产生随机数,作为会话密钥
RAND_bytes(SessionKey,128);
//使用接收者证书公钥加密会话密钥
CipherSessionKeyLen = EVP_PKEY_encrypt_old(CipherSessionKey,SessionKey,128,X509_get_pubkey(RecvCert));
if(CipherSessionKeyLen <= 0)
{
X509_free(RecvCert);
return 0;
}
X509_free(RecvCert);
//利用会话密钥加密原文,并输出到密文到文件
FILE *fpIn;
FILE *fpOut;
fpIn = fopen(argv[3],"rb");
if(fpIn == NULL)
{
return 0;
}
fpOut = fopen(argv[4],"wb");
if(fpOut == NULL)
{
return 0;
}
//密文文件格式:
// |------------------|--------|------------------------|------------|--------------
// |签名信息长度4Bytes|签名信息|会话密钥的密文长度4Bytes|会话密钥密文|原文数据的密文
fwrite(&siglen,1,sizeof(siglen),fpOut);//写签名长度到文件
fwrite(sig,1,siglen,fpOut); //写签名值到文件
fwrite(&CipherSessionKeyLen,1,sizeof(CipherSessionKeyLen),fpOut); //写密文的会话密钥的长度到文件
fwrite(CipherSessionKey,1,CipherSessionKeyLen,fpOut);//写密文的会话密钥到文件
OPENSSL_free(sig);
EVP_CIPHER_CTX *ctx;
ctx = EVP_CIPHER_CTX_new();
unsigned char out[1024];
int outl;
unsigned char in[1024];
int inl;
EVP_CIPHER_CTX_init(ctx);//初始化密码算法上下文
//设置密码算法和密钥,这里采用128位的sm4算法。
rv = EVP_EncryptInit_ex(ctx,EVP_sm4_ecb(),NULL,SessionKey,NULL);
if(rv != 1)
{
EVP_CIPHER_CTX_free(ctx);
return 0;
}
//以1024字节为单位,循环读取原文,并加密,然后输出到密文文件。
for(;;)
{
inl = fread(in,1,1024,fpIn);//读取1024字节
if(inl <= 0)
break;
rv = EVP_EncryptUpdate(ctx,out,&outl,in,inl);//加密
if(rv != 1)
{
fclose(fpIn);
fclose(fpOut);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
fwrite(out,1,outl,fpOut);//输出密文到文件
}
rv = EVP_EncryptFinal_ex(ctx,out,&outl);//完成加密,输出剩余的密文。
if(rv != 1)
{
fclose(fpIn);
fclose(fpOut);
EVP_CIPHER_CTX_cleanup(ctx);
return 0;
}
fwrite(out,1,outl,fpOut);//写文件
fclose(fpIn);
fclose(fpOut);
EVP_CIPHER_CTX_free(ctx);
return 0;
}