Client-ServerRSA加解密通信方案-Server端(C++)(一)

4 篇文章 0 订阅
1 篇文章 0 订阅

0. 背景

最近,需要新做一个游戏demo,类似《部落冲突·皇室战争》的推塔玩法。客户端使用Unity,编程语言为C#,服务端使用C++。由于从零开始,需要建立基础部件,其中网络模块是最重要的模块之一。网络模块协议遵从如下图步骤时序图进行通信。

这里写图片描述
图1.通信步骤时序图

由上图可以看出网络通信主要分为两个步骤,第一步:客户端使用RSA加解密请求通信服务端,获取RC4秘钥;第二步:客户端与服务端使用RC4加解密通信。本文主要介绍第一步中在Server端(C++)的RSA加解密模块。

注:本文不叙述网络模块中的框架设计,只关注在客户端/服务端获得加密消息后,进行解密的技术环节。

1. RSA加密与解密

RSA为非对称加密方式,在C/C++中是基于使用OpenSSL库来实现。通过以下4个步骤来简单讲述。

a). 使用OpenSSL产生RSA秘钥

RSA的私钥包含了公钥的信息,所以首先通过以下命令产生长度为2048的RSA私钥。

openssl genrsa -out private.pem 2048

这样private.pem就会生成在当前目录下,使用以下命令就可以从private.pem提取出public.pem。

openssl rsa -in private.pem -outform PEM -pubout -out public.pem

如此,就产生两个秘钥,不难猜出:

public.pem是使用PEM格式的公钥,而private.pem是对应的私钥。

b). 公钥加密与私钥解密

RSA是一种非对称的加解密方式。基于它的特性(wiki),它可以以公钥加密,然后私钥解密;也可以以私钥加密,公钥解密。本小节罗列下OpenSSL对于RSA提供的接口:

int RSA_public_encrypt(int flen, unsigned char *from,
   unsigned char *to, RSA *rsa, int padding);

int RSA_private_decrypt(int flen, unsigned char *from,
    unsigned char *to, RSA *rsa, int padding);

从函数名可以清晰地看出接口的功能。以RSA_public_encrypt为例叙述参数,其他的接口可参考linux Doc

  • from: 待加密的字符串;
  • flen: 待加密字符串长度;
  • to: 加密完成后的字符串,其长度必须是RSA_size(rsa);
  • rsa: 公钥;
  • padding: 填充模式,有一下模式可供选择:
    1) RSA_PKCS1_PADDING:最常用的模式,待加密字符串长度flen < RSA_size(rsa)-11;
    2) RSA_PKCS1_OAEP_PADDING:这种模式对于新应用也是推荐的,待加密字符串长度flen < RSA_size(rsa)-41
    3) RSA_SSLV23_PADDING:不常用模式;
    4) RSA_NO_PADDING:原RSA加密。这种模式只用于实现应用程序中的加密声音的填充模式。直接对用户数据RSA加密是不安全的,待加密字符串长度flen < RSA_size(rsa)

RSA_private_decrypt接口真是从长度为flenfrom字符串中使用私钥解密,并将解密后的字符存储在to中,而to字符串是指向一个足够存储解密字符串的buffer中,使用的填充模式padding是采取加密时的填充模式。

RSA_public_encrypt接口返回的是加密后字符串的长度,而RSA_private_decrypt接口返回的是解密后字符串的长度。如果加密/解密出错,返回-1。

我们可以通过以下函数封装,来使用公钥加密数据和私钥解密:

int padding = RSA_PKCS1_PADDING; //最常用的填充模式
char* pub_fp = $path_to_public_pem$;
char* pri_fp = $path_to_private_pem$;
// 公钥加密封装
int public_encrypt(unsigned char * data,int data_len,unsigned char * key, unsigned char *encrypted)
{
    RSA * rsa = createRSAWithFilename(pub_fp,1);
    int result = RSA_public_encrypt(data_len,data,encrypted,rsa,padding);
    return result;
}

// 私钥解密封装
int private_decrypt(unsigned char * enc_data,int data_len,unsigned char * key, unsigned char *decrypted)
{
    RSA * rsa = createRSAWithFilename(pri_fp,0);
    int  result = RSA_private_decrypt(data_len,enc_data,decrypted,rsa,padding);
    return result;
}

注:公钥加密是可以使用所以的填充模式(padding mode).

其中createRSAWithFilename(char* filename, int nPublic)接口是加载 a). 使用OpenSSL产生RSA秘钥的,filename为公私钥的文件路径,nPublic表示是否是公钥,具体实现如下:

RSA * createRSAWithFilename(char * filename,int nPublic)
{
    FILE * fp = fopen(filename,"rb");

    if(fp == NULL)
    {
        printf("Unable to open file %s \n",filename);
        return NULL;    
    }
    RSA *rsa= RSA_new() ;

    if(nPublic)
    {
        rsa = PEM_read_RSA_PUBKEY(fp, &rsa,NULL, NULL);
    }
    else
    {
        rsa = PEM_read_RSAPrivateKey(fp, &rsa,NULL, NULL);
    }

    return rsa;
}

c). 私钥加密与公钥解密

下面是OpenSSL中私钥加密与公钥解密的接口:

int RSA_public_encrypt(int flen, unsigned char *from,
   unsigned char *to, RSA *rsa, int padding);

int RSA_private_decrypt(int flen, unsigned char *from,
    unsigned char *to, RSA *rsa, int padding);

接口详述参考 b).公钥加密与私钥解密,或是参考linux Doc

d). 举个栗子

栗子代码如下:

//rsa_example.cpp

#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <stdio.h>

int padding = RSA_PKCS1_PADDING;

RSA* createRSAWithFilename(const char*, int);

int public_encrypt(unsigned char * data,int data_len, const char* pub_fp, unsigned char *encrypted)
{
    RSA * rsa = createRSAWithFilename(pub_fp,1);
    int result = RSA_public_encrypt(data_len,data,encrypted,rsa,padding);
    return result;
}
int private_decrypt(unsigned char * enc_data,int data_len, const  char* pri_fp, unsigned char *decrypted)
{
    RSA * rsa = createRSAWithFilename(pri_fp,0);
    int  result = RSA_private_decrypt(data_len,enc_data,decrypted,rsa,padding);
    return result;
}


int private_encrypt(unsigned char * data,int data_len, const char* pri_fp, unsigned char *encrypted)
{
    RSA * rsa = createRSAWithFilename(pri_fp,0);
    int result = RSA_private_encrypt(data_len,data,encrypted,rsa,padding);
    return result;
}
int public_decrypt(unsigned char * enc_data,int data_len, const char *pub_fp, unsigned char *decrypted)
{
    RSA * rsa = createRSAWithFilename(pub_fp,1);
    int  result = RSA_public_decrypt(data_len,enc_data,decrypted,rsa,padding);
    return result;
}

void printLastError(char *msg)
{
    char * err = (char*)malloc(130);
    ERR_load_crypto_strings();
    ERR_error_string(ERR_get_error(), err);
    printf("%s ERROR: %s\n",msg, err);
    free(err);
}

RSA * createRSAWithFilename(const char * filename,int nPublic)
{
    FILE * fp = fopen(filename,"rb");

    if(fp == NULL)
    {
        printf("Unable to open file %s \n",filename);
        return NULL;    
    }
    RSA *rsa= RSA_new() ;

    if(nPublic)
    {
        rsa = PEM_read_RSA_PUBKEY(fp, &rsa,NULL, NULL);
    }
    else
    {
        rsa = PEM_read_RSAPrivateKey(fp, &rsa,NULL, NULL);
    } 
    return rsa;
}

int main(){

    unsigned char plainText[2048/8] = "Hello this is tab_space";
    unsigned char  encrypted[4098]={};
    unsigned char decrypted[4098]={};

    const char* pub_fp = "/home/wsn/crypt/res/public.pem";
    const char* pri_fp = "/home/wsn/crypt/res/private.pem";
    int encrypted_length= public_encrypt(plainText,strlen((const char*)plainText),pub_fp,encrypted);
    if(encrypted_length == -1)
    {
        printLastError((char*)"Public Encrypt failed ");
        exit(0);
    }
    printf("Encrypted length =%d\n",encrypted_length);

    int decrypted_length = private_decrypt(encrypted,encrypted_length, pri_fp, decrypted);
    if(decrypted_length == -1)
    {
        printLastError((char*)"Private Decrypt failed ");
        exit(0);
    }
    printf("Decrypted Text =%s\n",decrypted);
    printf("Decrypted Length =%d\n",decrypted_length);


    encrypted_length= private_encrypt(plainText,strlen((const char*) plainText),pri_fp,encrypted);
    if(encrypted_length == -1)
    {
        printLastError((char*)"Private Encrypt failed");
        exit(0);
    }
    printf("Encrypted length =%d\n",encrypted_length);

    decrypted_length = public_decrypt(encrypted,encrypted_length,pub_fp, decrypted);
    if(decrypted_length == -1)
    {
        printLastError((char*)"Public Decrypt failed");
        exit(0);
    }
    printf("Decrypted Text =%s\n",decrypted);
    printf("Decrypted Length =%d\n",decrypted_length);

}

编译代码:

[root@xxx crypt]# g++ -o bin/example -I/usr/include/openssl/ -lcrypto src/example.cpp

打印的结果:

[root@xxx crypt]# bin/example 
Encrypted length =256
Decrypted Text =Hello this is tab_space
Decrypted Length =23
Encrypted length =256
Decrypted Text =Hello this is tab_space
Decrypted Length =23

2. 总结

本文讲述了在服务端(Linux/C++)使用OpenSSL库对通信消息进行RSA非对称加解密的技术过程,下一篇会讲述客户端(Unity/C#)使用OpenSSL库对通信消息进行RSA加解密的技术过程。栗子的代码上传至csdz的github中。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个使用libwebsockets-4.3.2写的C++ Websockets服务的示例代码: ```c++ #include <libwebsockets.h> #include <iostream> #include <string> using namespace std; struct WebsocketServerData { int counter; }; static int callback_websockets(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { WebsocketServerData *server_data = (WebsocketServerData*)lws_context_user(lws_get_context(wsi)); switch (reason) { case LWS_CALLBACK_ESTABLISHED: cout << "Websocket client connection established" << endl; break; case LWS_CALLBACK_SERVER_WRITEABLE: { string message = "Hello, World! " + to_string(server_data->counter); unsigned char *buffer = new unsigned char[LWS_SEND_BUFFER_PRE_PADDING + message.length() + LWS_SEND_BUFFER_POST_PADDING]; memcpy(buffer + LWS_SEND_BUFFER_PRE_PADDING, message.c_str(), message.length()); lws_write(wsi, buffer + LWS_SEND_BUFFER_PRE_PADDING, message.length(), LWS_WRITE_TEXT); delete[] buffer; server_data->counter++; } break; case LWS_CALLBACK_RECEIVE: cout << "Received message from client: " << string((char*)in, len) << endl; lws_callback_on_writable_all_protocol(lws_get_context(wsi), lws_get_protocol(wsi)); break; case LWS_CALLBACK_CLOSED: cout << "Websocket connection closed" << endl; break; default: break; } return 0; } int main(int argc, char **argv) { struct lws_context_creation_info context_info; struct lws_protocols protocols[] = { { "websockets", callback_websockets, sizeof(WebsocketServerData), 0 }, { NULL, NULL, 0, 0 } }; WebsocketServerData server_data = { 0 }; int port = 8080; int exit_code = 0; memset(&context_info, 0, sizeof(context_info)); context_info.port = port; context_info.protocols = protocols; context_info.user = &server_data; struct lws_context *context = lws_create_context(&context_info); if (!context) { cerr << "Failed to create libwebsocket context" << endl; exit_code = 1; goto cleanup; } while (true) { lws_service(context, 50); } cleanup: if (context) { lws_context_destroy(context); } return exit_code; } ``` 这个示例代码创建了一个Websocket服务,监听指定的口,并且每收到一条客户消息就回复一条消息。你可以根据你的需求调整服务回复消息的内容和频率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值