OpenSSL服务器和客户端交换RSA公钥

这个代码用于登录服务器。

第一步:客户端生成一对公钥和私钥,并将客户端公钥发给服务器。

第二步:服务器生成一对公钥和密钥,并将服务器公钥发给客户端。

第三步:客户端使用服务器公钥给登录账号密码加密发给服务器,服务器使用服务器私钥解密,并校验账号密码的正确性。

第四步:如果密码正确,服务器读取数据库中的用户信息,使用客户端的公钥加密,发给客户端

第五步:客户端使用客户端私钥将用户信息解密。

一 生成密钥和加解密算法的封装

头文件

#include <string>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/bn.h> // this is for the BN_new

/**
 * @todo:这里RSA的封装是PEM方式,在创建公钥-私钥时需要保存到文件再从文件中读取。
 * 下一个版本要改进这里,避免磁盘操作。
 */
class CRsaPeer
{
public:
	// 打开一对公钥私钥
	// 需要加密时,必须指定公钥文件。公钥文件不存在时加密函数会失败。
	// 需要解密时,必须制定私钥文件。私钥文件不存在时解密函数会失败。
	explicit CRsaPeer(const std::string& strPubKeyFilePath ,
		const std::string& strPriKeyPath, 
		const std::string& strPrivateFilePassword);
	~CRsaPeer();
	// 创建一对公钥-私钥
	static CRsaPeer* CreateRsaFiles(
		const std::string& strPubKeyFilePath ,
		const std::string& strPriKeyPath, 
		const std::string& strPrivateFilePassword
		);
public:
	std::string GetPubKeyPath() const { return m_strPubKeyPath; }
	std::string GetPriKeyPath() const { return m_strPriKeyPath; }
	// 打开m_strPubKeyPath指定的文件,读取公钥。
	int OpenPublicKey();
	// 打开m_strPriKeyPath指定的文件,解密并读取私钥
	int OpenPrivateKey();
	// 加密内容
	int Encrypt(
		const unsigned char *orig_data, 
		size_t orig_data_len, 
		unsigned char *enc_data, 
		size_t &enc_data_len
		);
	// 解密内容
	int Decrypt(
		const unsigned char *enc_data, 
		size_t enc_data_len, 
		unsigned char *orig_data, 
		size_t &orig_data_len);
private:
	// 创建公钥和私钥文件。
	int CreateKeyPairFileInternal();
	// 禁止拷贝构造和赋值
	CRsaPeer(const CRsaPeer& r);
	CRsaPeer operator = (const CRsaPeer& r);
private:
	// 公钥存放的路径
	std::string m_strPubKeyPath;
	// 私钥存放的路径
	std::string m_strPriKeyPath;
	// 私钥的加密密码
	std::string m_strPasswordForPrivateKey;
	// 公钥
	EVP_PKEY* m_pPubKey;
	// 私钥
	EVP_PKEY* m_pPriKey;
};


实现:

#include "stdafx.h"
#include "PackSSL.h"
#include "../common/TString.h"

using std::string;

CRsaPeer::CRsaPeer( const string& strPubKeyPath , 
	const string& strPriKeyPath , 
	const string& strPswdForPrivateKey)
{
	m_pPubKey = NULL ;
	m_pPriKey = NULL ;
	if ( strPubKeyPath.empty () && strPriKeyPath.empty() )
	{
		perror("file stores key values empty");
		return;
	}
	if ( strPswdForPrivateKey.empty ())
	{
		perror("password empty , use default");
		m_strPasswordForPrivateKey = "Wehavetowork8daysperweekbutonlYpayed1.0$";
	}
	printf("here ");
	m_strPubKeyPath = strPubKeyPath ;
	m_strPriKeyPath = strPriKeyPath ;
	m_strPasswordForPrivateKey = strPswdForPrivateKey ;
}

CRsaPeer::~CRsaPeer ()
{
	if ( m_pPubKey )
		EVP_PKEY_free( m_pPubKey );
	if ( m_pPriKey )
		EVP_PKEY_free( m_pPriKey ); 
}

CRsaPeer* CRsaPeer::CreateRsaFiles(
	const std::string& strPubKeyFilePath ,
	const std::string& strPriKeyPath, 
	const std::string& strPrivateFilePassword
	)
{
	CRsaPeer *RetValue = new CRsaPeer(strPubKeyFilePath,strPriKeyPath,strPrivateFilePassword);
	if (0 != RetValue->CreateKeyPairFileInternal())
	{
		delete RetValue;
		return NULL;
	}
	return RetValue;
}

// 打开公钥文件,返回EVP_PKEY结构的指针
// 返回0表示成功,其他值表示错误。
int CRsaPeer::OpenPublicKey()
{
	if (m_pPubKey)
	{
		return 0;
	}

	RSA *rsa = NULL;

	OpenSSL_add_all_algorithms();
	BIO *bp = BIO_new(BIO_s_file());;
	BIO_read_filename(bp, m_strPubKeyPath.c_str());
	if(NULL == bp)
	{
		printf("open_public_key bio file new error!\n");
		return 1;
	}

	rsa = PEM_read_bio_RSAPublicKey(bp, NULL, NULL, NULL);
	if(rsa == NULL)
	{
		printf("open_public_key failed to PEM_read_bio_RSAPublicKey!\n");
		BIO_free(bp);
		RSA_free(rsa);
		return 2;
	}

	printf("open_public_key success to PEM_read_bio_RSAPublicKey!\n");
	m_pPubKey = EVP_PKEY_new();
	if(NULL == m_pPubKey)
	{
		printf("open_public_key EVP_PKEY_new failed\n");
		RSA_free(rsa);
		return 3;
	}

	EVP_PKEY_assign_RSA(m_pPubKey, rsa);
	return 0;
}

// 打开私钥文件,返回EVP_PKEY结构的指针
int CRsaPeer::OpenPrivateKey()
{
	if (m_pPriKey)
	{
		return 0;
	}
	RSA *rsa = RSA_new();
	OpenSSL_add_all_algorithms();
	BIO *bp = NULL;
	bp = BIO_new_file(m_strPriKeyPath.c_str(), "rb"); 
	if(NULL == bp)
	{
		printf("open_private_key bio file new error!\n");
		return 1;
	}
	rsa = PEM_read_bio_RSAPrivateKey(bp, &rsa, NULL, (void *)m_strPasswordForPrivateKey.c_str());
	if(rsa == NULL)
	{
		printf("open_private_key failed to PEM_read_bio_RSAPrivateKey!\n");
		BIO_free(bp);
		RSA_free(rsa);
		return 2;
	}
	printf("open_private_key success to PEM_read_bio_RSAPrivateKey!\n");
	m_pPriKey = EVP_PKEY_new();
	if(NULL == m_pPriKey)
	{
		printf("open_private_key EVP_PKEY_new failed\n");
		RSA_free(rsa);
		return 3;
	}
	EVP_PKEY_assign_RSA(m_pPriKey, rsa);
	return 0;
}

// 使用公钥加密,这种封装格式只适用公钥加密,私钥解密。
int CRsaPeer::Encrypt( 
	const unsigned char *orig_data, 
	size_t orig_data_len, 
	unsigned char *enc_data, 
	size_t &enc_data_len)
{
	if (0 != OpenPublicKey())
	{
		return -1;
	}
	
	EVP_PKEY_CTX *ctx = NULL;
	OpenSSL_add_all_ciphers();
	ctx = EVP_PKEY_CTX_new(m_pPubKey, NULL);
	if(NULL == ctx)
	{
		printf("Encrypt failed to open ctx.\n");
		EVP_PKEY_free(m_pPubKey);
		m_pPubKey = NULL;
		return -1;
	}
	if(EVP_PKEY_encrypt_init(ctx) <= 0)
	{
		printf("ras_pubkey_encryptfailed to EVP_PKEY_encrypt_init.\n");
		EVP_PKEY_free(m_pPubKey);
		m_pPubKey = NULL;
		return -1;
	}
	if(EVP_PKEY_encrypt(ctx,
		enc_data,
		&enc_data_len,
		orig_data,
		orig_data_len) <= 0)
	{
		printf("ras_pubkey_encryptfailed to EVP_PKEY_encrypt.\n");
		EVP_PKEY_CTX_free(ctx);
		EVP_PKEY_free(m_pPubKey);
		m_pPubKey = NULL;
		return -1;
	}
	EVP_PKEY_CTX_free(ctx);
	//EVP_PKEY_free(m_pPubKey);
	return 0;
}

// 使用私钥解密,这种封装格式只适用公钥加密,私钥解密
int CRsaPeer::Decrypt(
	const unsigned char *enc_data, 
	size_t enc_data_len, 
	unsigned char *orig_data, 
	size_t &orig_data_len
	)
{
	if (0 != OpenPrivateKey())
	{
		return -1;
	}

	EVP_PKEY_CTX *ctx = NULL;
	OpenSSL_add_all_ciphers();
	ctx = EVP_PKEY_CTX_new(m_pPriKey, NULL);
	if(NULL == ctx)
	{
		printf("RSAKeyDecrypt failed to open ctx.\n");
		EVP_PKEY_free(m_pPriKey);
		m_pPriKey = NULL;
		return -1;
	}
	if(EVP_PKEY_decrypt_init(ctx) <= 0)
	{
		printf("RSAKeyDecrypt failed to EVP_PKEY_decrypt_init.\n");
		EVP_PKEY_free(m_pPriKey);
		m_pPriKey = NULL;
		return -1;
	}
	if(EVP_PKEY_decrypt(ctx,
		orig_data,
		&orig_data_len,
		enc_data,
		enc_data_len) <= 0)
	{
		printf("RSAKeyDecrypt failed to EVP_PKEY_decrypt.\n");
		EVP_PKEY_CTX_free(ctx);
		EVP_PKEY_free(m_pPriKey);
		m_pPriKey = NULL;
		return -1;
	}
	EVP_PKEY_CTX_free(ctx);
//	EVP_PKEY_free(m_pPriKey);
	return 0;
}

int CRsaPeer::CreateKeyPairFileInternal()
{
	RSA *rsa ;
	int modulelen = 1024 ;
	int ret ;
	unsigned long e = RSA_3 ;
	BIGNUM *bn ;
	bn = BN_new () ;
	ret = BN_set_word ( bn , e ) ;
	if ( ret != 1 )
	{
		perror ("BN_set_word method goes wrong ") ;
		return -1 ;
	}
	rsa = RSA_new () ;
	if ( RSA_generate_key_ex ( rsa , modulelen , bn , NULL ) != 1 )
	{
		perror ("RSA_generate_key_ex method goes wrong") ;
		return -1 ;
	}
	//---------------------------------------------------------------
	// public key
	BIO *bioPtr = BIO_new ( BIO_s_file () ) ;
	//----- open public key store file ------
	if ( BIO_write_filename ( bioPtr , (void*)m_strPubKeyPath.c_str ()) <= 0 )
	{
		perror ("failed to open public key file ") ;
		return -1 ;
	}
	//----- write public key into file -----
	if ( PEM_write_bio_RSAPublicKey( bioPtr , rsa ) != 1 )
	{
		perror ("failed to write RSA public key into file") ;
		return -1 ;
	}
	//----- if we get here , everything goes well -----
	printf ("generated RSA public key already written into file %s \n" , m_strPubKeyPath.c_str()) ;
	BIO_free_all( bioPtr ) ; // don't forget release and free the allocated space
	//-----------------------------------------------------------------------------------------
	//----- private key -----
	bioPtr = BIO_new_file ( m_strPriKeyPath.c_str() , "w+") ;
	if ( bioPtr == NULL )
	{
		perror ("failed to open file stores RSA private key ") ;
		return -1 ;
	}
	if ( PEM_write_bio_RSAPrivateKey ( bioPtr , rsa ,EVP_des_ede3_ofb() ,
		(unsigned char *)m_strPasswordForPrivateKey.c_str() , m_strPasswordForPrivateKey.size() , NULL , NULL ) != 1 )
	{
		perror ("failed write RSA private key into file") ;
		return -1 ;
	}
	BIO_free_all ( bioPtr ) ; // do not forget this 
	printf ("genertated RSA private key already written into file %s \n" , m_strPriKeyPath.c_str () ) ;

	return 0 ;
}

二 客户端生成密钥和发送密钥

BOOL CClientAffairs::ExchangeKey()
{
	if (!m_pNetWorkBase->m_socket)
	{
		return FALSE;
	}
	string strPubKeyPath = MakeTempFileName();
	string strPriKeyPath = MakeTempFileName();
	string strPassword = "Wehavetowork8daysperweekbutonlYpayed1.0$";
	// 生成公钥和私钥文件
	CRsaPeer* rsaKeyPair = CRsaPeer::CreateRsaFiles(strPubKeyPath,strPriKeyPath,strPassword);
	DebugMessageA("客户端生成的公钥  = %s",strPubKeyPath.c_str());
	if (!rsaKeyPair)
	{
		ERROR_MSG("生成RSA密钥失败了。");
		return FALSE;
	}
	// 打开公钥文件,读入内容到缓冲区中。
	string strPublicKey;
	FileReader fr(strPubKeyPath);
	if (fr.open())
	{
		strPublicKey = fr.read();
		fr.close();
	}
	if (strPublicKey.empty())
	{
		ERROR_MSG("读取公钥失败了。");
		delete rsaKeyPair;
		return FALSE;
	}
	m_rsaPeerClient = rsaKeyPair;
	int iSend = m_pNetWorkBase->SendMsg(strPublicKey,NETMSG_EXC_RSA_PUBLIC_KEY);
	if (iSend == MEMORY_BAD_ALLOC)
	{
		ERROR_MSG("申请缓冲区失败了。");
	}
	if (iSend <= 0)
	{
		return FALSE;
	}
	BOOL bRet = FALSE;
	m_iAffairId = affair_exchange_public_key;
	DWORD dwWait = WaitForSingleObject(m_hEventExcKey,TIMEOUT_OF_EXCHANGE_KEY);
	if (dwWait == WAIT_OBJECT_0)
	{
		bRet = TRUE;
	}
	//else time out or error happened.
	return bRet;
}

三 服务器接收密钥、生成服务器密钥并发送给客户端

BOOL PackNetworkObject::OnExcPubKeys( BYTE *pData, WORD wSize )
{
	if (m_rsaKeyClientPub || m_rsaKeyServerPair)
	{
		DebugMessageA("Error,sesson %d has exchanged pubkey!",GetSession()->GetSocket());
		return FALSE;
	}
	// 保存到临时文件
	string strClientPubKeyFile = MakeTempFileName();
	FileWriter fw(strClientPubKeyFile);
	string data((char*)pData,wSize);
	fw.write(data);
	fw.close();
	DebugMessageA("服务器收到了客户端的公钥并保存在 : %s",strClientPubKeyFile.c_str());
	// 仅存放公钥
	m_rsaKeyClientPub = new CRsaPeer(strClientPubKeyFile,"","");
	// 检查是否能正常打开这个公钥
	if (0 != m_rsaKeyClientPub->OpenPublicKey())
	{
		return FALSE;
	}
	// 服务器生成一对KEY,将公钥发送给客户端使用。
	string strPubKeyPath = MakeTempFileName();
	string strPriKeyPath = MakeTempFileName();
	string strPassword = "Wehavetowork8daysperweekbutonlYpayed1.0$";
	// 生成公钥和私钥文件
	m_rsaKeyServerPair = CRsaPeer::CreateRsaFiles(strPubKeyPath,strPriKeyPath,strPassword);
	if (!m_rsaKeyServerPair)
	{
		DebugMessageA("生成RSA密钥失败了。");
		return FALSE;
	}
	// 打开公钥文件,读入内容到缓冲区中。
	string strPublicKey;
	FileReader fr(strPubKeyPath);
	if (fr.open())
	{
		strPublicKey = fr.read();
		fr.close();
	}
	if (strPublicKey.empty())
	{
		DebugMessageA("读取公钥失败了。");
		return FALSE;
	}
	// 发送出去
	return SendMsg(strPublicKey,NETMSG_EXC_RSA_PUBLIC_KEY);
}

四 客户端接收并保存服务器的公钥

int CClientAffairs::OnExcRsaPublicKeyRecv( char* pRecvBuffer, int packlen )
{
	string strSrvKeyFile = MakeTempFileName();
	FileWriter fw(strSrvKeyFile);
	string data(pRecvBuffer,packlen);
	fw.write(data);
	fw.close();
	// 仅存放公钥
	ASSERT(m_rsaPeerServer == NULL);
	m_rsaPeerServer = new CRsaPeer(strSrvKeyFile,"","");
	// 检查是否能正常打开这个公钥
	if (0 == m_rsaPeerServer->OpenPublicKey())
	{
		SetEvent(m_hEventExcKey);
	}
	// 
	return 0;
}

五 客户端使用服务器公钥加密账号密码信息并且发送

BOOL CClientAffairs::Login( const std::string& acc,const std::string& pswd )
{
	if (acc.empty() || pswd.empty())
	{
		ERROR_MSG("长度有问题。");
		return FALSE;
	}
	unsigned char BufferForEncodeAcc[1024] = {0};
	unsigned char BufferForEncodePswd[1024] = {0};
	if (!m_rsaPeerClient ||
		!m_rsaPeerServer)
	{
		ERROR_MSG("密钥未准备好。");
		return FALSE;
	}
	size_t iEnAccSize = sizeof(BufferForEncodeAcc);
	//使用服务器提供的公钥加密账号
	if (0 != m_rsaPeerServer->Encrypt((const unsigned char*)acc.c_str(),acc.length(),BufferForEncodeAcc,iEnAccSize))
	{
		ERROR_MSG("加密失败 - 1。");
		return FALSE;
	}
	if (0 == iEnAccSize)
	{
		ERROR_MSG("加密失败 - 2。");
		return FALSE;
	}
	//使用服务器提供的公钥加密密码
	size_t iEnPswdSize = sizeof(BufferForEncodePswd);
	if (0 != m_rsaPeerServer->Encrypt((const unsigned char*)pswd.c_str(),pswd.length(),BufferForEncodePswd,iEnPswdSize))
	{
		ERROR_MSG("加密失败 - 3。");
		return FALSE;
	}

	if (0 == iEnPswdSize)
	{
		ERROR_MSG("加密失败 - 4。");
		return FALSE;
	}

	//组织缓冲区并发送
	//缓冲区的格式(账号长度(USHORT) + 密码长度(USHORT) + 账号 + 密码)
	size_t needlen = iEnAccSize + sizeof(WORD)*2 + iEnPswdSize;
	char* pBuf = new char[needlen];
	BufferSetV<WORD>(pBuf, 0, LOWORD(iEnAccSize));
	BufferSetV<WORD>(pBuf, 2, LOWORD(iEnPswdSize));
	memcpy(pBuf + 4, BufferForEncodeAcc, iEnAccSize);
	memcpy(pBuf + 4 + iEnAccSize , BufferForEncodePswd, iEnPswdSize);
	BOOL bRet = (0 < m_pNetWorkBase->SendMsg(pBuf,needlen,NETMSG_EXC_RSA_LOGIN));
	delete [] pBuf;
	if (!bRet)
	{
		return FALSE;
	}
	//等待服务器的相应,等待的时间为TIMEOUT_OF_LOGIN
	m_iAffairId = affair_login;
	DWORD dwWait = WaitForSingleObject(m_hEventLogin,TIMEOUT_OF_LOGIN);
	if (dwWait == WAIT_OBJECT_0)
	{
		bRet = TRUE;
	}
	else
	{
		ERROR_MSG("等待服务器登录响应失败了");
		return FALSE;
	}
	//
	return bRet;
}

六 服务器验证账号密码信息并发送用户信息

BOOL PackNetworkObject::OnLogin( BYTE *pData, WORD wSize )
{
	ASSERT(wSize >= 4);
	if (!m_rsaKeyClientPub || !m_rsaKeyServerPair)
	{
		DebugMessageA("Error,sesson %d has NOT exchanged pubkey!",GetSession()->GetSocket());
		return FALSE;
	}
	// 1 从数据中分割出账号,密码。头两个字节分别为加密后账号密码的长度
	WORD accLen = BufferGetV<WORD>(pData,0);
	WORD pswdLen = BufferGetV<WORD>(pData,2);
	// 1.1 校验长度
	if (accLen + pswdLen + 4 != wSize)
	{
		DebugMessageA("Error,sesson %d 登录数据包长度不对!",GetSession()->GetSocket());
		return FALSE;
	}
	// 1.2 分离账号
	BYTE* pEncAcc = new BYTE[accLen];
	if (!pEncAcc)
	{
		return FALSE;
	}
	BYTE* pEncPsw = new BYTE[pswdLen];
	if (!pEncPsw)
	{
		return FALSE;
	}
	memcpy(pEncAcc, pData+4, accLen);
	memcpy(pEncPsw, pData+4+accLen, pswdLen);
	BOOL bDecodeResult = FALSE;
	BOOL bLoginResult = FALSE;

	BYTE pDecAcc[1024] = {0};
	BYTE pDecPsw[1024] = {0};
	do
	{
		// 2 使用服务器私钥解密
		size_t DecAccLen = sizeof(pDecAcc);
		memset(pDecAcc,0,sizeof(pDecAcc));
		size_t DecPswLen = sizeof(pDecPsw);
		memset(pDecPsw,0,sizeof(pDecPsw));
		if (0 != m_rsaKeyServerPair->Decrypt(pEncAcc,accLen,pDecAcc,DecAccLen))
		{
			DebugMessageA("Error,sesson %d 解密账号失败 - 1!",GetSession()->GetSocket());
			break;
		}
		if (DecAccLen <= 0)
		{
			DebugMessageA("Error,sesson %d 解密账号失败 - 2!",GetSession()->GetSocket());
			break;
		}
		if (0 != m_rsaKeyServerPair->Decrypt(pEncPsw,pswdLen,pDecPsw,DecPswLen))
		{
			DebugMessageA("Error,sesson %d 解密密码失败 - 1!",GetSession()->GetSocket());
			break;
		}
		if (DecPswLen <= 0)
		{
			DebugMessageA("Error,sesson %d 解密密码失败 - 2!",GetSession()->GetSocket());
			break;
		}
		bDecodeResult = TRUE;

	}while(false);
	delete [] pEncAcc;
	delete [] pEncPsw;
	if (!bDecodeResult)
	{
		Disconnect();
	}
	int iResultVerify = VerifyUserAuthrizetion(pDecAcc,pDecPsw);
	if (iResultVerify != 0)
	{
		m_bLogined = FALSE;
		DebugMessageA("Error,sesson %d 校验账号密码失败,id=%d!",GetSession()->GetSocket(),iResultVerify);
		BYTE LoginFailedMsg[4] = {0};
		BufferSetV<int>(LoginFailedMsg,0,iResultVerify);
		return SendMsg(LoginFailedMsg,4,NETMSG_ACK_LOGIN_FAILED);
	}
	else
	{
		ASSERT(m_pUserInfo);
		if (!m_pUserInfo)
		{
			Disconnect();
			return FALSE;
		}
		string strUserInfo = m_pUserInfo->toJsonString();
		if (strUserInfo.empty())
		{
			Disconnect();
			return FALSE;
		}
		DebugMessageA("sesson %d 成功登录,用户名: %s!",GetSession()->GetSocket(),m_pUserInfo->m_strUserName.c_str());
		m_bLogined = TRUE;
		
		BYTE pEncUserInfo[1024] = {0};
		size_t SizeOfEncBuf = sizeof(pEncUserInfo);
		if (0 != m_rsaKeyClientPub->Encrypt((BYTE*)strUserInfo.c_str(),strUserInfo.length(),pEncUserInfo,SizeOfEncBuf)
			|| 0 == SizeOfEncBuf)
		{
			DebugMessageA("加密失败了");
			Disconnect();
			return FALSE;
		}
		
		int msgLen = SizeOfEncBuf;
		BYTE* LoginMsg = new BYTE[msgLen];
		if (!LoginMsg)
		{
			Disconnect();
			return FALSE;
		}
		// TODO 组织缓冲区要改成用流式处理,经常漏算偏移。
		memcpy(LoginMsg, pEncUserInfo, SizeOfEncBuf);
		BOOL bRet = SendMsg(LoginMsg,msgLen,NETMSG_ACK_LOGIN_SUCCESS);
		delete [] LoginMsg;
		return bRet;
	}
}


七 客户端解密用户信息

int CClientAffairs::OnLoginSuccess( char* pRecvBuffer,int packlen )
{
	char Desc[1024] = {0};
	size_t descSize = sizeof(Desc);

	int iRet = m_rsaPeerClient->Decrypt((const unsigned char*)pRecvBuffer,packlen,(unsigned char*)Desc,descSize);
	if (iRet != 0 || descSize >= sizeof(Desc))
	{
		ERROR_MSG("解密失败,返回%d",iRet);
		return -1;
	}

	Desc[descSize] = 0;
	ERROR_MSG("成功获取用户信息:%s",Desc);
	m_UserInfo = CUserInfo::fromJsonString(Desc);
	return 0;
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值