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

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

一、RSA简介

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

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

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

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

二、命令行操作

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

1、生成秘钥对

生成私钥文件private.pem:

openssl genrsa -out private.pem

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

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

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

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

2、加解密文件

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

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

加密:

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

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

解密:

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

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

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

参考: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

验签:

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

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

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

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

验证成功,如下:

在这里插入图片描述

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

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

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

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

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

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

参考: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);
参数名称含义
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);
参数名称含义
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);
}

(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;
}

(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);
}

(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();
}

执行结果:

在这里插入图片描述

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

本文涉及工程代码地址:https://gitee.com/bailiyang/cdemo/tree/master/Qt/49OpenSSL/OpenSSL



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

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

在这里插入图片描述

  • 16
    点赞
  • 87
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
首先,为了使用 RSA 算法进行大文件签名和验证,你需要了解 OpenSSL 库和 RSA 算法的基本知识。OpenSSL 是一个开源的加密库,支持多种加密算法,包括 RSA 算法RSA 算法是一种非对称加密算法,它使用公钥和私钥来加密和解密数据以及进行签名和验证。 下面是使用 OpenSSL 库进行大文件签名和验证的步骤: 1. 生成 RSA 密钥对 使用 OpenSSL 库生成 RSA 密钥对,可以使用以下命令: ```bash openssl genrsa -out private_key.pem 2048 openssl rsa -in private_key.pem -pubout -out public_key.pem ``` 这将生成一个 2048 位的 RSA 私钥和一个对应的公钥,保存在 private_key.pem 和 public_key.pem 文件中。 2. 加载 RSA 密钥 使用 OpenSSL 库加载 RSA 密钥,可以使用以下代码: ```c++ RSA* rsa = nullptr; FILE* fp = fopen("private_key.pem", "rb"); if (fp) { rsa = PEM_read_RSAPrivateKey(fp, nullptr, nullptr, nullptr); fclose(fp); } ``` 这将加载 private_key.pem 文件中的 RSA 私钥。 3. 对文件进行签名 使用 OpenSSL 库对文件进行签名,可以使用以下代码: ```c++ bool sign_file(const char* filename, const char* signature_filename, RSA* rsa) { bool success = false; FILE* fp = fopen(filename, "rb"); if (fp) { // 计算文件的 SHA256 摘要 SHA256_CTX sha256_ctx; SHA256_Init(&sha256_ctx); unsigned char buffer[4096]; size_t len; while ((len = fread(buffer, 1, sizeof(buffer), fp)) > 0) { SHA256_Update(&sha256_ctx, buffer, len); } unsigned char hash[SHA256_DIGEST_LENGTH]; SHA256_Final(hash, &sha256_ctx); fclose(fp); // 使用 RSA 私钥对摘要进行签名 unsigned char signature[RSA_size(rsa)]; unsigned int signature_length; if (RSA_sign(NID_sha256, hash, sizeof(hash), signature, &signature_length, rsa)) { fp = fopen(signature_filename, "wb"); if (fp) { fwrite(signature, 1, signature_length, fp); fclose(fp); success = true; } } } return success; } ``` 这将对指定的文件进行 SHA256 摘要计算,并使用 RSA 私钥对摘要进行签名签名结果将保存在指定的文件中。 4. 对文件进行验证 使用 OpenSSL 库对文件进行验证,可以使用以下代码: ```c++ bool verify_file(const char* filename, const char* signature_filename, RSA* rsa) { bool success = false; FILE* fp = fopen(filename, "rb"); if (fp) { // 计算文件的 SHA256 摘要 SHA256_CTX sha256_ctx; SHA256_Init(&sha256_ctx); unsigned char buffer[4096]; size_t len; while ((len = fread(buffer, 1, sizeof(buffer), fp)) > 0) { SHA256_Update(&sha256_ctx, buffer, len); } unsigned char hash[SHA256_DIGEST_LENGTH]; SHA256_Final(hash, &sha256_ctx); fclose(fp); // 加载签名 unsigned char signature[RSA_size(rsa)]; unsigned int signature_length; fp = fopen(signature_filename, "rb"); if (fp) { signature_length = fread(signature, 1, sizeof(signature), fp); fclose(fp); // 使用 RSA 公钥对签名进行验证 if (RSA_verify(NID_sha256, hash, sizeof(hash), signature, signature_length, rsa)) { success = true; } } } return success; } ``` 这将对指定的文件进行 SHA256 摘要计算,并加载签名文件,使用 RSA 公钥对签名进行验证。 注意,在实际应用中,需要对签名和验证的结果进行适当的处理和错误处理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

百里杨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值