OpenSSL 实现RSA AES加解密

OpenSSL初学总结

20081229日星期一

最近一段时间看些关于OpenSSL加密函数的使用,现将一些使用总结如下:

1、  OpenSSL简介:

openssl是一个功能丰富且自包含的开源安全工具箱。它提供的主要功能有:SSL协议实现(包括SSLv2SSLv3TLSv1)、大量软算法(对称/非对称/摘要)、大数运算、非对称算法密钥生成、ASN.1编解码库、证书请求(PKCS10)编解码、数字证书编解码、CRL编解码、OCSP协议、数字证书验证、PKCS7标准实现和PKCS12个人数字证书格式实现等功能。

openssl采用C语言作为开发语言,这使得它具有优秀的跨平台性能。openssl支持LinuxUNIXwindowsMac等平台。

2、  OpenSSL加密算法——AES加密流程:

(1)       加密

设置加密密钥:AES_set_encrypt_key

加密:AES_encrypt

(2)       减密

设置减密密钥:AES_set_decrypt_key

减密:AES_decrypt

3、  OpenSSL加密算法——RSA加密流程:

(1)       公钥加密,私钥减密

初始化RSA加密:RSA_new

设置密钥:RSA_general_key(自动生成)和BN_bin2bn(加载)

验证密钥:RSA_check_key

指定Padding和要加密的长度(通常选择RSA_PKCS1_PADDING

加密:RSA_public_encrypt

减密:RSA_private_decrypt

释放RSARSA_free

(2)       私钥加密,公钥减密

初始化RSA加密:RSA_new

设置密钥:RSA_general_key(自动生成)和BN_bin2bn(加载)

验证密钥:RSA_check_key

指定Padding和要加密的长度(通常选择RSA_PKCS1_PADDING

加密:RSA_private_encrypt

减密:RSA_public_decrypt

释放RSARSA_free

4、  对上述两种算法接口的简单封装:

CMyOpenSSL头文件:

//------------------------------------------------------------------------------------------

/*

   类名:CMyOpenSSL

   功能:实现了对OpenSSL中RSA和AES加密的基本操作的简单封装

*/

 

#pragmaonce

 

#include<openssl/rsa.h>

#include<openssl/sha.h>

#include<openssl/aes.h>

#pragmacomment(lib,"libeay32.lib")

#include"define.h"

 

 

#define OPENSSL_RSA_BUFFBLOCK 128           // RSA缓冲区块

#define OPENSSL_AES_BUFFBLOCK 16            // AES缓冲区块

 

 

enum RSA_TYPE                               // 加密类型

{

     PUBENCRYPT,

     PRIENCRYPT

};

 

enum PADDING                                // Padding类型

{

     RSA_NO,

     RSA_PKCS1,

     RSA_SSLV23,

//   RSA_X931,

     RSA_PKCS1_OAEP

};

 

 

constint RSA_bits = 1024;                  // RSA bits

constunsignedlong e = RSA_3;

 

constint AES_bits = 128;                   // AES bits

 

// class CMyOpenSSL //----

 

class CMyOpenSSL

{

public:

     CMyOpenSSL(void);

     ~CMyOpenSSL(void);

 

     ULONGLONG m_sourceFileLen;              // 原文件大小

     int m_len;                              // 加减密缓存大小

     virtualvoid GetSourceFileLen(ULONGLONG len);

 

// RSA Encrypt/Decrypt //----

public:

     void InitRSA();                         // 初始化RSA加密

     void FreeRSA();                         // 释放RSA加密

 

     void GenerateKey();                     // 自动生成密钥

     void GetEncryptKey(RSA_TYPE type);      // 根据加密类型,将外界导入的密钥生成RSA加密密钥

     void GetDecryptKey(RSA_TYPE type);      // 根据加密类型,将外界导入的密钥生成RSA减密密钥

     BOOL CheckKey();                        // 验证RSA密钥信息

 

     int RSAEncrypt(unsignedchar from[OPENSSL_RSA_BUFFBLOCK],unsignedchar to[OPENSSL_RSA_BUFFBLOCK],

         RSA_TYPE type);                     // RSA加密函数

     void RSADecrypt(unsignedchar from[OPENSSL_RSA_BUFFBLOCK],unsignedchar to[OPENSSL_RSA_BUFFBLOCK],

         RSA_TYPE type);                     // RSA减密函数

 

     void MyEncryptFile(LPCTSTR sourceFileName,LPCTSTR destFileName,RSA_TYPE type);

                                             // RSA文件加密函数

     void MyDecryptFile(LPCTSTR sourceFileName,LPCTSTR destFileName,RSA_TYPE type);

                                             // RSA文件减密函数

 

     int GetBuffLen(PADDING padding);        // 获取加减密缓冲区大小

 

private:

     RSA *m_rsa;                             // RSA密钥

     int m_padding;                          // RSA padding类型

     int m_nRetLen;                          // 加密返回的密文长度

     int m_nRet;                             // 返回值

//------------------------------------------------------------------------------------------

 

// AES Encrypt/Decrypt //----

public:

     void SetEncryptKey(unsignedchar *aes_key); // 设置AES加密密钥

     void SetDecryptKey(unsignedchar *aes_key); // 设置AES减密密钥

 

     void MyAESEncryptFile(LPCTSTR sourceFileName,LPCTSTR destFileName,unsignedchar *aes_key);

                                                 // AES文件加密函数

     void MyAESDecryptFile(LPCTSTR sourceFileName,LPCTSTR destFileName,unsignedchar *aes_key);

                                                 // AES文件减密函数

 

private:

     AES_KEY m_key;                              // AES密钥

     unsignedchar m_from[OPENSSL_AES_BUFFBLOCK],m_to[OPENSSL_AES_BUFFBLOCK]; // 输入、输出缓冲区

};

//--------------------------------------------------------------------------------------

CMyOpenSSL源文件:

#include"StdAfx.h"

#include"MyOpenSSL.h"

 

CMyOpenSSL::CMyOpenSSL(void)

{

     m_len = 0;

     m_nRetLen = OPENSSL_RSA_BUFFBLOCK;

}

 

CMyOpenSSL::~CMyOpenSSL(void)

{

}

 

// RSA //-----

 

void CMyOpenSSL::InitRSA()

{

     m_rsa = RSA_new();

}

 

void CMyOpenSSL::FreeRSA()

{

     RSA_free(m_rsa);

}

 

void CMyOpenSSL::GenerateKey()

{

     m_rsa = RSA_generate_key(RSA_bits,e,NULL,NULL);

}

 

void CMyOpenSSL::GetEncryptKey(RSA_TYPE type)

{

     m_rsa->n = BN_bin2bn(rsa_N,sizeof(rsa_N),m_rsa->n);

//   if (type == PUBENCRYPT)

//   {

//       m_rsa->e = BN_bin2bn(rsa_E,sizeof(rsa_E),m_rsa->e);

//   }

//   else if (type == PRIENCRYPT)

     {

         m_rsa->e = BN_bin2bn(rsa_E,sizeof(rsa_E),m_rsa->e);

         m_rsa->d = BN_bin2bn(rsa_D,sizeof(rsa_D),m_rsa->d);

     }

     m_rsa->p = BN_bin2bn(rsa_P,sizeof(rsa_P),m_rsa->p);

     m_rsa->q = BN_bin2bn(rsa_Q,sizeof(rsa_Q),m_rsa->q);

     m_rsa->dmp1 = BN_bin2bn(rsa_DP,sizeof(rsa_DP),m_rsa->dmp1);

     m_rsa->dmq1 = BN_bin2bn(rsa_DQ,sizeof(rsa_DQ),m_rsa->dmq1);

     m_rsa->iqmp = BN_bin2bn(rsa_InverseQ,sizeof(rsa_InverseQ),m_rsa->iqmp);

}

 

void CMyOpenSSL::GetDecryptKey(RSA_TYPE type)

{

     m_rsa->n = BN_bin2bn(rsa_N,sizeof(rsa_N),m_rsa->n);

//   if (type == PRIENCRYPT)

//   {

//       m_rsa->e = BN_bin2bn(rsa_E,sizeof(rsa_E),m_rsa->e);

//   }

//   else if (type == PUBENCRYPT)

     {

         m_rsa->e = BN_bin2bn(rsa_E,sizeof(rsa_E),m_rsa->e);

         m_rsa->d = BN_bin2bn(rsa_D,sizeof(rsa_D),m_rsa->d);

     }

     m_rsa->p = BN_bin2bn(rsa_P,sizeof(rsa_P),m_rsa->p);

     m_rsa->q = BN_bin2bn(rsa_Q,sizeof(rsa_Q),m_rsa->q);

     m_rsa->dmp1 = BN_bin2bn(rsa_DP,sizeof(rsa_DP),m_rsa->dmp1);

     m_rsa->dmq1 = BN_bin2bn(rsa_DQ,sizeof(rsa_DQ),m_rsa->dmq1);

     m_rsa->iqmp = BN_bin2bn(rsa_InverseQ,sizeof(rsa_InverseQ),m_rsa->iqmp);

}

 

BOOL CMyOpenSSL::CheckKey()

{

     m_nRet = RSA_check_key(m_rsa);

     return m_nRet;

}

 

int CMyOpenSSL::GetBuffLen(PADDING padding)

{

     m_len = RSA_size(m_rsa);

 

     switch (padding)

     {

     case RSA_NO:

         m_padding = RSA_NO_PADDING;

         m_len = m_len;

         break;

 

     case RSA_PKCS1:

         m_padding = RSA_PKCS1_PADDING;

         m_len -= 11;

         break;

 

     case RSA_SSLV23:

         m_padding = RSA_SSLV23_PADDING;

         m_len -= 11;

         break;

 

//   case RSA_X931:

//       m_padding = RSA_X931_PADDING;

//       m_len -= 2;

//       break;

 

     case RSA_PKCS1_OAEP:

         m_padding = RSA_PKCS1_OAEP_PADDING;

         m_len = m_len - 2 * SHA_DIGEST_LENGTH - 2;

         break;

 

     default:

         break;

     }

 

     return m_len;

}

 

int CMyOpenSSL::RSAEncrypt(unsignedchar *from,unsignedchar *to,

                                 RSA_TYPE type)

{

     if (type == PUBENCRYPT)

     {

         m_nRet = RSA_public_encrypt(m_len,from,to,m_rsa,m_padding);

     }

     else

     {

         m_nRet = RSA_private_encrypt(m_len,from,to,m_rsa,m_padding);

     }

     return m_nRet;

}

 

void CMyOpenSSL::RSADecrypt(unsignedchar *from,unsignedchar *to,

                                 RSA_TYPE type)

{

     if (type == PUBENCRYPT)

     {

         m_nRet = RSA_private_decrypt(m_nRetLen,from,to,m_rsa,m_padding);

     }

     else

     {

         m_nRet = RSA_public_decrypt(m_nRetLen,from,to,m_rsa,m_padding);

     }

}

 

void CMyOpenSSL::MyEncryptFile(LPCTSTR sourceFileName, LPCTSTR destFileName, RSA_TYPE type)

{

     // Open source file and dest file

     CFile sourceFile,destFile;

     sourceFile.Open(sourceFileName,CFile::modeRead);

     destFile.Open(destFileName,CFile::modeCreate | CFile::modeWrite);

 

     ULONGLONG sourceLen,destLen,sourceOff,destOff;

     sourceLen = sourceFile.GetLength();

     m_sourceFileLen = sourceLen;

     destLen = 0;

     sourceOff = 0;

     destOff = 0;

 

     // read, encrypt and write

     unsignedchar sourceBuff[OPENSSL_RSA_BUFFBLOCK],destBuff[OPENSSL_RSA_BUFFBLOCK];

     while (sourceLen > destLen)

     {

         sourceFile.Read(sourceBuff,m_len);

         // Encrypt

         m_nRetLen = RSAEncrypt(sourceBuff,destBuff,PRIENCRYPT);

         destFile.Write(destBuff,m_nRetLen);

 

         // Update

         destLen = destFile.GetLength();

         sourceOff += m_len;

         destOff += m_nRetLen;

 

         // Move the point to current pos

         sourceFile.Seek(sourceOff,CFile::begin);

         destFile.Seek(destOff,CFile::begin);

     }

 

     // Close file

     sourceFile.Close();

     destFile.Close();

}

 

void CMyOpenSSL::MyDecryptFile(LPCTSTR sourceFileName, LPCTSTR destFileName, RSA_TYPE type)

{

     // Open and read file

     CFile sourceFile,destFile;

     sourceFile.Open(sourceFileName,CFile::modeRead);

     destFile.Open(destFileName,CFile::modeCreate | CFile::modeWrite);

     ULONGLONG destLen,sourceOff,destOff;

     destLen = 0;

     sourceOff = 0;

     destOff = 0;

 

     unsignedchar sourceBuff[OPENSSL_RSA_BUFFBLOCK],destBuff[OPENSSL_RSA_BUFFBLOCK];

     while (m_sourceFileLen > destLen)

     {

         sourceFile.Read(sourceBuff,m_nRetLen);

         // Decrypt

         RSADecrypt(sourceBuff,destBuff,PRIENCRYPT);

 

         if (m_sourceFileLen - destLen < OPENSSL_RSA_BUFFBLOCK)

              m_len = (int)(m_sourceFileLen - destLen);

 

         destFile.Write(destBuff,m_len);

 

         // Update

         destLen = destFile.GetLength();

         sourceOff += m_nRetLen;

         destOff += m_len;

 

         // Move the point to current pos

         sourceFile.Seek(sourceOff,CFile::begin);

         destFile.Seek(destOff,CFile::begin);

     }

 

     // Close file

     sourceFile.Close();

     destFile.Close();

}

 

//---------------------------------------------------------------------------------------------

 

// AES //----

 

void CMyOpenSSL::SetEncryptKey(unsignedchar *aes_key)

{

     AES_set_encrypt_key(aes_key,AES_bits,&m_key);

}

 

void CMyOpenSSL::SetDecryptKey(unsignedchar *aes_key)

{

     AES_set_decrypt_key(aes_key,AES_bits,&m_key);

}

 

void CMyOpenSSL::MyAESEncryptFile(LPCTSTR sourceFileName, LPCTSTR destFileName,unsignedchar *aes_key)

{

     // Set Encrypt Key

     SetEncryptKey(aes_key);

 

     // Open source file and dest file

     CFile sourceFile,destFile;

     sourceFile.Open(sourceFileName,CFile::modeRead);

     destFile.Open(destFileName,CFile::modeCreate | CFile::modeWrite);

 

     ULONGLONG sourceLen,destLen,sourceOff,destOff;

     sourceLen = sourceFile.GetLength();

     m_sourceFileLen = sourceLen;

     destLen = 0;

     sourceOff = 0;

     destOff = 0;

     m_len = OPENSSL_AES_BUFFBLOCK;

 

     // read, encrypt and write

     while (sourceLen > destLen)

     {

         sourceFile.Read(m_from,m_len);

         // Encrypt

         AES_encrypt(m_from,m_to,&m_key);

         destFile.Write(m_to,m_len);

 

         // Update

         destLen = destFile.GetLength();

         sourceOff += m_len;

         destOff += m_len;

 

         // Move the point to current pos

         sourceFile.Seek(sourceOff,CFile::begin);

         destFile.Seek(destOff,CFile::begin);

     }

 

     // Close file

     sourceFile.Close();

     destFile.Close();

}

 

void CMyOpenSSL::MyAESDecryptFile(LPCTSTR sourceFileName, LPCTSTR destFileName,unsignedchar *aes_key)

{

     // Set Decrypt Key

     SetDecryptKey(aes_key);

 

     // Open and read file

     CFile sourceFile,destFile;

     sourceFile.Open(sourceFileName,CFile::modeRead);

     destFile.Open(destFileName,CFile::modeCreate | CFile::modeWrite);

     ULONGLONG destLen,sourceOff,destOff;

     destLen = 0;

     sourceOff = 0;

     destOff = 0;

     m_len = OPENSSL_AES_BUFFBLOCK;

 

     while (m_sourceFileLen > destLen)

     {

         sourceFile.Read(m_from,m_len);

         // Decrypt

         AES_decrypt(m_from,m_to,&m_key);

 

         if (m_sourceFileLen - destLen < OPENSSL_AES_BUFFBLOCK)

              m_len = (int)(m_sourceFileLen - destLen);

 

         destFile.Write(m_to,m_len);

 

         // Update

         destLen = destFile.GetLength();

         sourceOff += m_len;

         destOff += m_len;

 

         // Move the point to current pos

         sourceFile.Seek(sourceOff,CFile::begin);

         destFile.Seek(destOff,CFile::begin);

     }

 

     // Close file

     sourceFile.Close();

     destFile.Close();

}

 

void CMyOpenSSL::GetSourceFileLen(ULONGLONG len)

{

     m_sourceFileLen = len;

}

 

CMyCryptClass头文件:

//--------------------------------------------------------------------------------------------------------

 

/*

   类名:CMyCryptClass

   功能:实现了RSA非对称加密对称密钥,AES对称加密文件数据

*/

 

#pragmaonce

 

#include"MyOpenSSL.h"

 

#define MYCRYPT_PLAINTEXT_BLOCK OPENSSL_AES_BUFFBLOCK     // 明文缓冲区块

#define MYCRYPT_CIPHERTEXT_BLOCK OPENSSL_RSA_BUFFBLOCK    // 密文缓冲区块

 

class CMyCryptClass

{

public:

     CMyCryptClass(void);

     ~CMyCryptClass(void);

 

// Operations

public:

     BOOL InitCrypt();                           // 初始化加密

     void DestroyCrypt();                        // 销毁加密

 

     BOOL MyKeyEncrypt(unsignedchar *KeyFrom,unsignedchar *KeyTo,RSA_TYPE type);

                                                 // 加密密钥函数

     BOOL MyKeyDecrypt(unsignedchar *KeyFrom,unsignedchar *KeyTo,RSA_TYPE type);

                                                 // 减密密钥函数

 

     BOOL MyFileEncrypt(LPCTSTR sourceFileName,LPCTSTR destFileName,unsignedchar *key);

                                                 // 文件加密函数

     BOOL MyFileDecrypt(LPCTSTR sourceFileName,LPCTSTR destFileName,unsignedchar *key);

                                                 // 文件减密函数

     void GetSourceFileLen(ULONGLONG len);

 

private:

     CMyOpenSSL *m_OpenSSL;

     ULONGLONG m_sourceFileLen;

};

 

//---------------------------------------------------------------------------------------

 

CMyCryptClass源文件:

#include"StdAfx.h"

#include"MyCryptClass.h"

 

CMyCryptClass::CMyCryptClass(void)

{

     m_OpenSSL = new CMyOpenSSL();

}

 

CMyCryptClass::~CMyCryptClass(void)

{

     delete m_OpenSSL;

}

 

BOOL CMyCryptClass::InitCrypt()

{

     m_OpenSSL->InitRSA();

     return TRUE;

}

 

void CMyCryptClass::DestroyCrypt()

{

     m_OpenSSL->FreeRSA();

}

 

BOOL CMyCryptClass::MyKeyEncrypt(unsignedchar *KeyFrom,unsignedchar *KeyTo,

                                    RSA_TYPE type)

{

     m_OpenSSL->GetEncryptKey(type);

 

     if (!m_OpenSSL->CheckKey()) return FALSE;

 

     m_OpenSSL->GetBuffLen(RSA_PKCS1);

 

     m_OpenSSL->RSAEncrypt(KeyFrom,KeyTo,type);

 

     return TRUE;

}

 

BOOL CMyCryptClass::MyKeyDecrypt(unsignedchar *KeyFrom,unsignedchar *KeyTo,

                                    RSA_TYPE type)

{

     m_OpenSSL->GetDecryptKey(type);

 

     if (!m_OpenSSL->CheckKey()) return FALSE;

    

     m_OpenSSL->GetBuffLen(RSA_PKCS1);

 

     m_OpenSSL->RSADecrypt(KeyFrom,KeyTo,type);

 

     return TRUE;

}

 

BOOL CMyCryptClass::MyFileEncrypt(LPCTSTR sourceFileName,LPCTSTR destFileName,unsignedchar *key)

{

     m_OpenSSL->MyAESEncryptFile(sourceFileName,destFileName,key);

 

     return TRUE;

}

 

BOOL CMyCryptClass::MyFileDecrypt(LPCTSTR sourceFileName,LPCTSTR destFileName,unsignedchar *key)

{

     m_OpenSSL->MyAESDecryptFile(sourceFileName,destFileName,key);

 

     return TRUE;

}

 

void CMyCryptClass::GetSourceFileLen(ULONGLONG len)

{

     m_sourceFileLen = len;

}

5、  使用上面的封装类实现RSA加密对称密钥,AES对称加密文件的小示例:

CMyCryptClass myCrypt;

ULONGLONG fileLen;

 

char sourceCABFileName[] = "C://Documents and Settings//Administrator//桌面//old//GlobalUpdateFile(src).cab";

char EncryptedCABFileName[] = "C://Documents and Settings//Administrator//桌面//old//GlobalUpdateFile.cab";

 

void CTestCabDlg::OnBnClickedButton5()

{

     // 制作CAB文件,加密并加密密钥写入CAB文件

     unsignedchar KeyBeforeEncrypt[MYCRYPT_PLAINTEXT_BLOCK];

     unsignedchar KeyAfterEncrypt[MYCRYPT_CIPHERTEXT_BLOCK];

     for (int i = 0; i < MYCRYPT_PLAINTEXT_BLOCK; i++)

         KeyBeforeEncrypt[i] = aes_Key[i];

 

     // Make CAB file //----

     CCabinet a;

     a.CreateCabinet(sourceCABFileName);

     a.AddFile2Cab("C://Documents and Settings//Administrator//桌面//old//爱要怎么说出口(赵传).mp3");

     a.AddFile2Cab("C://Documents and Settings//Administrator//桌面//old//TSReader.ocx");

     // add new file for CAB file

     // ...

     a.FlushCab();

 

     // Get source file len

     CFile cab;

     cab.Open(EncryptedCABFileName,CFile::modeRead);

     fileLen = cab.GetLength();

     cab.Close();

 

     // Encrypt the CAB file //----

     if (!myCrypt.InitCrypt()) return;

     if (!myCrypt.MyKeyEncrypt(KeyBeforeEncrypt,KeyAfterEncrypt,PRIENCRYPT)) return;

     if (!myCrypt.MyFileEncrypt((LPCTSTR)sourceCABFileName,(LPCTSTR)EncryptedCABFileName,KeyBeforeEncrypt)) return;

     myCrypt.DestroyCrypt();

 

     // 将KeyAfterEncrypt发送给远程接收端

     CFile file;

     file.Open(EncryptedCABFileName,CFile::modeWrite);

     file.SeekToEnd();

     file.Write(KeyAfterEncrypt,MYCRYPT_CIPHERTEXT_BLOCK);

     file.Close();

}

 

// Get the file name

     CString FileBeforeDecrypt("C://Documents and Settings//Administrator//桌面//old//GlobalUpdateFile.cab"),

         FileAfterDecrypt("C://Documents and Settings//Administrator//桌面//new//GlobalUpdateFile(des).cab");

 

void CTestCabDlg::OnBnClickedButton6()

{

     // 从CAB文件中读取密钥密文,解密密钥并解密文件

     CFile sourceFile,destFile;

 

     ULONGLONG sourceFileLen;

     sourceFile.Open(FileBeforeDecrypt,CFile::modeRead);

     sourceFileLen = sourceFile.GetLength();

     sourceFile.Seek(sourceFileLen - MYCRYPT_CIPHERTEXT_BLOCK, CFile::begin);

     unsignedchar KeyBeforeDecrypt[MYCRYPT_CIPHERTEXT_BLOCK],KeyAfterDecrypt[MYCRYPT_PLAINTEXT_BLOCK];

     sourceFile.Read(KeyBeforeDecrypt,MYCRYPT_CIPHERTEXT_BLOCK);

     sourceFile.SeekToBegin();

     sourceFile.Close();

 

     // Init the crypt

     if (!myCrypt.InitCrypt()) return;

 

     // Use RSA to decrypt the key

     if (!myCrypt.MyKeyDecrypt(KeyBeforeDecrypt,KeyAfterDecrypt,PRIENCRYPT))

     {

         MessageBox("解密密钥错误");

         return;

     }

 

     // Get source file len from ini file

     ULONGLONG len = fileLen;

     myCrypt.GetSourceFileLen(len);

 

     // Use AES to decrypt the file

     if (!myCrypt.MyFileDecrypt(FileBeforeDecrypt.GetBuffer(),FileAfterDecrypt.GetBuffer(),

         KeyAfterDecrypt))

     {

         MessageBox("解密失败");

         return;

     }

     FileBeforeDecrypt.ReleaseBuffer();

     FileAfterDecrypt.ReleaseBuffer();

 

     // Free the crypt

     myCrypt.DestroyCrypt();

}
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值