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

来源:互联网 发布:广州cnc编程招聘信息 编辑:程序博客网 时间:2024/05/23 17:43

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 =256Decrypted Text =Hello this is tab_spaceDecrypted Length =23Encrypted length =256Decrypted Text =Hello this is tab_spaceDecrypted Length =23

2. 总结

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

1 0
原创粉丝点击