首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

实战篇-OpenSSL之RSA算法-加密与数字签名

  • 23-09-19 13:42
  • 2911
  • 12267
blog.csdn.net

本文属于《OpenSSL加密算法库使用系列教程》之一,欢迎查看其它文章。

实战篇-OpenSSL之RSA算法-加密与数字签名

  • 一、RSA简介
  • 二、命令行操作
    • 1、生成秘钥对
    • 2、加解密文件
    • 3、数字签名
  • 三、编程实现
    • 1、函数说明
      • (1)加密函数RSA_public_encrypt
      • (2)解密函数RSA_private_decrypt
    • 2、编程实现
      • (1)生成密钥对
      • (2)数据加解密
      • (3)数字签名与验签
      • (4)测试代码

一、RSA简介

RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的。

RSA属于非对称加密,与对称加密算法不同,RSA是由一对密钥来进行加解密的过程,分别称为公钥和私钥。

具体的加密原理,就不进行介绍了,本文主要从使用角度,进行说明。

以下命令行和编程实现,均基于OpenSSL开源库。在命令行中,我们可以使用命令实现对文件加解密,以验证我们的编程实现,是否正确。

二、命令行操作

接下来,通过命令行方式来演示,如何使用RSA算法进行加解密,以及数字签名。

1、生成秘钥对

生成私钥文件private.pem:

openssl genrsa -out private.pem
  • 1

这里-out指定生成文件的。需要注意的是这个文件包含了公钥和密钥两部分,也就是说这个文件即可用来加密也可以用来解密。后面的1024是生成密钥的长度。

根据私钥private.pem提取出公钥public.pem:

openssl rsa -in private.pem -out public.pem -pubout
  • 1

-in指定输入文件,-out指定提取生成公钥的文件名。至此,我们手上就有了一个公钥,一个私钥(包含公钥)。现在可以将用公钥来加密文件了。

2、加解密文件

大家都知道公钥加密,私钥解密。

我们在目录中创建一个hello的文本文件,然后利用此前生成的公钥加密文件。

加密:

openssl rsautl -encrypt -in hello.txt -inkey public.pem -pubin -out hello.en
  • 1

-in指定要加密的文件,-inkey指定密钥,-pubin表明是用纯公钥文件加密,-out为加密后的文件。

解密:

openssl rsautl -decrypt -in hello.en -inkey private.pem -out hello.de
  • 1

-in指定被加密的文件,-inkey指定私钥文件,-out为解密后的文件。

至此,一次加密解密的过程告终。

参考:http://iyenn.com/index/link?url=https://www.cnblogs.com/aLittleBitCool/archive/2011/09/22/2185418.html

3、数字签名

数字签名分为摘要和加密两部分,但是在openssl提供的指令中,并没有区分两者。需要注意的是,签名是指对明文的摘要进行加密,得到的密文就称为签名,而非对明文数据进行加密。

大家都知道私钥签名,公钥验签。

签名:

使用dgst指令指定sha1算法,对hello.txt进行签名,生成签名文件sign.txt

openssl dgst -sha1 -sign private.pem -out sign.txt hello.txt
  • 1

验签:

先根据私钥private.pem提取出公钥public.pem

openssl rsa -in private.pem -out public.pem -pubout
  • 1

使用RSA公钥验证签名(verify参数)

openssl dgst -verify public.pem -sha1 -signature sign.txt hello.txt
  • 1

验证成功,如下:

在这里插入图片描述

另外,大家都知道可以从私钥文件中提取公钥文件,所以验签时,也可以直接用私钥文件来验签。如下,使用RSA私钥验证签名(prverify参数),验证成功。

openssl dgst -prverify private.pem -sha1 -signature sign.txt hello.txt
  • 1

疑问:为什么可以从私钥导出公钥,而不能从公钥导出私钥?

从公钥导出私钥,实际上等同于RSA被破解,理论上,RSA可以被破解,但是随着key越长,其破解难度越大。

目前被破解的最长RSA密钥就是768位,因此就常见的RSA 1024位及以上,基本上是不能被破解的。也就是说公钥导出私钥是不成立的。

所以,OpenSSL中可以由私钥导出公钥,猜测应该是私钥的容器往往同时包含私钥与公钥(公钥是让所有人都会知道,那么拥有私钥的人没有道理不留存一份公钥),而公钥的容器仅包含公钥。

参考:http://iyenn.com/index/link?url=https://www.cnblogs.com/yanhuang/p/9646578.html

三、编程实现

接下来,通过编程方式来演示,如何使用RSA算法进行加解密,以及数字签名。

这里重点讲一下,RSA加密/解密函数的使用注意点,其他的生成密钥/签名之类的,无非是看看函数帮助就ok。

1、函数说明

(1)加密函数RSA_public_encrypt

int RSA_public_encrypt(int flen, const unsigned char *from,
                       unsigned char *to, RSA *rsa, int padding);
  • 1
  • 2
参数名称含义
flen明文数据长度字节数,若padding参数使用RSA_PKCS1_PADDING方式,则该值最大为所使用密钥的位数 / 8 - 11
from明文数据
to存放生成的密文数据,该空间大小应该为秘钥位数 / 8,保证可以存放的下
rsa公钥
padding填充方式

RSA_public_encrypt一次性只能加密(密钥的位数 / 8 = N)字节的数据,且加密前后数据长度相等。

比如对于1024bit的密钥,可一次性加密128字节,由于采用RSA_PKCS1_PADDING填充,填充需要占用11字节,故真正的明文数据,最多只占128-11=117字节。当实际明文数据过长时,应采用分段加密,并将加密结果拼到一起即可。详细参考:《RSA密钥长度、明文长度和密文长度》。

(2)解密函数RSA_private_decrypt

int RSA_private_decrypt(int flen, const unsigned char *from,
                        unsigned char *to, RSA *rsa, int padding);
  • 1
  • 2
参数名称含义
flen密文数据长度,一般固定为秘钥位数 / 8
from密文数据
to存放解密后的明文数据,该空间大小应该为秘钥位数 / 8,保证可以存放的下
rsa私钥
padding填充方式

与RSA_public_encrypt类似,RSA_private_decrypt也是一次性只能解密(密钥的位数 / 8 = N)字节的数据,且解密前后数据长度相等。

返回值:以RSA 1024为例,表示将一段128字节的密文,进行解密,并解除填充,得到的实际明文数据,该数据的长度,作为函数返回值。该返回值,可以用于从to参数指向的内存中,提取实际长度的明文数据。

2、编程实现

(1)生成密钥对

封装了2个函数,一个生成秘钥对文件,一个生成秘钥对的内存数据。

/**
 * @brief RSAC::generateKeyPair
 * 生成密钥对,并分别保存为文件
 * @param priKeyFile 私钥文件名
 * @param pubKeyFile 公钥文件名
 * @param bits 秘钥长度,一般建议1024及以上
 */
void RSAC::generateKeyPair(const QString &priKeyFile, const QString &pubKeyFile, int bits)
{
    // 生成公钥
    RSA* rsa = RSA_generate_key(bits, RSA_F4, nullptr, nullptr);
    BIO *bp = BIO_new(BIO_s_file());
    BIO_write_filename(bp, (void*)pubKeyFile.toStdString().c_str());
    PEM_write_bio_RSAPublicKey(bp, rsa);
    BIO_free_all(bp);

    // 生成私钥
    bp = BIO_new(BIO_s_file());
    BIO_write_filename(bp, (void*)priKeyFile.toStdString().c_str());
    PEM_write_bio_RSAPrivateKey(bp, rsa, nullptr, nullptr, 0, nullptr, nullptr);
    CRYPTO_cleanup_all_ex_data();
    BIO_free_all(bp);
    RSA_free(rsa);
}

/**
 * @brief RSAC::generateKeyPair
 * 生成密钥对数据
 * @param privateKey 私钥数据
 * @param publicKey 公钥数据
 * @param bits 秘钥长度,一般建议1024及以上
 */
void RSAC::generateKeyPair(QByteArray &privateKey, QByteArray &pubKey, int bits)
{
    // 生成密钥对
    RSA *keyPair = RSA_generate_key(bits, RSA_F4, nullptr, nullptr);

    BIO *pri = BIO_new(BIO_s_mem());
    BIO *pub = BIO_new(BIO_s_mem());

    PEM_write_bio_RSAPrivateKey(pri, keyPair, nullptr, nullptr, 0, nullptr, nullptr);
    PEM_write_bio_RSA_PUBKEY(pub, keyPair);

    // 获取长度
    int pri_len = BIO_pending(pri);
    int pub_len = BIO_pending(pub);

    privateKey.resize(pri_len);
    pubKey.resize(pub_len);

    BIO_read(pri, privateKey.data(), pri_len);
    BIO_read(pub, pubKey.data(), pub_len);

    // 内存释放
    RSA_free(keyPair);
    BIO_free_all(pub);
    BIO_free_all(pri);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

(2)数据加解密

包含一对加密、解密函数。该函数可执行对任意长度明文加密,并进行解密。

/**
 * @brief RSAC::encrypt
 * RSA加密函数,使用公钥对输入数据,进行加密
 * @param in 输入数据(明文)
 * @param out 输出数据(密文)
 * @param pubKey 公钥
 * @return 执行结果
 */
bool RSAC::encrypt(const QByteArray &in, QByteArray &out, const QByteArray& pubKey)
{
    // 公钥数据转RSA
    RSA* rsa = publicKeyToRSA(pubKey);
    if (rsa == nullptr)
    {
        return false;
    }

    // 对任意长度数据进行加密,超长时,进行分段加密
    int keySize = RSA_size(rsa);
    int dataLen = in.size();
    const unsigned char *from = (const unsigned char *)in.data();
    QByteArray to(keySize, 0);
    int readLen = 0;
    do
    {
        int select = (keySize - 11) > dataLen ? dataLen : (keySize - 11);
        RSA_public_encrypt(select, (from + readLen), (unsigned char *)to.data(), rsa, RSA_PKCS1_PADDING);
        dataLen -= select;
        readLen += select;
        out.append(to);
    }while (dataLen > 0);
    RSA_free(rsa);
    return true;
}

/**
 * @brief RSAC::private_decrypt
 * RSA解密函数,使用私钥对输入数据,进行解密
 * @param in 输入数据(密文)
 * @param out 输出数据(解密后的内容)
 * @param priKey 私钥
 * @return 执行结果
 */
bool RSAC::decrypt(const QByteArray &in, QByteArray &out, const QByteArray& priKey)
{
    // 私钥数据转RSA
    RSA* rsa = privateKeyToRSA(priKey);
    if (rsa == nullptr)
    {
        return false;
    }

    // 对任意长度数据进行解密,超长时,进行分段解密
    int keySize = RSA_size(rsa);
    int dataLen = in.size();
    const unsigned char *from = (const unsigned char *)in.data();
    QByteArray to(keySize, 0);
    int readLen = 0;
    do
    {
        int size = RSA_private_decrypt(keySize, (from + readLen), (unsigned char *)to.data(), rsa, RSA_PKCS1_PADDING);
        dataLen -= keySize;
        readLen += keySize;
        out.append(to.data(), size);
    }while (dataLen > 0);
    RSA_free(rsa);
    return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

(3)数字签名与验签

包含一对签名、验签函数。

/**
 * @brief RSAC::sign
 * 使用私钥对摘要数据进行签名
 * @param digest 摘要数据
 * @param sign 签名后的数据
 * @param priKey 私钥
 * @return 执行结果
 */
bool RSAC::sign(const QByteArray &digest, QByteArray &sign, const QByteArray& priKey)
{
    // 私钥数据转RSA
    RSA* rsa = privateKeyToRSA(priKey);
    if (rsa == nullptr)
    {
        return false;
    }

    // 对digest进行签名
    unsigned int siglen = 0;
    QByteArray temp(RSA_size(rsa), 0);
    RSA_sign(NID_sha1, (const unsigned char*)digest.data(), digest.size(),
             (unsigned char*)temp.data(), &siglen, rsa);
    sign.clear();
    sign.append(temp.data(), siglen);
    RSA_free(rsa);
    return true;
}

/**
 * @brief RSAC::verify
 * 使用公钥对摘要数据进行验签
 * @param digest 摘要数据
 * @param sign 签名后的数据
 * @param pubKey 公钥
 * @return 执行结果
 */
bool RSAC::verify(const QByteArray &digest, const QByteArray &sign, const QByteArray& pubKey)
{
    // 公钥数据转RSA
    RSA* rsa = publicKeyToRSA(pubKey);
    if (rsa == nullptr)
    {
        return false;
    }

    // 对digest、sign进行验签
    int ret = RSA_verify(NID_sha1, (const unsigned char*)digest.data(), digest.size(),
               (const unsigned char *)sign.data(), sign.size(), rsa);
    RSA_free(rsa);
    return (ret == 1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

(4)测试代码

void createTestData(QByteArray& data, int size)
{
    data.resize(size);
    for (int i = 0; i < size; i++)
    {
        data[i] = i % 128;
    }
}

void testRSA(const QByteArray& data)
{
    // RSA密钥对生成验证
    RSAC rsac;
    rsac.generateKeyPair("./prikey.pem", "./pubkey.pem", 1024);

    // RSA加解密验证
    QByteArray priKey, pubKey;
    QByteArray plainText = data;
    QByteArray encryptText;
    QByteArray decryptText;
    rsac.generateKeyPair(priKey, pubKey, 1024);
    rsac.encrypt(plainText, encryptText, pubKey);       // 加密
    rsac.decrypt(encryptText, decryptText, priKey);     // 解密
    qDebug() << "RSA encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();

    // RSA签名、验签
    HASH hash(HASH::SHA256); // 生成文件SHA256摘要
    hash.addData("life's a struggle");
    QByteArray digest = hash.result();

    QByteArray sign;
    rsac.sign(digest, sign, priKey);                // 签名
    bool ret = rsac.verify(digest, sign, pubKey);   // 验签
    qDebug() << "RSA sign verify" << (ret ? "succeeded" : "failed");
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 产生1MB+3B的测试数据,为了使该测试数据长度,不为8或16的整数倍
    QByteArray data;
    createTestData(data, 1*1024*1024+3);

    // 测试RSA
    testRSA(data);

    return a.exec();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

执行结果:

在这里插入图片描述

对生成的(1x1024x1024+3)字节的测试数据,进行了加解密验证,SHA256摘要签名验签,均测试成功。

本文涉及工程代码地址:http://iyenn.com/index/link?url=https://gitee.com/bailiyang/cdemo/tree/master/Qt/49OpenSSL/OpenSSL



若对你有帮助,欢迎点赞、收藏、评论,你的支持就是我的最大动力!!!

同时,阿超为大家准备了丰富的学习资料,欢迎关注公众号“超哥学编程”,即可领取。

在这里插入图片描述

文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览50757 人正在系统学习中
注:本文转载自blog.csdn.net的百里杨的文章"https://blog.csdn.net/zyhse/article/details/113844114"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top