基于RSA的激活验证机制

公司开发了一款软件,需要配合加密芯片进行激活验证,我就想着我也搞一个。

RSA是目前最有影响力和最常用的加密算法,它能够抵抗到目前为止已知的绝大多数密码攻击,已被ISO推荐为公钥数据加密标准。这是一种非对称密码算法,所谓非对称,就是指该算法需要一对公密钥,使用其中一个加密,则需要用另一个才能解密。密钥越长,它就越难破解。根据已经披露的文献,目前被破解的最长RSA密钥是768个二进制位。也就是说,长度超过768位的密钥,还无法破解(至少没人公开宣布)。因此可以认为,1024位的RSA密钥基本安全,2048位的密钥极其安全。

公私秘钥是成对使用的,公钥加密私钥解密,私钥加密公钥解密。既然是加密,那肯定是不希望别人知道我的消息,所以只有我才能解密,所以可得出公钥负责加密,私钥负责解密;同理,既然是签名,那肯定是不希望有人冒充我发消息,只有我才能发布这个签名,所以可得出私钥负责签名,公钥负责验证。一般使用公钥加密,私钥解密(本文使用此种方法)。

本文采用的是512位的RSA秘钥。

OpenSSL库

首先要准备OpenSSL的动态链接库,Linux下由软件仓库提供,Windows下可以自己编译,也可以使用别人编译好的。

1
2
3
openssl genrsa -out rsa_private_key.pem 512 #生成512位私钥
openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem #利用私钥生成RSA公钥文件
openssl pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -out pkcs8_rsa_private_key.epm -nocrypt #利用之前的私钥文件进行PKCS#8编码,生成编码后文件

如果是在openssl命令中,就不需要openssl前缀

cmd

Qt

创建一个空白Widget工程。

将OpenSSL安装后的头文件和库文件复制到工程目录下,然后添加

1
2
3
INCLUDEPATH += $$PWD/include
LIBS += -L$$PWD/lib
LIBS += -lcrypto-1_1-x64 -lssl-1_1-x64

测试代码

头文件

1
2
3
4
5
6
7
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/bn.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

公钥加密函数

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
QString MainWindow::rsa_pub_encrypt_base64(const QString &strClearData)
{
//公钥 长度为512 (使用自己生成的公秘钥)
std::string public_key =ui->textPublicKey->toPlainText().toStdString();
if(public_key.empty()){
qDebug()<<"Must have a public_key for encryption.";
return "";
}

//将字符串键加载到bio对象
BIO* pKeyBio = BIO_new_mem_buf(public_key.data(), public_key.size());
if (pKeyBio == NULL){
qDebug()<<"Public encryption BIO is empty.";
return "";
}
RSA* pRsa = RSA_new();
pRsa = PEM_read_bio_RSA_PUBKEY(pKeyBio, &pRsa, NULL, NULL);
if ( pRsa == NULL ){
qDebug()<<"Public encryption read bio rsa error.";
BIO_free_all(pKeyBio);
return "";
}
int nLen = RSA_size(pRsa);
char* pEncryptBuf = new char[nLen];
memset(pEncryptBuf, 0, nLen);
QByteArray clearDataArry = strClearData.toUtf8();
int nClearDataLen = clearDataArry.length();
uchar* pClearData = (uchar*)clearDataArry.data();
int nSize = RSA_public_encrypt(nClearDataLen,
pClearData,
(uchar*)pEncryptBuf,
pRsa,
RSA_PKCS1_PADDING);

QString strEncryptData = "";
if ( nSize >= 0 ){
QByteArray arry(pEncryptBuf, nSize);
strEncryptData = arry.toBase64();
}
// 释放内存
delete[] pEncryptBuf;
BIO_free_all(pKeyBio);
RSA_free(pRsa);
return strEncryptData;
}

私钥解密

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
QString MainWindow::rsa_pri_decrypt_base64(const QString &strDecryptData)
{
//私钥解密
std::string private_key = ui->textPrivateKey->toPlainText().toStdString();
if(private_key.empty()){
qDebug()<<"Must have private_key for decryption.";
return "";
}

//将字符串键加载到bio对象
BIO* pKeyBio = BIO_new_mem_buf(private_key.data(), private_key.size());
if (pKeyBio == NULL){
qDebug()<<"Private decryption BIO is empty.";
return "";
}

RSA* pRsa = RSA_new();
pRsa = PEM_read_bio_RSAPrivateKey(pKeyBio, &pRsa, NULL, NULL);
if ( pRsa == NULL ){
qDebug()<<"Private decryption read bio rsa error.";
BIO_free_all(pKeyBio);
return "";
}

int nLen = RSA_size(pRsa);
char* pClearBuf = new char[nLen];
memset(pClearBuf, 0, nLen);
//解密
QByteArray decryptDataArry = strDecryptData.toUtf8();
decryptDataArry = QByteArray::fromBase64(decryptDataArry);
int nDecryptDataLen = decryptDataArry.length();
uchar* pDecryptData = (uchar*)decryptDataArry.data();
int nSize = RSA_private_decrypt(nDecryptDataLen,
pDecryptData,
(uchar*)pClearBuf,
pRsa,
RSA_PKCS1_PADDING);
QString strClearData = "";
if ( nSize >= 0 ){
strClearData = QByteArray(pClearBuf, nSize);
}

// 释放内存
delete[] pClearBuf;
BIO_free_all(pKeyBio);
RSA_free(pRsa);
return strClearData;
}

测试代码

1
2
3
4
QString encrypt_str = rsa_pub_encrypt_base64("123abc");
qDebug()<<"加密数据:"<<encrypt_str;
QString decrypt_str = rsa_pri_decrypt_base64(encrypt_str);
qDebug()<<"解密数据:"<<decrypt_str;

输出为:

1
2
加密数据: "SDrjl8qmW//wB55Tb8uoJGUhdb+cvhHWpXHslmVh35QkzZK5inA3JJLWg/UOb8HG97bMNFzA5M6rko66aUBfew=="
解密数据: "123abc"

注册机制

经过测试,可以加密,可以解密。

我设计一个界面。

  • 不同版本对应不同的公私秘钥。
  • 绑定MAC地址
  • 绑定CPU信息
  • 绑定硬盘信息
  • 授权到期时间

界面如下

ui

以MAC和到期时间作为激活信息,然后把激活信息进行加密,得到激活秘钥。把私钥(其实公钥好一点)写入软件中,每次启动时验证信息。

还可以添加额外字符串,如果此字符串所在的密钥泄露,就直接跳过。

效果为:

result

操作流程就是将私钥直接写入到软件中,软件用户在使用时提供MAC/有效期等等向开发者申请注册码,软件用户将注册码填入激活窗口,软件使用私钥解密信息,判断MAC是否与本机相同,判断是否在有效期内等等,然后软件正常启动。

可以看到不论加密方式有多安全,注册流程有多复杂,只要有比较是否有效这一步,就是破解的关键,主要是是否值得破解。

完整Qt工程在QtApps下的Encrypt中。


基于RSA的激活验证机制
https://feater.top/qt/license-tool-with-openssl-rsa/
作者
JackeyLea
发布于
2021年11月18日
许可协议