该项目是我在全面、系统地学习完Linux系统编程和网络编程后,在网上找的开源项目,也是我的练手项目之一。该项目是一个典型的C/S模型的全栈项目,涉及到TCP/IP网络通信、多线程并发、OpenSSL数据的加解密、Protobuf序列化与反序列化、MySQL数据库等众多知识点,因为觉得这个项目具有较强的综合性和可拓展性,因此将其作为我的Linux C++练手项目,项目从开始到完成一共经历了三个月时间,在这三个月里,我重写了该项目的全部源代码,对代码的可维护性以及程序的执行效率做出了改进和优化,并在源代码的基础上增加了更多的功能。在这段时间里,我每天都沉浸在项目相关知识点的学习中,虽然在做项目的过程中遇到了不少困难,但是最终也都一一解决了,遇到知识盲区就去学,遇到bug就去查,这在一定程度上也锻炼了我分析问题、解决问题的能力,该项目让我更加熟悉了Linux环境下C/C++的开发,让我对高并发通信有了更深刻的理解。项目完成后,无论是在Linux操作系统的使用方面、项目开发环境的部署方面还是C++本身的代码能力方面,我认为我都有了一个不小的进步。最后,也欢迎大家为此项目提出您宝贵的意见!
根据我对该项目的理解,我将其命名为SecureTransferPlatform(密钥协商安全传输平台),该项目是一款基础设施类项目,可以为各集团、企业的信息系统提供统一、标准的信息安全服务,以解决企业和企业之间或集团内部之间,数据的本地存储、网络传输、身份认证、数据完整性等安全问题。简而言之,就是为第三方信息系统提供加密算法和密钥,解决密钥分发困难问题,保证第三方信息系统点到点的安全通信。该项目不仅可面向具有高安全性和高性能需求的电子商务、电子政务领域应用,还可以应用于各种"云"中心、"云"节点中。
以上是这个项目的整体架构图,整体上分为五个部分,分别为第三方信息系统的客户端与服务端、密钥协商系统的客户端(SecMngClient)与服务端(SecMngServer)以及密钥协商服务器的配置终端。第三方信息系统是这个项目的服务对象,最终目的就是为其提供信息的安全传输功能。密钥协商服务器以及密钥协商客户端是项目的核心模块,这两个模块需要分别部署在第三方信息系统所在的两台主机上,通过密钥协商功能,在各自所在的主机上生成密钥,存储在共享内存中,并通过提供外联接口,对第三方信息传输系统的数据进行加解密操作。密钥协商服务器配置终端用来管理接入系统的网点应用,负责网点生命周期的管理,具备历史密钥管理、网点审批等功能。
在这里我以两个网点之间的应用为例来说明项目的部署方案,假设现在第三方信息系统为某银行内部的信息管理系统,位于北京的总行需要和位于广州的分行进行数据通信,在通信时需要用到数据加解密服务,此时项目的部署流程为:
- 在北京总行网点所在的硬件服务器上,部署该项目的外联接口(AppInterface)和密钥协商服务器程序(SecMngKeyServer)。
- 在广州分行网点所在的硬件服务器上,部署该项目的外联接口(AppInterface)和密钥协商软件客户端程序(SecMngKeyClient),密钥协商软件客户端可部署在Windows平台或Linux平台。
- 两个网点在进行通信之前,需首先使用密钥协商客户端对秘钥协商服务器进行秘钥协商,生成数据加密所需的密钥。
- 位于北京和广州的网点应用通过调用外联接口(AppInterface)进行数据的加解密操作。
该项目主要由四大基础模块组成,分别为基础组件部分、密钥协商服务器&&客户端、配置管理终端以及外联接口。基础组件部分针对各个功能模块的底层逻辑进行了封装,为业务模块提供了包括数据序列化、套接字通信、共享内存和数据库操作在内的基础功能。密钥协商服务器和客户端实现了主要的业务逻辑,用于解决密钥分发困难的问题,为数据的加解密操作提供密钥,具备密钥协商、密钥校验和密钥查看等功能。配置管理终端具备历史密钥管理、网点审批等功能,用于管理后台数据库,外联接口可以为第三方信息传输系统提供数据加解密操作,是第三方应用程序调用本项目加解密模块的主要途径。
-
基础组件部分
-
数据序列化模块
该模块主要解决数据的跨平台传输问题,涉及到主机字节序和网络字节序的相互转换、字节对齐、序列化与反序列化等内容。原项目中使用了抽象语法标记ASN.1编写该模块,但是底层需封装较多的细节,编写代码的难度较高并且较为复杂,因此我在该项目中使用Google Protobuf技术进行了替代,protobuf可以让我们自己定义数据的结构,然后通过使用特殊编译器生成源代码的方式让我们对数据进行序列化和反序列化操作,操作简单并且效率较高,该模块的UML类图如下:
-
套接字通信模块
该模块对C++中原生Socket API进行了封装,提供了两个类,分别为TcpSocket和TcpServer。TcpSocket类基于TCP/IP协议实现了基础的通信功能,包括向指定IP和端口的进程建立连接、发送数据、接收数据、断开与进程的连接等,TcpServer类针对密钥协商服务器的业务逻辑,对TcpSocket类进行了更深层次的封装,实现了设置监听、等待客户端连接等功能,以下展示了这两个类的UML类图:
-
共享内存操作模块
项目使用由System V版本的Unix引入的共享内存(shm)来实现进程间通信,之所以选用共享内存来实现进程间通信,是因为它的效率是最高的,相较于其他进程间通信方式(如管道、内存映射等),共享内存在实现时不依赖于文件描述符(fd),即不需要对磁盘文件进行操作,而是直接为两个需要通信的进程共同安排同一块物理内存,因此效率最高。项目中提供了三个与共享内存操作相关的类,分别为BaseShm、SecKeyShm和ShmNodeInfo。BaseShm对共享内存原生API进行了封装,实现了共享内存创建、关联、销毁等功能,SecKeyShm和ShmNodeInfo则是针对具体的业务逻辑衍生出来的类,并继承了BaseShm中的功能,UML类图如下:
-
编解码模块
编解码模块是本项目最核心的部分,涉及到对称加密、非对称加密、哈希运算、数字签名和Base64编解码等诸多内容,由于内容繁多,因此我使用了OpenSSL安全套接字密码库来实现该模块的全部功能,OpenSSL是一个开源共享库,为我们提供了丰富的密码算法、常用的秘钥以及SSL协议。该模块主要主要由AesCrypto、RsaCrypto和Hash这三个类所组成,AesCrypto类实现了使用AES算法进行对称加密的功能,AES算法是目前最安全、效率最高的公开的对称加密算法,秘钥长度可以为16字节、24字节、32字节,该类对具体的实现细节进行了封装,在使用时仅需调用对应的类的方法即可。Hash类封装了目前主流的哈希算法,包括MD5、SHA1、SHA224、SHA256、SHA384和SHA512等。RsaCrypto类提供了使用RSA算法进行非对称加密、数字签名、签名校验以及Base64编解码等功能,非对称加密保证了密钥的安全分发,数字签名和签名校验技术能够鉴别出数据的所有者,Base64编解码能够将二进制字符转化为可见字符,以保证数据在网络环境中传输的完整性,以下展示的是RsaCrypto类的部分代码:
class RsaCrypto { public: RsaCrypto(); /* 根据已有的私钥和公钥文件来初始化 */ RsaCrypto(string fileName, bool isPrivate = true); ~RsaCrypto(); /* 将公钥/私钥字符串数据解析到RSA对象中 */ void parseKeyString(string keyStr, bool isPrivate = true); /* 使用RSA算法生成秘钥对 */ void generateKeyPair(int bits, string pub = "public.pem", string pri = "private.pem"); /* 使用公钥加密 */ string pubKeyEncrypt(string data); /* 使用私钥解密 */ string priKeyDecrypt(string encData); /* 对数据进行数字签名 */ string sign(string data, SignLevel level = Level3); /* 签名校验 */ bool signVerify(string data, string signData, SignLevel level = Level3); private: /* 获取公钥 */ bool initPublicKey(string pubFile); /* 获取私钥 */ bool initPrivateKey(string priFile); /* Base64编码 */ string toBase64(const char* str, int len); /* Base64解码 */ char* fromBase64(string str); private: /* 公钥 */ RSA* m_publicKey; /* 私钥 */ RSA* m_privateKey; };
-
数据库模块
项目使用MySQL数据库进行数据存储,在实现时使用的是MySQL官方提供的C API,该模块提供了MySQLOP类,对数据库的增删改查操作进行了封装,部分代码如下:
class MySQLOP { public: MySQLOP(); ~MySQLOP(); /* 初始化环境连接数据库 */ bool connectDB(string host, string user, string pwd, string DBName); /* 得到keyID -> 根据业务需求封装的小函数 */ int getSecKeyID(); /* 更新秘钥ID */ bool updateSecKeyID(int secKeyID); /* 向数据库中写入秘钥的相关信息 */ bool writeSecKey(ShmNodeInfo* pNode); /* 根据keyID查找密钥,将密钥标记为不可用 */ bool cancelSecKey(string secKeyID); /* 查询最近N天的密钥信息 */ bool getLastNDaysInfo(int n, string& output); /* 关闭与数据库的连接 */ void closeDB(); private: /* 获取当前时间,并格式化为字符串 */ string getCurTime(); /* 执行SQL语句 */ bool executeQuery(string sql); private: MYSQL* m_connection; MYSQL_RES* m_result; MYSQL_ROW m_row; };
-
-
密钥协商服务器&&客户端
原项目中仅实现了密钥协商部分的功能,在此基础上,我加入了密钥校验、密钥注销和密钥查看这三项功能。下面介绍这四项功能的主要流程。
- 密钥协商
- 密钥协商客户端使用RSA算法生成密钥对,分别为公钥和私钥
- 客户端对公钥进行哈希运算,并对哈希值进行数字签名
- 客户端将公钥和数字签名发送给服务器
- 服务器端接收到客户端发来的RSA公钥,并校验数字签名
- 校验成功后,生成对称加密的密钥,在这里以AES算法的密钥为例
- 服务器端将生成的AES密钥信息分别写入到后台数据库和共享内存中
- 使用客户端发来的RSA公钥对AES密钥进行加密
- 与客户端进行通信,向客户端发送AES密钥
- 客户端使用RSA私钥对数据进行解密,得到服务器端生成的AES密钥
- 将密钥信息写入到共享内存中
- 密钥校验
- 当密钥协商成功之后,再次校验客户端和服务器使用的密钥是否相同
- 客户端对接收到的密钥进行哈希运算,得到散列值1
- 将得到的散列值发送给服务器
- 服务器也对本地的密钥进行哈希运算,得到散列值2
- 比较散列值1和散列值2,若相同,则说明双方密钥相同
- 密钥注销
- 当客户端不再需要使用密钥时,可以对秘钥进行注销
- 客户端将本地共享内存中的密钥状态进行修改
- 向服务器发送需要注销的密钥的ID
- 服务器端通过密钥ID找到共享内存中的密钥,并标记为不可用
- 更新数据库中该密钥的状态
- 密钥查看
- 客户端可以查看历史密钥信息
- 可以根据网点或日期进行查看
- 密钥协商
-
配置管理终端
- 管理密钥协商服务器的后台数据库->MySQL
- 进行网点信息的添加、修改和删除操作
- 可以对密钥信息进行查看
-
外联接口
- 以静态或动态库的形式给出,可以被业务程序调用
- 通过JSON配置文件读取信息,找到共享内存所在位置
- 利用密钥协商阶段生成的密钥对数据进行加密
- Interface:包含了外联接口模块的所有源文件、头文件、目标文件以及库文件
- ModuleTest:包含的是项目中各个基础组件的测试代码,用于项目前期对各个基础组件的校验工作。
- SecKeyClient: 包含了密钥协商客户端的所有源文件、头文件、目标文件以及可执行文件。
- SecKeyServer:包含了密钥协商服务器的所有源文件、头文件、目标文件以及可执行文件。
- README.assets:包含了本文档(README.md)的图片文件。