阅读 2412

一种基于RSA+AES算法实现的软件授权License设计思路(附源码)

一、前言

本篇文章通过介绍基础加密算法知识结合软件授权激活需求,提出了一种基于RSA+AES加密算法的离线软件授权激活码的设计思路及示例代码。即使您没有软件授权设计方面的需求,也可以通过本文加深对RSA+AES加密算法的理解。

二、加密算法基础

软件授权机制的基础就是加密算法。加密算法可以简单分为对称加密算法(加解密均采用同一密钥)和非对称加密算法(采用公钥加密,私钥解密或私钥签名,公钥验签)。下面先来简单了解一下AES、RSA加密算法的基础概念,方便理解整个软件授权License设计思路。

2.1、AES加密算法

AES(Advanced Encryption Standard)加密算法,在密码学中又称Rijndael加密算法,是一种区块加密标准算法。AES加密算法由美国国家标准与技术研究(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。现在AES加密算法已然成为对称密钥加密中最流行的算法之一。

2.2、RSA加密算法及其应用模式

RSA(Ron Rivest、Adi Shamir、Leonard Adleman,3个提出者的姓氏首字母)加密算法是一种非对称加密算法(公钥加密算法)。RSA加密算法有一对密钥,一个是公开密钥,另一个是私有密钥,公钥用作加密,私钥则用作解密。使用公钥把加密后所得的密文,只能用相对应的私钥才能解密并得到原本的明文。

RSA的原理基于一个简单的数论知识:对两个互质的大数乘积进行因式分解很困难。RSA加密算法有两种应用模式,分别为加解密模式、签名验签模式。

2.2.1、RSA加解密模式

RSA加解密模式是指采用公钥加密,私钥解密的模式。其应用为:需要加密明文数据并解密的场景。发送方将公钥加密后的密文发送给接收方,接收方用私钥解密密文后即可得到明文。

2.2.2、RSA签名验签模式

RSA签名验签模式则是网文中常见的所谓**“私钥加密,公钥解密”的逻辑。它其实是指采用私钥签名,公钥验签的模式,其应用为:需要给一条消息署名的场景。发送方将明文+私钥签名后的明文摘要消息发送给接收方,接收方用公钥解密出来私钥签名后的明文摘要**,并计算出发送方传输过来的明文的摘要进行比对,一致则验签成功。常用数字签名算法有:MD5withRSA、SHA1withRSA、SHA256withRSA。

一般来说,对称算法比非对称算法的加解密速度要快得多,并且RSA加密算法加密的长度有效,单次加密最大不大于密钥长度。因此实际使用中一般会结合RSA+AES两者使用,这也是为什么要用RSA算法签名摘要或者加密AES密钥的原因,提高效率的同时规避加密长度限制。

比如在HTTPS通信中的TLS(Transport Layer Security,其前身是安全套接层Secure Sockets Layer,缩写:SSL)安全协议,握手时采用RSA算法加密随机数与对话密钥,握手成功后就采用AES算法进行对称加密通信。

三、软件授权逻辑设计

前面通过对RSA+AES加密算法的基础知识的介绍,相信大家已经对加密有了感性认识,接下来我们以Android平台为例,设计一套离线软件授权License生成与校验方案。

3.1、软件授权信息

首先我们来看激活码中包含一些基本的授权信息,如绑定的AppId、License签发时间、License有效期起止、客户信息等等,根据实际需求而定。这里我们采用json数据格式来表示授权信息。

示例授权信息:

{
    "appId": "cn.fdm.offlicensedemo",
    "issuedTime": 1595951714,
    "notBefore": 1538671712,
    "notAfter": 1640966400,
    "customerInfo": "亚马逊公司"
}
复制代码

3.2、激活码生成

接着,我们理一下思路,授权信息如何安全护送到App上,并保证不被中间人篡改呢?还记得刚才我们学习的RSA签名模式吧?什么?防篡改?这就是RSA签名算法的使用场景啊!

话不多说,上(伪)代码:

License = AesKey16 + AesEnc(data).length + AesEnc(data) + RsaSign(AesEnc(data)); 
复制代码

License:就是最终生成的激活码字符串;

AesKey16:随机生成的16位AES密钥,选用固定长度密钥,因此不用拼接AesKey长度;

AesEnc(data).length:加密后的授权信息长度,由于授权信息会变动,因此需要拼接该密文长度,方便截取;

AesEnc(data):AES加密后的授权信息;

RsaSign(AesEnc(data)):RSA私钥签名后的加密授权信息。

生成逻辑:

首先生成一个随机的AES密钥(16位即可),用来加密授权信息,完成明文信息的脱敏。然后将整段加密后的授权信息使用私钥进行签名,拼接生成的字符串就是最终的激活码了。

看到这里,也许有人会说:万一你这激活码生成规则被人知道了,怎么防止别人破解伪造啊?

答案是,即使有人了解了生成规则,不知道私钥,也无法通过篡改授权信息实现破解伪造激活码。因为我们的核心是基于非对称加密算法的签名逻辑。通过私钥对密文授权信息签名,而一旦授权信息改变,密文也随之改变,在验证环节使用公钥进行验签时就无法通过。因此,只要私钥始终未曾泄露,这个激活逻辑就是相对安全的。

激活码生成示例:

public static String getLic(licenseBean bean) {
    String lic = "";
    try {
        String aesKey = MyUtil.getRandomString(16);
        String encData = AesUtil.encrypt(aesKey, bean.toJson());
        String encDataLength = Integer.toHexString(encData.length());
        String sign = RSAUtil.sign(RSA_PRI_KEY, encData);
        lic = aesKey + encDataLength + encData + sign;
        Log.d("bruce", lic);
        return lic;
    } catch (Exception e) {
        e.printStackTrace();
        return lic;
    }
}
复制代码

一个激活码样例:

11Tf2wg9JjXeuvKTd8m4QwVfLJczRej06EAVut7coeJf3iSBDAkEl3E6faNYnG3SwiRbNBtSTmW+oRJ0kLVZf8ZIWeDQecyUqfBMiCwkYkPK4PAsmCK+kWd/aAZ+90t9QSZTq/qe4VQzkFuAk9a7D+vcF4e3BkVUovZuzB9XvmdIrNw8menjFLdWE+S0YD+VIs7AS8wUBBdA9+aH9/mZ8Oc53klslYT0mGGtTK0g==YRjmOKNaA5hA4hTAmHAK8LvRWjJqzKbGpxALd3V9dcX6Ln1ImKw0NhAVflAA5asyD4eaoafExlclCU+ayd2CyHpFmlIKTd4Uzh20y5Qke03WrGMWgSEaCgz+mZrihX0nwoyg5GXuOI0/dqOplX7mf+mSd8wgk9d2paOBvm5+ea4=

3.3、激活码校验

既然我们已经生成了激活码,那在校验时无非就是读取License,按照我们设计激活码时拼接的规则进行截取、校验。

激活步骤:

  1. 从程序中读取License激活码;
  2. 根据生成规则分割、截取出各段信息;
  3. 使用RSA公钥验签算法对AesEnc(data)、**RsaSign(AesEnc(data))**进行验证,通过则说明信息可信;
  4. 接着使用激活码中截取的AesKey16对**AesEnc(data)**解密,得到可信的明文授权信息;
  5. 最后使用可信的明文授权信息与当前环境进行授权比对,如AppId、授权时间等,即可完成激活流程。

示例代码:

public static boolean checkLic(String lic) {
    if (TextUtils.isEmpty(lic)) {
        return false;
    }
    try {
        String aesKey = lic.substring(0, 16);
        int encDataLength = Integer.parseInt(lic.substring(16, 18), 16);
        String encData = lic.substring(18, 18 + encDataLength);
        String sign = lic.substring(18 + encDataLength);
        if (!RSAUtil.verifySign(RSA_PUB_KEY, encData, sign)) {
            return false;
        }
        String data = AesUtil.decrypt(aesKey, encData);
        String appId = JsonUtil.getString(data, "appId");
        long notBefore = JsonUtil.getLong(data, "notBefore");
        long notAfter = JsonUtil.getLong(data, "notAfter");
        String customerInfo = JsonUtil.getString(data, "customerInfo");
        if (!MyUtil.getAppId(App.getContext()).equals(appId)) {
            return false;
        }
        long time = System.currentTimeMillis();
        if (time < notBefore || time > notAfter) {
            return false;
        }
        return true;
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}
复制代码

至此,一个简单的软件激活码授权License就设计完成了,demo示例代码详见文末链接。

四、软件授权的增强思路

4.1、绑定硬件序列号逻辑

软件授权中,常见需要先在设备中运行程序,提取相关信息后,再生成激活码。其实提取的信息就是该设备的唯一信息摘要,如Mac地址、序列号等与硬件唯一关联的信息。

因此,如果我们需要防止软件授权被复制、转移时,就需要针对硬件信息进行加密、授权。

4.2、校验算法置于Native层

本文采用了Android端代码示例,展示了一个授权码的生成与校验逻辑。但由于Java代码比较容易被反编译破解,因此实际采用的授权激活方案建议将校验逻辑及关键业务逻辑置于Native层。通过Native层的C/C++代码直接读取授权文件,进行截取、校验,可以增加破解难度。

五、结语

本文主要介绍了RSA+AES加密算法基础知识,结合软件授权需求设计出了一种简单的离线软件授权License方案,为软件授权激活设计提供了一种思路。在实际应用中还需要兼顾更多细节,通过一定程度的完善演化来增加破解难度,在攻防的环节中守住阵地。

可能有人会说:软件授权还是可以破解的,强如微软不是也没能逃过软件破解?确实是,但这并不意味着软件授权是无用功。软件授权机制的一个价值在于使用破解版带来的焦虑,特别是企业级需要保证稳定的软件授权。

最后,附上完整Aandroid端示例代码:OffLicenseDemo。如果本篇文档对您的开发有所帮助或启发,点赞/关注/分享三连就是对作者持续创作最好的激励,感谢支持!

参考文档:

AES加密算法

RSA算法原理(一)

RSA算法原理(二)

维基百科:公开密钥加密

维基百科:RSA加密算法

版权声明:

本文首发于我的专栏 AndDev安卓开发 欢迎大家关注点赞。

文章分类
代码人生
文章标签