License
关于License的概念、用途和好坏处,网上一搜一大堆的,但我讲的这个License,并非是自娱自乐,而是教大家如何做一个符合商用的license。
如何商用?
可以定义一个或者多个唯一的属性,用来标识License使用方,说白了就是标识谁使用了你的平台、软件、模块或者库,这个标识可以是machine也可以是people,一经生成,便不能修改和删除。比如计算机Mac地址、主板序列号和CPU序列号,或者是软件安装序列号、用户UUID,根据这些number,可以确保授权对象唯一,不可复制。
有了上面的保护之后,那就可以赚钱了。比如你可以区分业务类型,其实就是使用范围,不同业务不同套餐费用,或者是实例创建数量和API调用次数,这些都可以是计费标准,还有最常见的就是时间限制,超过有效时间期限,则无法继续使用。
君子爱财取之有道。是时候切入正题了,在写代码之前,需要制作一对秘钥,私钥对授权内容进行签名,公钥给授权方校验License文件是否正确有效。
秘钥制作
在这里,我用的是JDK自带keytool工具制作的。方法如下:
前提:使用CMD命令进入JDK的bin目录下:
## 1. 生成私匙库
# validity:私钥的有效期多少天
# alias:私钥别称
#keyalg:指定加密算法,默认是DSA
# keystore: 指定私钥库文件的名称(生成在当前目录)
# storepass:指定私钥库的密码(获取keystore信息所需的密码)
# keypass:指定别名条目的密码(私钥的密码)
keytool -genkeypair -keysize 1024 -validity 730 -alias "privateKey" -keyalg "RSA" -keystore "D:\KeyStore\privateKey.keystore" -storepass "654321" -keypass "123456" -dname "CN=mine, OU=test, O=test, L=gz, ST=gd, C=CN"
## 2. 生成证书
# alias:私钥别称
# keystore:指定私钥库的名称(在当前目录查找)
# storepass: 指定私钥库的密码
# file:证书名称
keytool -exportcert -alias "privateKey" -keystore "D:\KeyStore\privateKey.keystore" -storepass "654321" -file "D:\KeyStore\publicCer.cer"
到这里,已经创建好了两个文件了。对于License文件的制作和验证,这两个文件是够用的,我在这里顺便生成公匙库,后面验证签名时,可以选择证书或者公钥进行验证。
## 3. 生成公匙库
# alias:公钥别称
# file:证书名称
# keystore:公钥文件名称
# storepass:指定私钥库的密码
keytool -import -alias "publicKey" -file "D:\KeyStore\publicCer.cer" -keystore "D:\KeyStore\publicKey.keystore" -storepass "123456"
运行完之后截图:
生成文件截图:
制作License文件
- 首先,要确定License的内容,确定业务种类、业务范围、有效时间或者其他限制性要求。
我举个简单的栗子:
<?xml version="1.0" encoding="UTF-8"?>
<license vendor="Steven Jon" expiration="2021-04-29" hostId="Win-C498-97D7-262D-55DD" generated="2020-04-29">
<feature name="INFramework" expiration="2021-04-29"/>
<signature>MC0CFHyTYkh59tPA50tylcHk6gVX0KtfAhUAhLnBwN1S1Pi8A1js0BG7cnFzkRE=</signature>
</license>
vendor--------------------------------------------license供应商
expiration----------------------------------------license有效期
hostId---------------------------------------------软件安装序列号,以此为标识
generated----------------------------------------license生成时间
在license下面定义需要使用的功能名称
feature name------------------------------------功能名称,区分业务类型和范围
feature expiration------------------------------功能有效期
我只写了INFramework一个feature,可以写多个feature在这里。
signature-----------------------------------------顾名思义,签名 下面内容讲如何生成
- signature
从上面可以看出license文件的全部内容,但是我们不必把它所有内容都进行签名,提取关键信息,按照自己的格式连接起来,然后再进行签名,这样安全性是最高的。
举栗子,按照这种方式进行连接起来:
//格式:vender expiration hostId generated feature name feature expiration sys hostId
//需要注意的是sys hostId,这个是软件实际的安装序列号,签名时跟hostId值一样,但是验证时一定要读取实际的序列号
//示例
Steven Jon2021-04-29Win-C498-97D7-262D-55DD2020-04-29INFramework2021-04-29Win-C498-97D7-262D-55DD
需要获取私钥对上面内容进行签名:
public class KeyConfig {
private static final String BASE_FILE_PATH = "D:/KeyStore";
//私钥存放路径
public static final String PRIVATE_KEY_FILE_PATH = BASE_FILE_PATH + "/privateKey.keystore";
//公钥存放路径
public static final String PUBLICKEY_FILE_PATH = BASE_FILE_PATH + "/publicKey.keystore";
//Cer证书存放路径
public static final String CER_FILE_PATH = BASE_FILE_PATH + "/publicCer.cer";
//私钥别名
public static final String PRIVATE_ALIAS = "privateKey";
//公钥别名
public static final String PUBLIC_ALIAS = "publicKey";
//获取keystore所需的密码
public static final String KEYSTORE_PASSWORD = "654321";
//获取私钥所需密码
public static final String KEY_PASSWORD = "123456";
}
获取私钥工具类:
public class KeyTools
{
/**
* 通过keystore获取private key
*
*/
public static PrivateKey getPrivateKey() {
FileInputStream is = null;
PrivateKey privateKey = null;
try {
KeyStore keyStore = KeyStore.getInstance("JKS");
is = new FileInputStream(new File(KeyConfig.PRIVATE_KEY_FILE_PATH));
keyStore.load(is, KeyConfig.KEYSTORE_PASSWORD.toCharArray());
privateKey = (PrivateKey) keyStore.getKey(KeyConfig.PRIVATE_ALIAS, KeyConfig.KEY_PASSWORD.toCharArray());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return privateKey;
}
}
签名工具类:
public class SignTools
{
//非对称密钥算法
private static final String KEY_ALGORITHM = "SHA1withRSA";
/**
* 使用私钥进行许可内容签名
*/
public static byte[] sign(byte[] message, PrivateKey privateKey) throws Exception {
Signature signature;
signature = Signature.getInstance(KEY_ALGORITHM);
signature.initSign(privateKey);
signature.update(message);
return Base64.getEncoder().encode(signature.sign());
}
}
对示例内容进行签名:
//示例签名内容
String signContent = "Steven Jon2021-04-29Win-C498-97D7-262D-55DD2020-04-29INFramework2021-04-29Win-C498-97D7-262D-55DD";
//进行签名
byte[] sign = SignTools.sign(signContent.getBytes(), KeyTools.getPrivateKey());
System.out.println(">>>>>>>>>>>>>>>>>>私钥签名结果 :" + new String(sign));
把new String(sign)结果填入licence文件下的signature标签里,写到这里,license文件制作完成。
看到这里,是不是觉得so easy,自己完全可以写一个license生成器,这样生成license文件就会很轻松了,一键到位。
验证License文件
验证license是否有效,需要用到证书文件,或者公钥,用cer证书最后也是为了获取公钥的,至于用哪个,看自己的咯,可以把这个文件发给客户,也可以放到服务器上,只要能找到就可以了。
举个栗子,发cer证书给客户,让客户把证书安装在指定位置。
获取公钥工具类:
public class KeyTools
{
/**
* 通过 cer证书获取公钥
*/
public static PublicKey getPublicKeyFromCer(){
PublicKey publicKey = null;
FileInputStream in = null;
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
in = new FileInputStream(KeyConfig.CER_FILE_PATH);
Certificate c = cf.generateCertificate(in);
publicKey = c.getPublicKey();
} catch (CertificateException | FileNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return publicKey;
}
}
验证签名工具类:
public class VerifyTools
{
//非对称密钥算法
private static final String KEY_ALGORITHM = "SHA1withRSA";
/**
* 验签
*/
public static boolean verify(byte[] message, byte[] signMessage, PublicKey publicKey) throws Exception {
Signature signature;
signature = Signature.getInstance(KEY_ALGORITHM);
signature.initVerify(publicKey);
signature.update(message);
return signature.verify(Base64.getDecoder().decode(signMessage));
}
}
解析License文件,将里面的关键信息提取出来,按照签名之前的格连接起来。
这一步我就不详细写了,大概过程就是解析xml,难倒是不难,感觉会有点繁琐的,毕竟需要提取的信息比较多。
//偷个懒直接拿上面的
String message = "Steven Jon2021-04-29Win-C498-97D7-262D-55DD2020-04-29INFramework2021-04-29Win-C498-97D7-262D-55DD";
boolean verify = VerifyTools.verify(message.getBytes(), sign, KeyTools.getPublicKeyFromCer());
System.out.println(">>>>>>>>>>>>>>>>>>验证结果 verify =" + verify);
好了,文章写完了,非常感谢你能看到最后,如果觉得有帮助,麻烦点个赞哦!