OpenSSL 面向 Win32 开发人员的导览






4.87/5 (18投票s)
本文旨在为DES模式、哈希函数、AES、RSA算法及其使用OpenSSL的示例提供一个简明扼要的教程。

目录
引言
OpenSSL是一个开源工具包,实现了SSL等安全协议以及RSA、AES等多种加密算法,以及SHA1和MD5等多种哈希函数。该库因其开源性质和C语言风格的接口而受到开发者的广泛赞誉,并为许多其他语言提供了桥梁。该库的一个弱点是缺乏面向新手开发者的入门材料,并且openssl.org上的文档不够完善。本文旨在提供一些算法及其使用示例的简明教程。
OpenSSL并非预先构建,您可以从openssl.org下载源代码并自行编译,或者从以下链接下载win32环境的预构建二进制文件,本文的示例就是基于此。本文假设您已将OpenSSL安装在C:\OpenSSL-Win32路径下,并将C:\OpenSSL-Win32\include添加到编译器的头文件搜索路径,同时对库文件C:\OpenSSL-Win32\lib\VC\static也做了同样的操作(本文为简化起见将链接到静态版本库,生产环境中您应该使用动态库)。
为什么选择OpenSSL
- 它很受欢迎,是Apache、Oracle、PHP、浏览器和大多数操作系统等许多大型软件的一部分。
- 快速、轻量级且易于使用的接口。
- 存在已久,并经受住了许多攻击,如时序攻击、可预测的私钥攻击等,其中大多数漏洞已随着时间的推移得到修复。
- 开源性质为您打开了阅读源代码、更好地理解算法工作原理以及深入了解库的广阔视野。
- 在遵循少量简单条件的情况下,对于商业产品,它在apache风格的许可证下是免费的。
密码学入门
密码学是隐藏数据的科学。隐藏数据的过程称为加密(也称为encipher),结果数据称为密文,反向操作称为解密(decipher)。这两个过程都依赖于一个密钥来锁定或解锁数据。密钥有两个基本属性:长度,以比特为单位测量,如256位密钥;以及密钥是否对称。对称密钥用于加密和解密,非对称密钥算法使用两个密钥,一个用于加密(公钥),另一个用于解密(私钥)。有些算法是单向加密算法,只产生密文,称为摘要,许多哈希函数属于此类。
OpenSSL的类别
哈希算法
哈希函数是一种单向加密算法,它产生固定长度的数据,称为摘要,无论输入数据有多大,摘要的长度都是固定的。这些算法保证,如果数据发生任何一个比特的变化,都会产生一个完全不同的摘要,这取决于该数据。OpenSSL实现了多种哈希函数,本文将只介绍最流行的MD5和SHA-1。
您可能会想,为什么需要单向加密算法,以下是一些使用场景:
- 大多数现代文件系统在其目录中附加文件哈希。
- 下载管理器和种子客户端通过比较两个哈希值来保证文件下载成功。
- 开发环境通过计算当前哈希值并将其与嵌入在对象文件*.o中的哈希值进行比较,从而检测到哪些源文件发生了更改,并相应地决定是否重新编译该文件。
- 总的来说,文件哈希保证文件在从源到目标的传输过程中没有被篡改或编辑。
- 软件公司为其可执行文件生成哈希,并用其SSL证书进行签名,以保证产品的真实性。
- 像herdprotect.com这样的云端杀毒软件在本地计算文件哈希,并获取已扫描文件的结果(如果可用);否则,它会上传文件,进行扫描,并将文件哈希和结果存储在服务器上以便稍后检索。
MD5消息摘要算法是一种广泛使用的密码学哈希函数,它产生一个128位(16字节)的哈希值,通常以文本格式表示为一个32位十六进制数。以下代码片段展示了如何对简单的文本数组使用MD5算法。
首先,包含头文件并链接到库。
#include "openssl/md5.h"
#pragma comment(lib,"libeay32MD.lib")
然后,使用MD5
函数,该函数接受三个参数:作为字符串的输入数据,该字符串的长度,以及一个16字节的缓冲区,用于接收结果。第三个参数在发生错误时为null。为了确保您从算法中获得预期的结果,请将您的结果与在线服务onlinemd5.com进行比较。
char inbuffer [] = "Truth comes out of error more easily than out of confusion." ;
unsigned char outbuffer[MD5_DIGEST_LENGTH];
char digest[33] = {0} ;
MD5((unsigned char*)&inbuffer, strlen(inbuffer), (unsigned char*)&outbuffer);
for(int i = 0; i < MD5_DIGEST_LENGTH; i++)
sprintf(&digest[i*2], "%02x", (unsigned int)outbuffer[i]); // convert hex to char
cout << digest << endl ;
SHA代表安全哈希算法,它产生160位(20字节或40个十六进制字符)的哈希摘要。该算法于1993年发布了第一个版本SHA-0,第四个版本SHA-3于2012年末发布。以下代码片段展示了如何对示例文本数组使用SHA-1哈希函数。
首先,包含头文件并链接到库。
#include "openssl/sha.h"
#pragma comment(lib,"libeay32MD.lib")
然后,使用SHA1
函数,该函数接受与MD5函数相同的参数。
char inbuffer[] = "Be nice to people on your way up because you'll need them on your way down.";
unsigned char outbuffer[SHA_DIGEST_LENGTH];
SHA1((const unsigned char*) inbuffer, strlen(inbuffer) , outbuffer);
char digest[41] = "\0";
for(int i = 0 ; i < SHA_DIGEST_LENGTH ; i++)
sprintf(&digest[i*2],"%02x",outbuffer[i]); // convert hex to char
cout << digest << endl ;
本文附带的代码
- 展示了不止一种计算哈希摘要的方法。它还包含关于相同算法其他版本的更多示例,并演示了如何计算整个输入文件的哈希,代码中包含了对每个步骤的完整文档(此处示例较短,以节省文章空间)。
- 演示了高级接口
EVP_
的使用。OpenSSL提供了一个统一的高级接口,通过它调用所有算法函数。此接口的所有函数都以EVP_
前缀开头,并定义在<openssl/evp.h>
头文件中。 - 演示了MDC-2和Whirlpool等其他哈希函数的用法。
OpenSSL附带openssl.exe(位于Bin文件夹中),允许您直接从命令行生成哈希。使用以下命令行计算输入文件的哈希值。
openssl dgst -sha1 -out filename [inputfile1...]
openssl dgst -md5 -out filename [inputfile1...]
请注意,它可以同时计算不同文件的哈希值,如果缺少输出文件名参数,则默认将输出显示在屏幕上。如果openssl.exe在您的环境变量搜索路径中,则可以使用system()函数直接从代码中调用它。
DES算法
数据加密标准(DES)是一种使用56位对称密钥进行数据加密的算法。该算法由IBM在20世纪70年代初开发,如今被认为对许多应用来说不安全。这主要是因为56位密钥长度太短。也有一些分析结果表明该密码存在理论上的弱点,尽管在实践中难以实现。在三重DES或3DES(1999年发布)形式下,该算法被认为在实践中是安全的,尽管存在理论上的攻击。近年来,该密码已被AES算法取代。三重DES操作在3*56位密钥上,或总计168位。它使用第一个密钥进行加密,第二个密钥进行解密,第三个密钥再次进行加密。DES和三重DES都以不同的模式操作来生成输出密文,以下是最著名的模式。
DES CBC(分组密码链接)模式
在此模式下,明文被分成长度为64位的块。它首先将明文与一个预先初始化的64位数据(称为初始化向量(IV))进行异或运算,然后加密数据生成64位密文块。这个密文块成为下一个明文块的IV,依此类推。最后,如果明文大小小于64位,则用零(null)填充并完成加密。在解密时,异或运算发生在解密之后,并且前一个密文块成为下一个密文块的IV。
以下示例演示了如何使用DES-CBC模式加密和解密文本数组。
char inbuffer[64] = "If you see a turtle on a fence post, he has had some help." ;
int inbufferSize = strlen(inbuffer);
unsigned char outbuffer[64] ;
DES_cblock key = {0x44 ,0x6f ,0x4a ,0x6a ,0x71 ,0x71 ,0x73 ,0x74 };
DES_cblock iv = {0x6c ,0x6c ,0x4b ,0x4b ,0x71 ,0x54 ,0x31 ,0x75};
DES_key_schedule schedule;
DES_set_odd_parity(&key);
DES_set_key_checked(&key, &schedule);
//encrypt
DES_ncbc_encrypt((const unsigned char*)inbuffer,outbuffer,inbufferSize,&schedule,&iv,DES_ENCRYPT);
cout << inbuffer << endl ;
//display cipher encoded in base64
cout << base64_encode(outbuffer,64) << endl ;
//re-initialize iv since it's changed from last encrypt process.
DES_cblock iv2 = {0x6c ,0x6c ,0x4b ,0x4b ,0x71 ,0x54 ,0x31 ,0x75};
//decrypt : notice last parameter and the first two parameter have been reversed
DES_ncbc_encrypt((const unsigned char*)outbuffer,(unsigned char*)inbuffer,64 ,&schedule,&iv2,DES_DECRYPT);
cout << inbuffer << endl ;
首先,我们定义两个类型为DES_cblock
(它是unsigned char [8]
的typedef)的变量key
和iv
。DES算法中的密钥必须具有奇校验(二进制表示中1的数量为奇数),这是DES_set_odd_parity
函数的作用。然后使用DES_set_key_checked
函数将密钥转换为与体系结构相关的形式,并将其结果存储在类型为DES_key_schedule
的结构中,该结构稍后用于加密或解密。
DES_ncbc_encrypt
函数用于加密和解密,具体取决于最后一个参数是1还是0。第一个参数是要加密的输入数据或要解密的密文数据。第二个参数是接收结果的缓冲区。第三个参数是输入缓冲区的长度(字节)。第四个参数是指向我们之前创建的调度对象的指针。第五个参数是初始化向量iv
,最后一个参数是一个整数,决定是加密还是解密。
CBC模式存在多种其他形式(仅有微小改动),如传播分组密码链接(PCBC)、密码反馈(CFB)、输出反馈(OFB)、RSA CBC模式(XCBC)。本文源代码中附带了这些模式的简要描述以及三重DES-CBC和EVP_接口的示例。
DES ECB(电子密码本)模式
这是最简单的DES形式。在此模式下,每个数据块都独立于其他块进行加密或解密,无需初始化向量。这使您可以自由地加密或解密特定的数据块,或者在加密或解密后按需更改块的顺序。
以下示例演示了如何使用DES-ECB模式加密和解密文本数组。
DES_cblock key;
//the random key generator must be seeded for key to get generated properly
DES_cblock seed = {0x58 ,0x48 ,0x54 ,0x4f ,0x36 ,0x65 ,0x69 ,0x47};
RAND_seed(seed, DES_KEY_SZ);
DES_random_key(&key);// let the library pick a secure key for us
DES_key_schedule schedule;
DES_set_key_checked(&key, &schedule);
string text = "The best education in the world is that got by struggling to get a living.";
DES_cblock outbuffer , inbuffer = {0};
string cipher , back ;
for(int i = 0 , size = text.length() ; i < size ; i+=8)
{
text.copy((char*)inbuffer,8,i);
//encrypt 8-byte block atime
DES_ecb_encrypt((const_DES_cblock*)inbuffer,(DES_cblock*)outbuffer,&schedule,DES_ENCRYPT);
cipher.append((const char*)outbuffer,8); // save encrypted data to restore it later
//IMPORTANT : zero the memory of the buffer ,
//otherwise you'll get dirty read if the
//last loop iteration read is less than 8 byte
ZeroMemory(inbuffer,8);
}
//print encrypted data encode in base64
cout << base64_encode((const unsigned char *)cipher.c_str(),cipher.length()) << endl ;
for(int i = 0 , size = cipher.length() ; i < size ; i+=8)
{
cipher.copy((char*)outbuffer,8,i);
//decrypt 8-byte block atime , notice the reverse of two parameter
DES_ecb_encrypt((const_DES_cblock*)outbuffer,(DES_cblock*)inbuffer,&schedule,DES_DECRYPT);
back.append((const char*)inbuffer,8);
}
cout << back << endl ;
代码使用与DES_CBC代码相同的步骤,除了密钥是通过DES_random_key
函数自动生成的。为了使该函数正常工作,必须通过调用RAND_seed
函数来播种随机数生成器。DES_ecb_encrypt
函数在两个长度均为64位的缓冲区上操作,作为前两个参数,一个是指向schedule
对象的指针,另一个是用于加密或解密的整数标志。
AES算法
高级加密标准(AES),也称为Rijndael(是两位发明者Joan Daemen和Vincent Rijmen名字的组合),是一种对称分组密码,可以处理**128位**的数据块,使用**128位**(AES-128)、**192位**(AES-192)和**256位**(AES-256)的密钥长度。该算法于2001年开发,并花了5年时间进行标准化。2002年,AES被美国政府采纳,用于保护机密信息,现已在全球范围内使用。
AES密码使用的密钥长度决定了转换轮(比特的替换和排列)的重复次数,这些轮将输入转换为最终的密文。重复周期如下:
以下示例演示了如何使用128位密钥长度加密和解密明文数组。
//key size must be 16-byte long (AES-128)
const unsigned char userKey[] = { 0x54 ,0x68 ,0x65 ,0x20 ,0x67 ,0x72 ,0x65 ,0x61,
0x74 ,0x20 ,0x70 ,0x6c ,0x65 ,0x61 ,0x73 ,0x75 };
AES_KEY key ;
AES_set_encrypt_key(userKey,128,&key);
string input = "The great pleasure in life is doing what people say you cannot do." ;
string cipher , back ;
unsigned char inbuffer[AES_BLOCK_SIZE] = {0};
unsigned char outbuffer[AES_BLOCK_SIZE] ={0};
for ( int i = 0 , size = input.length() ; i < size ; i+=AES_BLOCK_SIZE)
{
input.copy((char*)inbuffer,AES_BLOCK_SIZE,i);
AES_encrypt(inbuffer,outbuffer,&key);
//save outbuffer to decrypting it later
cipher.append((const char*)outbuffer,AES_BLOCK_SIZE);
cout << base64_encode(outbuffer,AES_BLOCK_SIZE) ;
//IMPORTANT : to avoid dirty read if block size of last iteration is less than 16
ZeroMemory(inbuffer,AES_BLOCK_SIZE);
}
AES_set_decrypt_key(userKey,128,&key);
for ( int i = 0 , size = cipher.length() ; i < size ; i+=AES_BLOCK_SIZE)
{
cipher.copy((char*)outbuffer,AES_BLOCK_SIZE,i);
AES_decrypt(outbuffer,inbuffer,&key);
back.append((const char*)inbuffer,AES_BLOCK_SIZE);
}
cout << "\n\n" << back << "\n";
AES_set_encrypt_key
函数需要三个参数:用户密钥(通常以十六进制表示),其长度取决于第二个参数(以比特为单位的密钥长度,其他可能值为192和256),如果用户提供的数组大于第二个参数指定的长度,则忽略其余字符;第三个参数是类型为AES_KEY
的与体系结构相关的密钥形式。之后,我们定义了两个中间缓冲区inbuffer
和outbuffer
,长度均为16字节。AES_encrypt
函数使用我们之前创建的AES_KEY
对这些缓冲区进行迭代操作。AES_decrypt
函数以类似的方式操作,除了需要调用AES_set_decrypt_key
函数进行解密,该函数需要与AES_set_encrypt_key
相同的参数(并且必须匹配才能工作)。
本文附带的代码
- 实现更多关于AES模式的演示,如CBC和EBC,以及各种密钥长度。
- 展示如何使用高级接口EVP_进行加密和解密。
最后,您可以通过命令行直接加密,使用命令:
openssl enc -aes-[256|192|128]-[cbc|ebc] -in plainText.txt -out Cipher.data -pass arg [-d|-e]
公钥密码标准(PKCS)
RSA是由三位数学家Ron **R**ivest、Adi **S**hamir和Leonard **A**dleman于1977年发明的。该算法依赖于两个数学上相关的密钥:用于加密的公钥(公开密钥)和用于解密的私钥(保密)。
该算法的工作方式纯粹是数学原理,我将引导您阅读另一篇文章和视频演示,了解算法的内部工作原理,而我将专注于提供可行的示例。
- RSA工作原理及示例(来自doctrina.org)
- 公钥密码学(来自youtube.com,推荐观看,16分钟)
在哪里可以看到RSA加密?
- 软件程序包签名:签名由私钥生成,并在另一端由公钥验证。
- 安全Shell通信(SSH):其中密钥对用于身份验证,只有拥有私钥的计算机才能与拥有公钥的计算机建立连接。
- 大多数以s结尾的网络协议,如HTTPS和FTPS。
密钥对生成
要生成密钥对,请使用以下命令行:
openssl genrsa -out private.pem 4096
这将生成一个4096位的RSA密钥对,并将结果保存到private.pem文件。密钥长度小于1024位是不安全的,大于4096位是不切实际的。文件内容可能如下所示:
-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgEAuwg0331lvqcxxk7QWhWe/lnoofUvbN7mXzmczWQ63oIlIEvl
. . .
ANdcujGYIFrAUpge7BWRtHhb19xSMdnrqmTnq8g6plHSKJKZVJaMC0mkzELo
-----END RSA PRIVATE KEY-----
要从生成的密钥对中提取公钥,请使用以下命令行:
openssl rsa -pubout -in private.pem -out public.pem
这将把公钥存储在public.pem文件中。如果缺少-out参数,则默认将密钥显示在屏幕上。
要查看密钥组件,请使用以下命令行:
openssl rsa -text -in private.pem
这将显示两个素数p和q,模数n,两个指数e和d,以及密钥数据。
您可以使用RSA_generate_key_ex函数直接在代码中生成密钥对。
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/rand.h>
#pragma comment(lib,"libeay32MD.lib")
//The pseudo-random number generator must be seeded prior to calling RSA_generate_key_ex function
unsigned char seed[] = {0x58 ,0x48 ,0x54 ,0x4f ,0x36 ,0x65 ,0x69 ,0x47};
RAND_seed(seed,8);
RSA* rsa = RSA_new();//allocate empty key
int bits = 4096;
unsigned long e = RSA_F4 ; //65537 public exponent
BIGNUM* bne = BN_new(); // allocate BINNUM structure in heap
BN_set_word(bne,e); //store that public exponent in big-number object bne
// generate RSA key with length 4096 , public exponent 65537
RSA_generate_key_ex(rsa,bits,bne,NULL);
//allocate a memory BIO in heap
BIO* bio_private = BIO_new(BIO_s_mem());
BIO* bio_public = BIO_new(BIO_s_mem());
//extract private and public key to bio-object respectively
PEM_write_bio_RSAPrivateKey(bio_private, rsa, NULL, NULL, 0, NULL, NULL);
PEM_write_bio_RSAPublicKey(bio_public, rsa);
//BIO_pending function return number of byte read to bio buffer during previous step
int private_len = BIO_pending(bio_private);
int public_len = BIO_pending(bio_public);
//this two buffer will hold the keys as string
char* private_key = new char[private_len + 1];
char* public_key = new char[public_len + 1];
//copy extracted keys to string
BIO_read(bio_private, private_key, private_len);
BIO_read(bio_public, public_key, public_len);
//ensure that both keys ends with null terminator
private_key[private_len]=0;
public_key[public_len]=0;
cout << private_key << "\n\n\n\n" ;
cout << public_key << "\n\n\n\n" ;
//clean up memory
RSA_free(rsa);
BN_free(bne);
BIO_free(bio_private);
BIO_free(bio_public);
delete [] private_key;
delete [] public_key;
RAND_cleanup();
RSA
结构体包含密钥组件,该密钥通过RSA_new()
分配,并由RSA_free()
函数释放。通常,通过API分配的所有对象都必须通过API进行操作和释放。bne是一个BIGNUM
类型的指针,用于保存公指数(e
=65537)。该对象分别由BN_new()
分配,由BN_set_word()
操作,并由BN_free()
释放。RSA_generate_key_ex()
函数执行实际的密钥生成,并接受以下参数:密钥指针、密钥长度(比特)、包含公指数e
的BIGNUM
指针、素数生成期间的回调函数,通常为null。OpenSSL定义了一个输入输出抽象层,它使用相同的接口来处理许多I/O设备(套接字、终端、内存、文件),这个接口称为BIO
。BIO
对象通过BIO_new()
分配,该函数接受BIO
方法作为参数。函数BIO_s_mem()
返回内存方法类型。PEM_write_bio_RSAPrivateKey()
和PEM_write_bio_RSAPublicKey()
函数分别将私钥和公钥提取到BIO
对象中。BIO_pending()
函数返回最后一次读写操作读取或写入的字节数(在本例中是BIO的长度)。BIO_read()
将BIO的内部缓冲区复制到我们已经创建的字符字符串中,然后我们在屏幕上打印这些缓冲区。
创建密钥对后,您可以使用这些函数进行加密和解密。
int RSA_public_encrypt(int flen, const unsigned char *from, unsigned char *to, RSA *rsa,int padding); int RSA_public_decrypt(int flen, const unsigned char *from, unsigned char *to, RSA *rsa,int padding); int RSA_private_encrypt(int flen, const unsigned char *from, unsigned char *to, RSA *rsa,int padding); int RSA_private_decrypt(int flen, const unsigned char *from, unsigned char *to, RSA *rsa,int padding);
前两对使用公钥加密数据,并使用私钥解密。后两对使用私钥加密,并使用公钥解密数据(较少见)。第一对函数的使用在以下代码片段中进行了演示。
//test public encrypt and private decrypt on sample array string
void testRSA_PublicEncrypt()
{
//note the line ending char \n is part of the key
char *public_key ="-----BEGIN PUBLIC KEY-----\n\
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyb5Uhv+IZBgER1ayzbJirMm3O\n\
YXglBBNMBC4RAupw9HZT2f/vrmtshnFSmwfiJvcjXpK7OmLHQZs2WHBgaqiRnSwu\n\
EcLDNIduiSRfZtFNWsvqe9yYb5jpYoY4cbiz0xXFIAwFtV2MOfoMSwldsM8E4TFv\n\
P6QAno7/ti16LQ6ftwIDAQAB\n\
-----END PUBLIC KEY-----\n";
char* private_key = "-----BEGIN RSA PRIVATE KEY-----\n\
MIICXgIBAAKBgQCyb5Uhv+IZBgER1ayzbJirMm3OYXglBBNMBC4RAupw9HZT2f/v\n\
rmtshnFSmwfiJvcjXpK7OmLHQZs2WHBgaqiRnSwuEcLDNIduiSRfZtFNWsvqe9yY\n\
b5jpYoY4cbiz0xXFIAwFtV2MOfoMSwldsM8E4TFvP6QAno7/ti16LQ6ftwIDAQAB\n\
AoGBAJFBMF+m+oFwV9KS5OGy150VkjndMpa/eI37IR0MCJknCGQ+JJCSpjRURU//\n\
kC1Tlv+d70imwN5/08Arwl9atBlpk0EgWF/8tbHc++jT0B3BZ4cEV464bvLDEIAR\n\
dj+RHn1QeJCVfaourWdq3GS+0M3tyA9E3Jnn2ar3EG762n8xAkEA2ijrquxoC2U6\n\
s4LFkEanqqM5KpP5p4N5nl4z4IkrkxKGk9stndLwjRA2Iepg0OK9M7RpIUzvrcRA\n\
rMsRuMgGmwJBANFixmQkmU4/KdmxHsR3Rd+8F0lJX5Dw4q4/+eLHIZCvTo92xbuN\n\
kMf9EYk3ezG4agqvVf/hPXfwYbspgdd5DxUCQQCPOMgnCVbxDD8ydIrhQhF3C/tO\n\
waDn4X+pgazLHyKVRldFoGHdOAumgFsZfvaajBCsbieKrii3ypyvFA4JYoA7AkB+\n\
5JKAvCFgZy0QraOMww/IgG/ITTwqVaG6ojDpO27fBS7iCMPaXvfAC2EmPEZfong5\n\
U3sV4EXlOvuvdn8mu0nlAkEAnkXDheA8/YTj+Kj8t8EL/dIGXIcek2nCJgMoJqx0\n\
llxmewvy094b0ZgadxZWv3fhJ7ZDbjlC2tSwfdLuNsw3sQ==\n\
-----END RSA PRIVATE KEY-----\n";
char* msg = "The great pleasure in life is doing what people say you cannot do.";
char* cipher,*back ;
int cipherSize;
// encrypting
{
RSA* rsa = RSA_new() ;
//load string key to bio object
BIO* mem_bio = BIO_new_mem_buf(public_key, strlen(public_key));
//convert bio to a key
rsa = PEM_read_bio_RSA_PUBKEY(mem_bio, &rsa,NULL, NULL);
// return key size in byte
cipherSize = RSA_size(rsa)+1;
cipher = new char [cipherSize];
// Encrypting using loaded public key
cipherSize = RSA_public_encrypt(strlen(msg), (unsigned char *)msg,
(unsigned char *)cipher, rsa, RSA_PKCS1_OAEP_PADDING);
cout << base64_encode((const unsigned char*)cipher,cipherSize) << "\n\n\n" ;
BIO_free(mem_bio);
RSA_free(rsa);
}
// decrypting
{
RSA* rsa = RSA_new() ;
BIO* mem_bio = BIO_new_mem_buf(private_key, strlen(private_key));
rsa = PEM_read_bio_RSAPrivateKey(mem_bio, &rsa,NULL, NULL);
back = new char [cipherSize];
cipherSize = RSA_private_decrypt(cipherSize,(unsigned char *)cipher,
(unsigned char *)back,rsa,RSA_PKCS1_OAEP_PADDING);
back[cipherSize]=0;
cout << back << "\n\n\n" ;
BIO_free(mem_bio);
RSA_free(rsa);
}
delete[] cipher ;
delete[] back ;
}
首先,我们将密钥定义为字符串,并通过BIO_new_mem_buf()
函数将它们加载到BIO
对象中。通过调用PEM_read_bio_RSA_PUBKEY()
函数将BIO
对象转换为RSA* key
,该函数在失败时返回公钥或null。RSA_public_encrypt()
函数执行实际的加密,将消息长度作为第一个参数,然后是消息本身,用于保存结果的缓冲区,该缓冲区必须小于RSA_size(rsa)-41
(对于RSA_PKCS1_OAEP_PADDING
模式)。第四个参数是用于加密的公钥,最后是填充模式,这是一个预定义常量。该函数返回实际写入输出缓冲区的字节长度。解密遵循与加密相同的步骤,只是调用了PEM_read_bio_RSAPrivateKey()
和RSA_private_decrypt()
函数。
本文附带的代码
- 展示更多关于如何将生成的密钥保存到文件并加载回来的演示,以及如何从字符串构建密钥。
- 演示如何使用私钥加密和公钥解密整个文件。
结语
在这篇文章之后,我才刚刚触及OpenSSL提供的庞大API的表面。我已经尽我所能使示例完整、真实、全面。亲爱的读者,如果您实现了更多关于这些算法的示例,我很乐意将其添加到我的代码中,并给予您全部署名,只需在评论区留下代码即可。另外请注意,本文提供的代码的错误检查非常少,请不要复制粘贴代码并期望它在任何地方都能正常工作。
希望这篇文章是一个有益的尝试 :) 。
参考文献和致谢
- AES标准
- openssl.org上的DES文档、RSA文档。
- RSA加密标准.
- OpenSSL命令行摘要 .
- base64编码由René Nyffenegger实现。
- OpenSSL预构建二进制文件由slproweb.com提供。