常见安全算法(RSA、AES、MD5等)原理及实现和win10下OpenSSL库的使用

一、安全算法概述

安全算法通常被人们分为对称加密算法和非对称加密算法两类。

对称加密算法是指加密秘钥和解密秘钥相同的算法,即用什么加密就用什么解密,早期的安全算法均属于这一类。目前常见的对称加密算法有AES、DES等。这类安全算法存在的最大问题就是密码不容易传递,如果在传递过程中被监听,那么该算法也就随之失效了。战争剧中经常出现的密码本就属于该类算法,消息发送方和接收方使用相同的密码本进行加解密,安全起见密码本通常需要定期更换,而传递密码本的过程在电视剧中通常颇费周折。

非对称加密算法就能很好地解决传递密码的问题。非对称加密算法是指加密秘钥和解密秘钥不同的加密算法,即加密用一个密码,解密时使用另一个密码,而加密密码只能加密不能解密,且加密密码和解密密码一一对应。这就不存在密码被监听的问题了,我们结合以下过程来进行解释(明文——需要传送的原消息,密文——加密后的消息:

①A有消息要发送给B;
②B收到请求后发给A加密秘钥;
③A用加密秘钥对明文进行加密;
④A将密文发送给B;
⑤B用解密秘钥进行解密,得到明文。

在该过程中,涉及到消息传输的过程有②和④,假设有人监听了这两个过程,可以得到加密秘钥和密文,但因为密文只能用解密秘钥来解密,监听者拿不到解密秘钥,所以也就不能对密文进行解密。所以整个过程是不怕被监听的,甚至可以完全公开,只要B保存好自己的解密秘钥不被泄露即可。非对称加密算法通常利用数学原理来实现,运算量较大,且通常会有明文长度的限制,有两种方法可以解决明文长度限制的问题,一是对明文进行分段加密传输,二是用对称加密算法对明文进行加密,再利用非对称加密算法加密对称加密算法的秘钥进行传输,这样也能达到非对称加密的效果,且运算速度也比较快。

以上过程是非对称加密算法用来加密消息的过程,其实它还有另外一个用途,就是用来做数字签名。所谓数字签名其实就是发送方对自己发送的数据做出相应的标识,让接收方能够确定发送方的身份,从而确定数据的有效性。过程如下:

①A事先向B公布自己的解密秘钥,B进行存储;
②A之后有数据要发送给B,就用自己的加密秘钥进行加密,再发送给B;
③B收到密文之后用解密秘钥进行解密,如果能解开,则说明消息确实是由A发送,同时A也无法否认自己发送了该消息,因为加密秘钥和解密秘钥一一对应,只有A拥有自己的加密秘钥,该密文能被B解开则说明一定是A用自己的加密秘钥进行加密并发送给B的。

通过以上过程,B确认了A的身份,并且A不可否认发送消息的行为,实现了签名的作用。在以上过程中,监听者可以获取到A的解密秘钥和密文,虽然监听者可以由此获得明文,但签名的目的并不在于消息的保密,而在于B是否能够确认A的身份。监听者在整个过程中没有获取到A的加密秘钥,也就不能冒充A向B发送消息。故该流程中的消息传递过程也是可对外公开的,并且只要A的加密秘钥不泄露,A的身份就不会被冒充。

数字签名在实际应用过程中,通常要进行签名的数据比较长,而我们上文也提到过非对称加密算法会有明文长度的限制,所以可利用摘要算法(SHA256、MD5等,下文会进一步详细介绍)和非对称加密算法相结合的方法来实现数字签名。即先对要签名的一段数据利用摘要算法进行摘要提取,提取后的摘要用加密秘钥进行加密,加密后的密文即为签名,然后将签名连同原数据一起发送给接收方。接收方成功接收后,利用解密秘钥先对签名进行解密,得到原数据的摘要,再计算所收到的数据的摘要,如果两者相同,则可确认发送方的身份,数据有效。

二、RSA算法

RSA算法是目前公认最安全的加密算法,被广泛应用于银行系统等各个领域,我们常见的RSA-2048中的2048是指秘钥的长度,目前为止该长度的RSA算法还没有被破解过。有关RSA算法的原理请参考以下两篇文章:

http://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html
http://www.ruanyifeng.com/blog/2013/07/rsa_algorithm_part_two.html

有关原理方面的东西不再多说,网上各位大神已经讲得比较清楚。结合上文对非对称加密算法的描述来看,RSA算法在用作加密消息的时候是将公钥作为加密秘钥对外公布,将私钥保存好作为解密秘钥;在用作数字签名时,发送方用私钥将数据进行加密(即签名),将公钥对外公布,接收方接收到数据后用公钥进行解密(即验签)。

RSA的算法实现较为复杂,涉及到大数运算,自己做过一版算法的代码,当然也有参考网上的代码,完整的代码见下面的链接。用C++ 在Visual Studio 2017写的。

https://download.csdn.net/download/weixin_42967006/11456772

三、AES算法

AES是一种对称加密算法,常用的秘钥长度为128/192/256位。我们前面提到过,因为非对称加密算法的明文长度限制和效率问题,可采用对称加密算法和非对称加密算法相结合的方法来进行信息的加密。比较常见的组合就是AES和RSA相结合的方式。关于AES算法的原理我觉得下面一篇文章讲得比较清晰:

https://blog.csdn.net/u014230646/article/details/79552792

这篇文章中也有代码,大家可参考,但我当时没有采用,用了另一套代码做了修改,出处忘记了,也是用C++在VS 2017上写的,亲测好用,可自动识别128/192/256位长度的密码。下载链接:

https://download.csdn.net/download/weixin_42967006/11456846

四、MD5算法

MD5算法即上文提到过的摘要算法的一种。所谓摘要算法,即将一段数据通过一定规律地运算和变换,得到一组固定长度的特征值,通常特征值都要比数据的长度小得多,且不论数据多长,特征值的长度总是固定的。此外摘要算法要尽可能地保证在数据发生变化的时候,特征值随之变化,利用该特性,我们可以检验数据在传输过程中是否出错或被篡改,传输数据时将数据和其特征值同时发送给接收方,接收方收到数据后计算其特征值,与收到的特征值进行比对,如果相同则数据有效,不同则数据出错。但摘要算法并不能保证数据和特征值一一对应,我们简单来想:数据长度不限,故数据有无限组,特征值长度有限,故特征值有有限组,无限组的数据对应有限组的特征值,必定存在多组数据对应同一个特征值。同理可知摘要算法是不可逆的,我们不能通过特征值来推出唯一的原数据。此外我们还可以知道,特征值的长度越长,组数就越多,不同数据对应同一个特征值的概率就越小,安全性也就越高。

摘要算法中大家较为常见的一种是CRC算法,因为其算法简单且有一定的可靠性,被广泛应用于安全性要求不是很高的通信场景中。如果你对CRC较为熟悉,那么就可以将MD5理解为高级版的CRC算法,不过它们在算法原理上其实是完全不同的。CRC算法目前常用的特征值长度有2字节或4字节,而MD5的特征值长度为16字节,所以根据上文的描述我们可以知道MD5比CRC要安全得多。MD5的算法原理可以参考下面一篇文章,里面也有C++实现的代码,在这里不再多说。

https://blog.csdn.net/dickdick111/article/details/84928228

五、OpenSSL库

openssl是一个功能丰富且自包含的开源安全工具箱。它提供的主要功能有:SSL协议实现(包括SSLv2、SSLv3和TLSv1)、大量软算法(对称/非对称/摘要)、大数运算、非对称算法密钥生成、ASN.1编解码库、证书请求(PKCS10)编解码、数字证书编解码、CRL编解码、OCSP协议、数字证书验证、PKCS7标准实现和PKCS12个人数字证书格式实现等功能。
openssl采用C语言作为开发语言,这使得它具有优秀的跨平台性能。openssl支持Linux、UNIX、windows、Mac等平台。

简单来说,OpenSSL是一个开源的算法代码库,我们目前主流的安全算法它都支持,使用者可直接调用算法函数接口,也可根据实际情况自行修改源代码。OpenSSL被人们广泛引用,可以说涉及到信息安全的地方就会见到OpenSSL,随着物联网的快速发展,在汽车等传统行业也开始逐渐运用它。只要掌握了OpenSSL的使用,主流的安全算法就几乎都可以实现,我们上文中的三个算法也不例外。

有关OpenSSL的详细介绍和源码的分析可见下面的链接,可以说非常详细了,在后文中我们主要介绍一下如何来使用它。

https://www.cnblogs.com/testlife007/p/6738506.html

1、OpenSSL库的编译

①需要先安装Visual Studio,我这里用的是Visual Studio 2017,没有安装过的同学可从下面的链接下载安装,对于Visual Studio的安装及破解这里不多说,如果没有操作过可百度,网上教程很多。

链接:https://pan.baidu.com/s/1uO98Qc24X75DuDDlmv7MWg
提取码:2346

②下载并安装ActivePerl,下载地址如下,下载时需要用邮箱注册一个账号。

https://www.activestate.com/products/activeperl/downloads/

安装时遇到下面两步像下图中一样选择,然后一直安装完毕即可。
在这里插入图片描述
在这里插入图片描述
③下载OpenSSL源码,下载链接:

https://www.openssl.org/source/

下载后解压到D:\openssl,如图:
在这里插入图片描述
④运行Visual Studio 2017的命令行工具:
在这里插入图片描述
⑤先输入 d: 并回车进入D盘,如图,我因为把VS装在了D盘所以打开命令行工具默认就在D盘,如果你也是那么请忽略该步骤。
在这里插入图片描述
⑥执行命令:cd d:\openssl 进入到openssl的文件夹:
在这里插入图片描述
⑦我们先来编译32位的动态链接库,之后再说64位以及静态链接库如何操作。执行命令:
perl Configure VC-WIN32 no-asm
出现第二张图则该步成功。
在这里插入图片描述
在这里插入图片描述
⑧执行命令:ms\do_ms.bat
在这里插入图片描述
⑨执行命令:nmake -f ms\ntdll.mak
该步即开始进行编译,需要花一些时间,执行完成后会在openssl目录下的 out32dll 文件夹中生成静态库、动态库和.exe文件。
在这里插入图片描述
⑩执行命令:nmake -f ms\ntdll.mak test
对编译结果进行测试,出现第二张图则说明编译成功。
在这里插入图片描述
在这里插入图片描述
⑪执行命令:nmake -f ms\ntdll.mak install
在这里插入图片描述
执行完毕后会在当前盘符生成一个ssl文件夹,路径如下图红框所示,里面会有bin、include、lib三个文件夹,其中include是使用库时需要包含的头文件,bin里面有用来测试的.exe程序,lib里就是算法库。至此32位的动态链接库就编译完成了。
在这里插入图片描述
在这里插入图片描述
⑫测试:双击bin文件夹里的openssl.exe,执行命令:genrsa -out rsa_private_key.pem 2048
在这里插入图片描述
在bin文件夹中就会生成一个2048位的RSA算法的私钥,双击可用记事本打开,如图:
在这里插入图片描述

2、静态链接库及64位库的编译:

(1)若要编译64位动态链接库,则只需把上边第⑦步中的命令由
perl Configure VC-WIN32 no-asm 替换为 perl Configure VC-WIN64A no-asm ,其余步骤均相同。
(2)若要编译32位静态链接库,则需把上边⑨⑩⑪步骤中 的 ntdll.mak 替换为nt.mak 即可,即该三步的命令分别为:
⑨nmake -f ms\nt.mak
⑩nmake -f ms\nt.mak test
⑪nmake -f ms\nt.mak install

同理,若要编译64位静态链接库,需同时替换以上两处。我把我编译好的四种链接库也传上来了,需要的朋友可自取。

https://download.csdn.net/download/weixin_42967006/11462022

3、OpenSSL库的使用

下面以32位动态链接库为例,演示一下如何使用OpenSSL库中的算法函数来实现我们上文提到的RSA、AES和MD5算法,对比之下就可以知道使用OpenSSL如此便利。下文是从头开始使用我们刚刚编译好的库的步骤和代码,我也把我自己做好的VIsualStudio工程传了上来,如果不想自己做的同学也可自行下载,链接如下:

https://download.csdn.net/download/weixin_42967006/11464773

调用OpenSSL库的步骤:

①在Visual Studio上新建一个C++控制台工程,然后在【项目】→【属性】→【VC++目录】中的包含目录中添加上面步骤⑪生成的include文件夹路径,在引用目录和库目录中添加lib文件夹路径,如图所示:
在这里插入图片描述
②在建好的C++工程脚本中添加以下头文件,注意:“applink.c”这个文件在D:\openssl\ms路径下有,可以直接添加绝对路径,或像我这样把该文件复制到项目主脚本所在路径下再添加,如果不添加该文件运行时会报错,当初被这个问题困扰了很久,而且其他教程中通常没有提到这个问题。

#include "pch.h"
#include <iostream>
#include "openssl/ssl.h"
#include <openssl/aes.h>
#include <openssl/rsa.h>
#include <openssl/md5.h> 
#include "applink.c"
#pragma comment(lib,"libeay32.lib")

③算法实现

/************************RSA算法实现************************/
//生成公钥和私钥
int GenerateRSAKey(char * cpPrivateKeyPath, char * cpPublicKeyPath, int iLength)
{
    RSA *pRsaKey = RSA_generate_key(iLength, RSA_F4, NULL, NULL);
    FILE *fp_prikey = NULL;
    if ((fp_prikey = fopen(cpPrivateKeyPath, "w")) == NULL)
    {
        printf("Open private key file failed!");
    }
    PEM_write_RSAPrivateKey(fp_prikey, pRsaKey, NULL, NULL, 0, NULL, NULL);
    fclose(fp_prikey);
    FILE *fp_pubkey = NULL;
    if ((fp_pubkey = fopen(cpPublicKeyPath, "w")) == NULL)
    {
        printf("Open public key file failed!");
    }
    PEM_write_RSAPublicKey(fp_pubkey, pRsaKey);
    fclose(fp_pubkey);
    return 1;
}
//使用私钥加密
int RSA_PrivateKeyEncrypt(char *cInData,char *cOutData, char * cpPrivateKeyPath)
{
    static RSA *rsa_prikey = NULL;
    FILE *fp_prikey = NULL;

    int rsa_len_prikey = 0;
    if ((fp_prikey = fopen(cpPrivateKeyPath, "r")) == NULL)
    {
        printf("Open private key file failed!");
    }

    if ((rsa_prikey = PEM_read_RSAPrivateKey(fp_prikey, NULL, NULL, NULL)) == NULL) {
        printf("Read private key failed!");
    }

    rsa_len_prikey = RSA_size(rsa_prikey);
    //cOutData = (char*)malloc(rsa_len_prikey);
    if (RSA_private_encrypt(rsa_len_prikey, (unsigned char *)cInData, (unsigned char*)cOutData, rsa_prikey, RSA_NO_PADDING))
        return 1;//success
    else  return 0;//failed


    RSA_free(rsa_prikey);
    fclose(fp_prikey);

}
//使用公钥加密
int RSA_PublicKeyEncrypt(char *cInData, char *cOutData, char * cpPublicKeyPath)
{
    static RSA *rsa_pubkey = NULL;
    FILE *fp_pubkey = NULL;

    int rsa_len_pubkey = 0;
    if ((fp_pubkey = fopen(cpPublicKeyPath, "r")) == NULL)
    {
        printf("Open public key file failed!");
    }

    if ((rsa_pubkey = PEM_read_RSAPublicKey(fp_pubkey, NULL, NULL, NULL)) == NULL) {
        printf("Read Public key failed!");
    }

    rsa_len_pubkey = RSA_size(rsa_pubkey);
    //cOutData = (char*)malloc(rsa_len_pubkey);
    if (RSA_public_encrypt(rsa_len_pubkey, (unsigned char *)cInData, (unsigned char*)cOutData, rsa_pubkey, RSA_NO_PADDING))
        return 1;//success
    else  return 0;//failed


    RSA_free(rsa_pubkey);
    fclose(fp_pubkey);
}
//使用私钥解密
int RSA_PrivateKeyDecrypt(char *cInData, char *cOutData, char * cpPrivateKeyPath)
{
    static RSA *rsa_prikey = NULL;
    FILE *fp_prikey = NULL;

    int rsa_len_prikey = 0;
    if ((fp_prikey = fopen(cpPrivateKeyPath, "r")) == NULL)
    {
        printf("Open private key file failed!");
    }

    if ((rsa_prikey = PEM_read_RSAPrivateKey(fp_prikey, NULL, NULL, NULL)) == NULL) {
        printf("Read private key failed!");
    }

    rsa_len_prikey = RSA_size(rsa_prikey);
    //cOutData = (char*)malloc(rsa_len_prikey);
    if (RSA_private_decrypt(rsa_len_prikey, (unsigned char *)cInData, (unsigned char*)cOutData, rsa_prikey, RSA_NO_PADDING))
        return 1;//success
    else  return 0;//failed


    RSA_free(rsa_prikey);
    fclose(fp_prikey);
}
//使用公钥解密
int RSA_PublicKeyDecrypt(char *cInData, char *cOutData, char * cpPublicKeyPath)
{
    static RSA *rsa_pubkey = NULL;
    FILE *fp_pubkey = NULL;

    int rsa_len_pubkey = 0;

    if ((fp_pubkey = fopen(cpPublicKeyPath, "r")) == NULL)
    {
        printf("Open public key file failed!");
    }

    if ((rsa_pubkey = PEM_read_RSAPublicKey(fp_pubkey, NULL, NULL, NULL)) == NULL) {
        printf("Read Public key failed!");
    }

    rsa_len_pubkey = RSA_size(rsa_pubkey);
    //cOutData = (char*)malloc(rsa_len_pubkey);
    if (RSA_public_decrypt(rsa_len_pubkey, (unsigned char *)cInData, (unsigned char*)cOutData, rsa_pubkey, RSA_NO_PADDING))
        return 1;//success
    else  return 0;//failed


    RSA_free(rsa_pubkey);
    fclose(fp_pubkey);
}
void DisplayBigNumber(const char cName[],char cInData[], int iLength)
{
    int i = 0;
    printf("\n%s:", cName);
    if (iLength == 0x00)
    {
        for (i = 0; cInData[i] != 0x00; i++);
        iLength = i;
    }
    for (i = 0; i < iLength; i++)
    {
        if (i % 16 == 0) printf("\n");
        if (i % 4 == 0) printf("\t");
        printf("0x%02X ", (unsigned char)cInData[i]);
    }
    printf("\n\n");
}
/*******************************main 测试函数*********************************/
int main()
{
    char cInData[] = "0123456789ABCDE";
    DisplayBigNumber("In data", cInData, 16);

    /******************RSA-2048-test********************/
    
    char EncryptedData[256] = { 0 };
    char DecryptedData[16] = { 0 };
    char cPrivateKeyPath[] = "rsa_private_key.pem";
    char cPublicKeyPath[] = "rsa_public_key.pem";
   
    //首次运行需先打开下面的函数生成秘钥,后续不需要每次运行都重新生成
    //GenerateRSAKey(cPrivateKeyPath, cPublicKeyPath, 2048);

    printf("result=%X\n",RSA_PrivateKeyEncrypt(cInData, EncryptedData, cPrivateKeyPath));
    DisplayBigNumber("RSA private key encrypt result", EncryptedData, 256);
    RSA_PublicKeyDecrypt(EncryptedData, DecryptedData, cPublicKeyPath);
    DisplayBigNumber("RSA public key decrypt result", DecryptedData, 0);

    printf("result=%X\n", RSA_PublicKeyEncrypt(cInData, EncryptedData, cPublicKeyPath));
    DisplayBigNumber("RSA public key encrypt result", EncryptedData, 256);
    RSA_PrivateKeyDecrypt(EncryptedData, DecryptedData, cPrivateKeyPath);
    DisplayBigNumber("RSA private key decrypt result", DecryptedData, 16);

    /******************AES-128-test********************/

    unsigned char aes_key[16];
    char AESEncryptedData[16] = { 0 };
    char AESDecryptedData[16] = { 0 };
    AES_KEY aeskey;
    AES_set_encrypt_key(aes_key, 128, &aeskey);
    AES_encrypt((unsigned char*)cInData, (unsigned char*)AESEncryptedData, &aeskey);
    DisplayBigNumber("AES encrypt result", AESEncryptedData, 16);
    AES_set_decrypt_key(aes_key, 128, &aeskey);
    AES_decrypt((unsigned char*)AESEncryptedData, (unsigned char*)AESDecryptedData, &aeskey);
    DisplayBigNumber("AES encrypt result", AESDecryptedData, 16);

    /******************MD5-test********************/

    char MD5EncryptedData[17] = { 0 };
#if 0 //方法1
    MD5((unsigned char*)cInData, strlen(cInData), (unsigned char*)MD5EncryptedData);

#else //方法2
    MD5_CTX temp;  
    MD5_Init(&temp);
    MD5_Update(&temp, cInData, strlen(cInData));
    MD5_Final((unsigned char*)MD5EncryptedData, &temp);
#endif
    DisplayBigNumber("MD5 encrypt result", MD5EncryptedData, 0);

}

以上就是我对于这几个常用安全算法和OpenSSL库的理解,文中链接了多位大神的文章,也参考了一些别人的代码,在此表示感谢!我在学习这些内容的时候走了很多弯路,尤其是使用OpenSSL库的时候遇到了很多困难,踩过了很多坑,在此做一个整理和总结,以便日后使用,也希望对大家有所帮助,欢迎大家交流指正!

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老孟的孟不是很老的孟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值