银行安全传输平台(六)秘钥协商服务器和客户端实现-1


秘钥协商服务器和客户端实现

由于秘钥协商服务器和客户端从的操作内容较多,我们分两篇笔记来记录这一部分,这篇笔记主要记录Jsoncpp和一般步骤(就是业务流程)。

一、JsonCpp

Json使数据的一种描述形式,以某种格式将数据组织起来。在应用程序启动的时候, 需要加载少量数据, 初始化应用程序,比如登录的用户名, 密码;远程数据库连接;连接的远程服务器地址;这样做可以让程序更加灵活,比如我们要修改初始配置,无需重新编译,运维只要改 JSON 即可调整行为。

1.1 json如何组织数据

主要有两种方式一种是json数组(本项目没用到,所以不做记录),一种是json对象:

  • 使用{}表示
    • 分为两部分, key: value
    • 键值对之间使用都被间隔, 最后一个键值对后边不要写逗号, 否则会解析失败
    • key: 必须是字符串, 对要存储的数据的描述
    • value: 要保存的数据, 数据格式可以有多种:
      • 整形, 浮点, 字符串, 布尔,

可以嵌套使用:

			{
                "name":"蕾蕾",
                "age":12,
                "sex":"man",
                "婚否":flase,
                "falily":["爸爸", "妈妈", "弟弟"],
                "资产":{
                    "car":"BMW",
                    "房子":"北京故宫"
                }
            }

1.2 server.json

配置文件

{
  "serverID":    "666",
  "dbUser":      "xyz",//数据库用户
  "dbPasswd":    "xxxxxx",//数据库密码
  "dbSID":       "192.168.164.xxx:1521/orcl",
  "port":        8989,
  "maxnode":     20,//共享内存中最大节点数
  "shmkey":      "/home/bin"
}

加载示例:

ifstream ifs("server.json");
Reader r; Value root;
r.parse(ifs, root);
m_port      = root["port"].asInt();
m_serverID  = root["serverID"].asString();
m_dbUser    = root["dbUser"].asString();
m_dbPwd     = root["dbPasswd"].asString();
m_dbConnStr = root["dbSID"].asString();
int maxNode = root["maxnode"].asInt();
string shmKey = root["shmkey"].asString();
m_shm = new SecKeyShm(shmKey, maxNode);

1.3 client.json

配置文件:

{
  "clientID":   "666",
  "serverID":   "999",
  "serverIP":   "127.0.0.1",
  "serverPort": 8989,
  "maxNode":    1,
  "shmKey":     "/usr/local/bin"
}

加载示例:

ifstream ifs("client.json");
Reader r; Value root;
r.parse(ifs, root);
m_info.ClientID = root["clientID"].asString();
m_info.ServerID = root["serverID"].asString();
m_info.ip       = root["serverIP"].asString();
m_info.port     = root["serverPort"].asInt();
int maxNode     = root["maxNode"].asInt();
string shmKey   = root["shmKey"].asString();
m_shm = new SecKeyShm(shmKey, maxNode);

这样运维只需维护两份 JSON,即可灵活控制服务器与客户端行为。

二、秘钥协商客户端业务流程实现

2.1 客户端流程

  • 首先用户在交互界面选择“密钥协商”,程序读取 client.json 中的 clientID、serverID、serverIP 和 serverPort,并生成一对 RSA 密钥(公钥/私钥)。
  • 将客户端id、服务器id、公钥(客户端自己用于非对称加密的公钥)和其 SHA-1 哈希签名填入 RequestMsg 结构体(cmdType=1),然后用 Protobuf 将该结构体序列化成字节流。

数据分析:

//客户端使用的结构体
struct RequestMsg
{
	int cmdType;	
    string clientID;
    string serverID;
    string sign;
    string data; 
};

message RequestMsg
{
	int32 cmdType=1;	
    bytes clientID=2;
    bytes serverID=3;
    bytes sign=4;
    bytes data=5; 
}

// 数据分析
cmdType: 发送给服务器的数据, 服务器需要对去进行判断:
	- 1 -> 秘钥协商
	- 2 -> 秘钥校验
	- 3 -> 秘钥注销
clientID: 所有有效的客户端都会被分配给一个有效的唯一的ID
serverID: 客户端要连接的服务器对应的ID
data: 对应的业务数据, 根据cmdType不同而不同
sign: 签名, 对data签名
  • 利用封装好的 TcpSocket,按配置的 IP 和端口连接服务器,发送刚才的序列化数据,并阻塞等待服务器的响应。
  • 收到服务器返回的 RespondMsg(内含用公钥加密的对称密钥和 seckeyID)后,用 Protobuf 反序列化得到密文,再用私钥解密出真正的对称密钥,最后将该密钥及其 ID 写入共享内存,完成协商。

2.2 流程实现

2.2.1 Client.h

#pragma once
#include <string>
using namespace std;

struct ClientInfo
{
	string ServerID;
	string ClientID;
	string ip;
	unsigned short port;
};

class ClientOP
{
public:
	ClientOP(string jsonFile);
	~ClientOP();

	// 秘钥协商
	bool seckeyAgree();

	// 秘钥校验
	void seckeyCheck() {}

	// 秘钥注销
	void seckeyZhuXiao() {}

private:
	ClientInfo m_info;
};

2.2.2 Client.cpp

#include "ClientOP.h"
#include <jsoncpp/json/json.h>
#include <fstream>
#include <sstream>
#include <iostream>
#include "RequestFactory.h"
#include "RequestCodec.h"
#include "RsaCrypto.h"
#include "TcpSocket.h"
#include "RespondFactory.h"
#include "RespondCodec.h"
#include "Message.pb.h"
#include "Hash.h"
using namespace std;
using namespace Json;

ClientOP::ClientOP(string jsonFile)
{
	// 解析json文件, 读文件 -> Value
	ifstream ifs(jsonFile);
	Reader r;
	Value root;
	r.parse(ifs, root);
	// 将root中的键值对value值取出
	m_info.ServerID = root["ServerID"].asString();
	m_info.ClientID = root["ClientID"].asString();
	m_info.ip = root["ServerIP"].asString();
	m_info.port = root["Port"].asInt();
}

ClientOP::~ClientOP()
{
}

bool ClientOP::seckeyAgree()
{
	// 0. 生成密钥对, 将公钥字符串读出
	RsaCrypto rsa;
	// 生成密钥对
	rsa.generateRsakey(1024);
	// 读公钥文件
	ifstream ifs("public.pem");
	stringstream str;
	str << ifs.rdbuf();
	// 1. 初始化序列化数据
	// 序列化的类对象 -> 工厂类创建
	RequestInfo reqInfo;
	reqInfo.clientID = m_info.ClientID;
	reqInfo.serverID = m_info.ServerID;
	reqInfo.cmd = 1;	// 秘钥协商
	reqInfo.data = str.str();	// 非对称加密的公钥
	// 创建哈希对象
	Hash sha1(T_SHA1);
	sha1.addData(str.str());
	reqInfo.sign = rsa.rsaSign(sha1.result());	// 公钥的的哈希值签名
	cout << "签名完成..." << endl;
	CodecFactory* factory = new RequestFactory(&reqInfo);
	Codec* c =  factory->createCodec();
	// 得到序列化之后的数据, 可以将其发送给服务器端
	string encstr = c->encodeMsg();
	// 释放资源
	delete factory;
	delete c;

	// 套接字通信, 当前是客户端, 连接服务器
	TcpSocket* tcp = new TcpSocket;
	// 连接服务器
	int ret = tcp->connectToHost(m_info.ip, m_info.port);
	if (ret != 0)
	{
		cout << "连接服务器失败..." << endl;
		return false;
	}
	cout << "连接服务器成功..." << endl;
	// 发送序列化的数据
	tcp->sendMsg(encstr);
	// 等待服务器回复
	string msg = tcp->recvMsg();

	// 解析服务器数据 -> 解码(反序列化)
	// 数据还原到 RespondMsg
	factory = new RespondFactory(msg);
	c = factory->createCodec();
	RespondMsg* resData = (RespondMsg*)c->decodeMsg();
	// 判断状态
	if (!resData->status())
	{
		cout << "秘钥协商失败" << endl;
		return false;
	}
	// 将得到的密文解密
	string key = rsa.rsaPriKeyDecrypt(resData->data());
	cout << "对称加密的秘钥: " << key << endl;
	// 秘钥写入共享内存中

	delete factory;
	delete c;
	// 这是一个短连接, 通信完成, 断开连接
	tcp->disConnect();
	delete tcp;

	return true;
}

三、秘钥协商服务器业务流程实现

3.1 服务器流程

服务器端作为守护进程启动后,首先从 server.json 中读取监听端口、数据库和共享内存配置,然后调用 TcpServer::setListen(port) 开始监听,并在一个循环里通过 acceptConn() 接受来自多个客户端的连接(可用多线程或 I/O 多路复用以支持并发):

  • 收到客户端请求数据 ->解析,序列化之后的数据,然后将数据反序列化得到结构体
  • 根据cmdType判断客户端想要干什么:
switch(cmdType)
{
    case 1:
        秘钥协商();
        break;
    case 2:
        秘钥校验();
        break;
    case 3:
        秘钥注销();
        break;
    default:
        break;
}
  • 请求秘钥协商:检查 clientID/serverID 是否在允许列表中,再读出 data(客户端公钥),用 OpenSSL 对该公钥的 SHA-1 哈希和 sign 进行验签,确保公钥未被篡改。
  • 生成aes密钥并发送,用 getRandKey(16) 随机生成一段 16 字节字符串作为 AES 对称密钥,用客户端公钥将对称密钥加密为密文
  • 初始化回复数据,然后序列化发送数据,最后通过网络通信发送到客户端。

3.2 流程实现

3.2.1 ServerOP.h

#pragma once
#include <map>
#include "TcpServer.h"
#include "Message.pb.h"
// 处理客户端请求
class ServerOP
{
public:
	enum KeyLen {Len16=16, Len24=24, Len32=32};
	ServerOP(string json);
	// 启动服务器
	void startServer();
	// 线程工作函数 -> 推荐使用
	static void* working(void* arg);
	// 友元破坏了类的封装
	friend void* workHard(void* arg);
	// 秘钥协商
	string seckeyAgree(RequestMsg* reqMsg);
	~ServerOP();

private:
	string getRandKey(KeyLen len);

private:
	string m_serverID;	// 当前服务器的ID
	unsigned short m_port;
	map<pthread_t, TcpSocket*> m_list;
	TcpServer *m_server = NULL;
};

void* workHard(void* arg);

3.2.2 ServerOP.cpp

#include "ServerOP.h"
#include "TcpSocket.h"
#include "RequestFactory.h"
#include "RequestCodec.h"
#include "RespondCodec.h"
#include "RespondFactory.h"
#include "RsaCrypto.h"
#include <string>
#include <iostream>
#include <fstream>
#include <jsoncpp/json/json.h>
#include <unistd.h>
#include "Hash.h"
using namespace std;
using namespace Json;

/*
	{
		"Port":9898
	}
*/
ServerOP::ServerOP(string json)
{
	// 解析json文件, 读文件 -> Value
	ifstream ifs(json);
	Reader r;
	Value root;
	r.parse(ifs, root);
	// 将root中的键值对value值取出
	m_port = root["Port"].asInt();
	m_serverID = root["ServerID"].asString();
}


void ServerOP::startServer()
{
	m_server = new TcpServer;
	m_server->setListen(m_port);
	while (1)
	{
		cout << "等待客户端连接..." << endl;
		TcpSocket* tcp = m_server->acceptConn();
		if (tcp == NULL)
		{
			continue;
		}
		cout << "与客户端连接成功..." << endl;
		// 通信
		pthread_t tid;
		// 这个回调可以是类的静态函数, 类的友元函数, 普通的函数
		// 友元的类的朋友, 但是不属于这个类
		// 友元函数可以访问当前类的私有成员
		pthread_create(&tid, NULL, workHard, this);
		m_list.insert(make_pair(tid, tcp));
	}
}

void * ServerOP::working(void * arg)
{
	return nullptr;
}

string ServerOP::seckeyAgree(RequestMsg* reqMsg)
{
	// 0. 对签名进行校验 -> 公钥解密 -> 得到公钥
	// 将收到的公钥数据写入本地磁盘
	ofstream ofs("public.pem");
	ofs << reqMsg->data();
	ofs.close();
	// 创建非对称加密对象
	RespondInfo info;
	RsaCrypto rsa("public.pem", false);

	// 创建哈希对象
	Hash sha(T_SHA1);
	sha.addData(reqMsg->data());
	cout << "1111111111111111" << endl;
	bool bl = rsa.rsaVerify(sha.result(), reqMsg->sign());
	cout << "00000000000000000000" << endl;
	if (bl == false)
	{
		cout << "签名校验失败..." << endl;
		info.status = false;
	}
	else
	{
		cout << "签名校验成功..." << endl;
		// 1. 生成随机字符串
		//   对称加密的秘钥, 使用对称加密算法 aes, 秘钥长度: 16, 24, 32byte
		string key = getRandKey(Len16);
		cout << "生成的随机秘钥: " << key << endl;
		// 2. 通过公钥加密
		cout << "aaaaaaaaaaaaaaaa" << endl;
		string seckey = rsa.rsaPubKeyEncrypt(key);
		cout << "加密之后的秘钥: " << seckey << endl;
		// 3. 初始化回复的数据
		info.clientID = reqMsg->clientid();
		info.data = seckey;
		info.seckeyID = 12;	// 需要数据库操作
		info.serverID = m_serverID;
		info.status = true;	
	}

	// 4. 序列化
	CodecFactory* fac = new RespondFactory(&info);
	Codec* c = fac->createCodec();
	string encMsg = c->encodeMsg();
	// 5. 发送数据
	return encMsg;

}

ServerOP::~ServerOP()
{
	if (m_server)
	{
		delete m_server;
	}
}

// 要求: 字符串中包含: a-z, A-Z, 0-9, 特殊字符
string ServerOP::getRandKey(KeyLen len)
{
	// 设置随机数数种子 => 根据时间
	srand(time(NULL));
	int flag = 0;
	string randStr = string();
	char *cs = "~!@#$%^&*()_+}{|\';[]";
	for (int i = 0; i < len; ++i)
	{
		flag = rand() % 4;	// 4中字符类型
		switch (flag)
		{
		case 0:	// a-z
			randStr.append(1, 'a' + rand() % 26);
			break;
		case 1: // A-Z
			randStr.append(1, 'A' + rand() % 26);
			break;
		case 2: // 0-9
			randStr.append(1, '0' + rand() % 10);
			break;
		case 3: // 特殊字符
			randStr.append(1, cs[rand() % strlen(cs)]);
			break;
		default:
			break;
		}
	}
	return randStr;
}

void* workHard(void * arg)
{
	sleep(1);
	string data = string();
	// 通过参数将传递的this对象转换
	ServerOP* op = (ServerOP*)arg;
	// 从op中将通信的套接字对象取出
	TcpSocket* tcp = op->m_list[pthread_self()];
	// 1. 接收客户端数据 -> 编码
	string msg = tcp->recvMsg();
	// 2. 反序列化 -> 得到原始数据 RequestMsg 类型
	CodecFactory* fac = new RequestFactory(msg);
	Codec* c = fac->createCodec();
	RequestMsg* req = (RequestMsg*)c->decodeMsg();
	// 3. 取出数据
	// 判断客户端是什么请求
	switch (req->cmdtype())
	{
	case 1:
		// 秘钥协商
		data = op->seckeyAgree(req);
		break;
	case 2:
		// 秘钥校验
		break;
	default:
		break;
	}

	// 释放资源
	delete fac;
	delete c;
	// tcp对象如何处理
	tcp->sendMsg(data);
	tcp->disConnect();
	op->m_list.erase(pthread_self());
	delete tcp;

	return NULL;
}

四、main函数实现

4.1 客户端

#include <iostream>
#include <string>
#include "ClientOP.h"
using namespace std;

int usage();
int main()
{
	// 创建客户端操作类对象
	ClientOP op("client.json");
	while (1)
	{
		int sel = usage();
		switch (sel)
		{
		case 1:
			// 秘钥协商
			op.seckeyAgree();
			break;
		case 2:
			op.seckeyCheck();
			// 秘钥校验
			break;
		case 3:
			// 秘钥注销
			op.seckeyZhuXiao();
		default:
			break;

		}
	}
	cout << "客户端退出, bye,byte..." << endl;
	return 0;
}

int usage()
{
	int nSel = -1;
	printf("\n  /*************************************************************/");
	printf("\n  /*************************************************************/");
	printf("\n  /*     1.密钥协商                                            */");
	printf("\n  /*     2.密钥校验                                            */");
	printf("\n  /*     3.密钥注销                                            */");
	printf("\n  /*     4.密钥查看                                            */");
	printf("\n  /*     0.退出系统                                            */");
	printf("\n  /*************************************************************/");
	printf("\n  /*************************************************************/");
	printf("\n\n  选择:");

	scanf("%d", &nSel);
	while (getchar() != '\n');

	return nSel;
}

4.2 服务器

#include <cstdio>
#include "ServerOP.h"

int main()
{
	ServerOP op("server.json");
	op.startServer();

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值