起因
在论坛高强度水贴学习过程中,我无意间浏览到了这个帖子
因为强迫症想修改激活码内容,又在不久前学习了 @novice.li 大佬的神贴Idea agent 激活原理,年轻的我简单得以为找到Squaretest的证书信息,并且调用下之前的轮子就可以了。很快我就遇到了问题。
开始学习
证书获取
不同于Jetbrains公司的证书获取,Squaretest并没有插件开发,相关信息只能从网上寻找,通过搜索关键词 squaretest 破解 我找到了CSDN文章。其中我得出了以下信息
- TestStarter-xxx.jar 会是我需要的入口
- doValidate()内内容会有我需要的验签内容
按照帖子内容,我下载了JD-GUI,虽然小有变动,但是还是找到了验签内容
细心的佬友已经注意到了第60行的
Signature signature = Signature.getInstance("SHA256withDSA", "SUN");
但是被我忽视了,我径直冲向61行的
signature.initVerify(b);
这里的b是来自同类的开头部分赋值的PublicKey
类
static {
try {
KeyFactory keyFactory = KeyFactory.getInstance("DSA");
byte[] arrayOfByte = Base64.getDecoder().decode("MIIExjCCAzkGByqGSM44BAEwggMsAoIBgQDqnNqfX72mbdgwSUYJQFaHq3zzhTjgWNHi9o3qlTZIZuHAW+rN7SQift7ijK2AvOytOZE747cTJns7lsjZ8PagO138kiLVz+SvzJmC8zeE92DDt1muvju+kJimuEyW8f3kTOEcCEwqCCx6dqDvFCkotPMoQGq5vrJPhFd90PRs6G/Y8ISIJpmYv0dC1kJfeg7HXYZgxd1vTjs9O+6BssIa/oyei4S4cZLizCD5YdK82BM6/PNnWrgGgcs3THjzPinRARCD2J+cVyi5Rnb8yxtXvGAojBXYWug4rhlBxaIK4rIEmzWD/jDaRV3bPmrZuZVc2btWgUMWIr6w+S2lM/yrSWzrxEeqG7WoA5Ui8tqY/0FiiTI6ZN9iaraIGHCSfc7jh/E7XJ0k1suh2C7TdaCCUG7oe8euMAZ/SpTi7jY9mSxA8nJbXbSzUl694iu7/Q+hJKWIsPWkrLOoaVGv8J+MgZj7W1PaDJMc7cWYtPg1t3nQTZkCbHugjEsn8RisHj0CIQDE7qwrureb2DGUbXF6Vqbmh1R6qOnFSUpaSy9MoT1sEQKCAYBC5fp4RPj6nYmY2DDQBOexWx0na8vl8Sw17JDBol9YMgGKZyS9nNvoA7Z1UJvtFn89fPhZn8hlxtWg95FYwbyRjwCpRNCtDzj1IPuR2F2CZ00NX4dPql/N/lbNF4wa/cfOh5Vye33ulm7Qs8XO3O+KymKL7+vy0QXHr/jrDanJYQc33WTc4SN7gsGyvIYI1V/9qY1xiUROZYgzFWacBXFr3jbHixMKo98uTWCZFMfI3EcPTjABh8d1+B57GpwNzkBdbqssu52cTvREErpXPdQDxO17wjZHcvVqMMSN549QA/k3HFUmLSyKwiRq3jsC/c/PXL/edPvL/m4OD98xYHZPhNMRwXmkCvZ5qPR6sTyPcGiTJF6xHtzORR+iq5gAGZh/El2NyWYi1Bm6DXHxbGAk3OnTZMOybY7Bo8go9snRSx0DM7ldt3v9vjxrzlM3oaWnrOEBESGUSERxl+KjRMxCO+dou4nie+bL0iCFYUpaM2C+I7G/u25uZHE2PTLIXTEDggGFAAKCAYA3NdE3k0LSjmNgtHl1RiTyxig+sFrjvvhWngG3dCXvexWWOccSKfHGJ5NasB0KEh4gGcDCxayuYM4ZqD28ATswcsMUQpkdXfct7Npnheo8OFIdt46TRbFuGcLhwhCnSmqKbd0jRrLfIC7OPOIBSQN//uDO3qwTFGmnNXq8hbxbW6ux28oEOw4swa0l9sS4naAH5Dh2lqh9jwL8Aaeg2wPEeSjIZqvH93fgTTfzWhPxx2ehYcYS6rkaaqxCyPjau65hdfK3tGX0gg90KYRdK42mtRuuWUnkpprf6lnQDqeFkdY8bCh3fOOsRUzE4mLAbieDsDF3JQgc/6yGQWigAqsp+nnHfpVg1A3r5vMoluAvOPbwh9rUVU8JwFaPC+0q4B+mlmNPzber1VSGXMhiBQdU9c/GgIlvhIFYXoWZTJHrAjmE2hFFnSwnEt8hZeVU6zWE8VmkaT7lFkv42iVOtn1VZLhc4bcgiStVRXlu/T56+f3iug3YCyqhf0/NV8Huqus=".getBytes(StandardCharsets.UTF_8));
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(arrayOfByte);
b = keyFactory.generatePublic(x509EncodedKeySpec);
} catch (Throwable throwable) {
a.error("Exception parsing Squaretest public key.", throwable);
throw new RuntimeException(throwable);
}
}
想必聪明的佬友已经知道我拿着DSA的证书去尝试 SHA1withRSA的相关方法遇到了一大堆报错
证书生成
意识到不能简单套用RSA相关方法的我先准备生成自己的DSA证书,比较简单就直接贴代码了
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;
public class CertificateGeneration {
/**
* DiffieHellman (1024, 2048, 4096)
* DSA (1024, 2048)
* RSA (1024, 2048, 4096)
*
* @param algorithm 算法
* @param keySize 密钥长度
* @param signatureAlgorithm 签名算法
* @param CERTIFICATE_PATH 证书路径
* @param PRIVATE_KEY_PATH 私钥路径
*/
public static void genCertKey(String algorithm, Integer keySize, String signatureAlgorithm, String CERTIFICATE_PATH, String PRIVATE_KEY_PATH) throws NoSuchAlgorithmException, OperatorCreationException, CertificateException, IOException {
Security.addProvider(new BouncyCastleProvider());
//使用algorithm算法生成一个keySize位的密钥对
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(algorithm);
keyPairGen.initialize(keySize, new SecureRandom());
KeyPair keyPair = keyPairGen.generateKeyPair();
//获取私钥和公钥
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
//创建证书的发行者名称和主题名称
X500Name issuerName = new X500Name("CN=JetProfile CA");
X500Name subjectName = new X500Name("CN=Novice-from-2024-01-19");
//生成一个随机的主题标识
BigInteger subject = new BigInteger(String.valueOf(System.currentTimeMillis()));
//设置证书的生效时间和过期时间。
Date notBefore = new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000);//昨天
Date notAfter = new Date(System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000);//一年后
//使用JcaX509v3CertificateBuilder构建证书
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(issuerName, subject, notBefore, notAfter, subjectName, publicKey);
//使用证书的私钥对证书进行签名
ContentSigner signer = new JcaContentSignerBuilder(signatureAlgorithm).build(privateKey);
X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certBuilder.build(signer));
//写私钥文件
try (JcaPEMWriter pemWriter = new JcaPEMWriter(new FileWriter(PRIVATE_KEY_PATH))) {
pemWriter.writeObject(privateKey);
}
//写证书文件
try (JcaPEMWriter pemWriter = new JcaPEMWriter(new FileWriter(CERTIFICATE_PATH))) {
pemWriter.writeObject(cert);
}
}
}
在获得证书文件后我自以为然的也是先生成License再获取power.conf中配置规则就好
许可证生成
根据验签流程51-64和139-160的部分
/* 51 */ List<String> list = b(paramString);
/* 52 */ if (!b(list)) {
/* 53 */ return r.a;
/* */ }
/* */
/* */
/* 57 */ String str1 = (String)list.get(1) + "\n" + (String)list.get(1) + "\n" + (String)list.get(2) + "\n";
/* 58 */ String str2 = list.get(4);
/* */ try {
/* 60 */ Signature signature = Signature.getInstance("SHA256withDSA", "SUN");
/* 61 */ signature.initVerify(b);
/* 62 */ signature.update(str1.getBytes(StandardCharsets.UTF_8));
/* 63 */ byte[] arrayOfByte = Base64.getDecoder().decode(str2);
/* 64 */ boolean bool = signature.verify(arrayOfByte);
...省略一部分
/* */ private List<String> b(@NotNull String paramString) {
/* 140 */ if (paramString == null) a(0); String str = paramString.trim().replace("\r\n", "\n");
/* 141 */ String[] arrayOfString = str.split("\n");
/* 142 */ int i = b(arrayOfString);
/* 143 */ int j = a(arrayOfString);
/* 144 */ ArrayList<String> arrayList = new ArrayList();
/* 145 */ arrayList.add("--- BEGIN SQUARETEST LICENSE ---");
/* 146 */ for (int k = i; k <= j; k++) {
/* 147 */ String str1 = arrayOfString[k].trim();
/* 148 */ if (!StringUtils.isBlank(str1))
/* */ {
/* */
/* 151 */ if (arrayList.size() == 5) {
/* */
/* 153 */ arrayList.set(4, (String)arrayList.get(4) + (String)arrayList.get(4));
/* */ } else {
/* 155 */ arrayList.add(str1);
/* */ } }
/* */ }
/* 158 */ arrayList.add("--- END SQUARETEST LICENSE ---");
/* 159 */ return arrayList;
/* */ }
再结合上前述帖子给出的激活码
--- BEGIN SQUARETEST LICENSE ---
neo
https://zhile.io
SQT1-101010101010101010101010101010101010
MEUCID2bg5gEzJLdpGbBU9QvoGsXZ0VIucgRP340/6s6omIKAiEAo9xDQdd07SPucE5w6cPLfUeo7YqLT+EIrVqGMOELzrs=
--- END SQUARETEST LICENSE ---
我们可以知道许可证用于签名的部分是name+url+key组成的
public static void genLicense(String privateKeyFilePath, String name, String url, DSAPublicKey publicKey) throws Exception {
String key = "SQT1-101010101010101010101010101010101010";
String licensePart = name + "\n" + url + "\n" + key + "\n";
byte[] licensePartBytes = licensePart.getBytes(StandardCharsets.UTF_8);
DSAPrivateKey privateKey = (DSAPrivateKey) getPrivateKey(privateKeyFilePath);
Signature signature = Signature.getInstance("SHA256withDSA", "SUN");
signature.initSign(privateKey);
signature.update(licensePartBytes);
AlgorithmParameters parameters = signature.getParameters();
byte[] signatureBytes = signature.sign();
String sigResultsBase64 = Base64.getEncoder().encodeToString(signatureBytes);
String start = "--- BEGIN SQUARETEST LICENSE ---";
String end = "--- END SQUARETEST LICENSE ---";
String license = start + "\n" + name + "\n" + url + "\n" + key + "\n" + sigResultsBase64 + "\n" + end + "\n";
System.out.println(license);
}
这样我们就得到了自己的许可证
power.conf文件规则生成
前置信息
吾皇plugin-power插件 power.conf
文件有以下配置区域
[Result]
EQUAL,x,y,z->fakeResult
这个代表了会将代码中x.modpow(y,z)
结果替换成我们预设的 fakeResult
,这个函数代表了数学运算 x^y \mod z
RSA体系
- 密钥对生成:
使用RSA算法生成一对公钥和私钥。私钥由两个大素数 p 和 q 以及它们的欧拉函数值 phi(n) 计算得出,其中 n = p * q 是模数。私钥还包括一个整数 d ,它是 e (通常选择为65537)关于 phi(n) 的模逆元,即满足 d \times e ≡ 1 (\mod phi(n)) 。 - 签名过程:
对消息 M 进行预处理(如使用SHA-256等散列函数生成消息摘要 H(M) )。
签名者用其私钥 (n, d) 对消息摘要进行加密计算: S = H(M)^d \mod n ,生成的 S 就是签名。 - 验签过程:
验证者收到原始消息 M 及其对应的签名 S。
使用签名者的公钥 (n, e) 对签名进行解密运算,即计算: V = S^e \mod n 。
接下来,验证者需要重新计算消息 M 的摘要 H'(M) 。
最后比较计算出的 V 是否与重新计算的消息摘要 H'(M) 相同。如果二者相同,则说明签名有效,因为只有拥有私钥的人才能创建出在公开验签过程中能够正确还原至原始摘要的签名。
通过上述信息,我们可以很简单的推断出我们需要篡改的x.modpow(y,z)
和fakeResult
分别为
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
public class PowerConfRuleGeneration {
/**
* 生成配置规则字符串
* @param certificatesFilePath 证书文件路径
* @param rootCertificates 根证书字符串
* @return 配置规则字符串
*/
static void genPowerConfRule(String certificatesFilePath, String rootCertificates) throws CertificateException, IOException {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(Files.newInputStream(Paths.get(certificatesFilePath)));
//证书的签名密文
BigInteger x = new BigInteger(1, cert.getSignature());
//证书指数 固定65537
BigInteger y = new BigInteger("65537");
//内置根证书的公钥
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(rootCertificates.getBytes(StandardCharsets.UTF_8));
X509Certificate rootCertificate = (X509Certificate) certificateFactory.generateCertificate(byteArrayInputStream);
RSAPublicKey publicKey = (RSAPublicKey) rootCertificate.getPublicKey();
BigInteger z = publicKey.getModulus();
//对DER 编码的证书信息(即来自该证书的tbsCertificate) 进行sha265摘要计算,计算的结果转换为ASN1格式数据,ASN1格式数据再进行填充得到的
RSAPublicKey certPublicKey = (RSAPublicKey) cert.getPublicKey();
BigInteger result = x.modPow(certPublicKey.getPublicExponent(), certPublicKey.getModulus());
System.out.println("使用ja-netfilter并在power.conf中的 [result] 节下新增一行配置");
System.out.printf("EQUAL,%s,%s,%s->%s%n", x, y, z, result);
}
}
DSA体系
公钥
参数 | 说明 |
---|---|
p | 素数,长度为L位 |
q | q 是 p−1 的素因子,长度为N位 |
g | g=h^{(p−1)/q} \mod p ,其中 h 为满足以下条件的数: h^{(p−1)/q} \mod p>1 , h 的值域: [2,p−2] |
y | y=g^x \mod p |
根据FIPS 186-4的建议,L和N的组合包括:(1024, 60),(2048, 224),(2048, 256),(3072, 256)。当然你也可以不遵守此建议。
私钥
参数 | 说明 |
---|---|
x | x<q |
签名
生成随机数 k(k<q)
r=(g^k\space mod \space p)\space mod \space q
s=(k^{-1}(H(m)+x*r))\space mod \space q
其中:
m 为待签名的消息。
H(m) 为对 m 进行摘要运算并取结果。
r 和 s 构成签名值。
验签
w=s^{-1}\space mod \space q
u_1=H(m)*w\space mod \space q
u_2=r*w\space mod \space q
v=(g^{u_1}*y^{u_2}\space mod \space p)\space mod \space q
如果 v=r 成立,则验签成功。
经过上述信息,我们可以看出不像RSA,没办法简单的找出 x^y \mod z 让我们生成power.conf
文件配置规则
吾将上下而求索
到这里我几近放弃,但是我将百折不挠地去追求和探索,在向 @neo 吾皇求助无果后,我开始在演算纸上反复变换公式,直到用模运算的分配律尝试出眉目
模运算的分配律
根据模运算的分配律我们可以将验签公式
变化成
我们已知plugin-power插件 会帮我们将 x^y\mod z 替换为 fakeResult
那我们就将目标就放在了 (y^{u_2}\mod p) 上
再设 KK= (g^{u_1}\mod p)
我们验签问题就变成了
朝闻道 ,夕死可矣
还记得DSA体系中p和q的产生逻辑么?
q 是 p−1 的素因子
我们的验签公式可以化成
((kk \times fakeResult) \mod pq) = v + tq
其中 t 是某个整数。这是因为当 (a \mod b) \mod c 中 b 和 c 互质时,可以转换为一次模运算。
聪明的佬友肯定不像我,看到这里早就知道用中国剩余定理(Chinese Remainder Theorem, CRT)来解决这个问题
在《孙子算经》中有这样一个问题:“今有物不知其数,三三数之剩二(除以3余2),五五数之剩三(除以5余3),七七数之剩二(除以7余2),问物几何?”这个问题称为“孙子问题”,该问题的一般解法国际上称为“中国剩余定理”。具体解法分三步:
- 找出三个数:从3和5的公倍数中找出被7除余1的最小数15,从3和7的公倍数中找出被5除余1 的最小数21,最后从5和7的公倍数中找出除3余1的最小数70。
- 用15乘以2(2为最终结果除以7的余数),用21乘以3(3为最终结果除以5的余数),同理,用70乘以2(2为最终结果除以3的余数),然后把三个乘积相加15∗2+21∗3+70∗215∗2+21∗3+70∗2得到和233。
- 用233除以3,5,7三个数的最小公倍数105,得到余数23,即233%105=23233%105=23。这个余数23就是符合条件的最小数。
综上
Squaretest .java
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.DLSequence;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import java.io.FileReader;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class Squaretest {
private static final String CERTIFICATE_PATH = "D:\\Develop\\Tools\\generateJetbrainsActivationCode\\ca1.crt";
//私钥路径
private static final String PRIVATE_KEY_PATH = "D:\\Develop\\Tools\\generateJetbrainsActivationCode\\ca1.key";
public static void main(String[] args) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("DSA");
byte[] decoded = Base64.getDecoder().decode(SQUARETEST_PUBLIC_KEY);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decoded);
PublicKey squaretestPublicKey = keyFactory.generatePublic(keySpec);
CertificateGeneration.genCertKey("DSA", 2048, "SHA256withDSA", CERTIFICATE_PATH, PRIVATE_KEY_PATH);
genLicense(PRIVATE_KEY_PATH, "neo", "https://zhile.io", (DSAPublicKey) squaretestPublicKey);
}
private static final String SQUARETEST_PUBLIC_KEY = "MIIExjCCAzkGByqGSM44BAEwggMsAoIBgQDqnNqfX72mbdgwSUYJQFaHq3zzhTjgWNHi9o3qlTZIZuHAW+rN7SQift7ijK2AvOytOZE747cTJns7lsjZ8PagO138kiLVz+SvzJmC8zeE92DDt1muvju+kJimuEyW8f3kTOEcCEwqCCx6dqDvFCkotPMoQGq5vrJPhFd90PRs6G/Y8ISIJpmYv0dC1kJfeg7HXYZgxd1vTjs9O+6BssIa/oyei4S4cZLizCD5YdK82BM6/PNnWrgGgcs3THjzPinRARCD2J+cVyi5Rnb8yxtXvGAojBXYWug4rhlBxaIK4rIEmzWD/jDaRV3bPmrZuZVc2btWgUMWIr6w+S2lM/yrSWzrxEeqG7WoA5Ui8tqY/0FiiTI6ZN9iaraIGHCSfc7jh/E7XJ0k1suh2C7TdaCCUG7oe8euMAZ/SpTi7jY9mSxA8nJbXbSzUl694iu7/Q+hJKWIsPWkrLOoaVGv8J+MgZj7W1PaDJMc7cWYtPg1t3nQTZkCbHugjEsn8RisHj0CIQDE7qwrureb2DGUbXF6Vqbmh1R6qOnFSUpaSy9MoT1sEQKCAYBC5fp4RPj6nYmY2DDQBOexWx0na8vl8Sw17JDBol9YMgGKZyS9nNvoA7Z1UJvtFn89fPhZn8hlxtWg95FYwbyRjwCpRNCtDzj1IPuR2F2CZ00NX4dPql/N/lbNF4wa/cfOh5Vye33ulm7Qs8XO3O+KymKL7+vy0QXHr/jrDanJYQc33WTc4SN7gsGyvIYI1V/9qY1xiUROZYgzFWacBXFr3jbHixMKo98uTWCZFMfI3EcPTjABh8d1+B57GpwNzkBdbqssu52cTvREErpXPdQDxO17wjZHcvVqMMSN549QA/k3HFUmLSyKwiRq3jsC/c/PXL/edPvL/m4OD98xYHZPhNMRwXmkCvZ5qPR6sTyPcGiTJF6xHtzORR+iq5gAGZh/El2NyWYi1Bm6DXHxbGAk3OnTZMOybY7Bo8go9snRSx0DM7ldt3v9vjxrzlM3oaWnrOEBESGUSERxl+KjRMxCO+dou4nie+bL0iCFYUpaM2C+I7G/u25uZHE2PTLIXTEDggGFAAKCAYA3NdE3k0LSjmNgtHl1RiTyxig+sFrjvvhWngG3dCXvexWWOccSKfHGJ5NasB0KEh4gGcDCxayuYM4ZqD28ATswcsMUQpkdXfct7Npnheo8OFIdt46TRbFuGcLhwhCnSmqKbd0jRrLfIC7OPOIBSQN//uDO3qwTFGmnNXq8hbxbW6ux28oEOw4swa0l9sS4naAH5Dh2lqh9jwL8Aaeg2wPEeSjIZqvH93fgTTfzWhPxx2ehYcYS6rkaaqxCyPjau65hdfK3tGX0gg90KYRdK42mtRuuWUnkpprf6lnQDqeFkdY8bCh3fOOsRUzE4mLAbieDsDF3JQgc/6yGQWigAqsp+nnHfpVg1A3r5vMoluAvOPbwh9rUVU8JwFaPC+0q4B+mlmNPzber1VSGXMhiBQdU9c/GgIlvhIFYXoWZTJHrAjmE2hFFnSwnEt8hZeVU6zWE8VmkaT7lFkv42iVOtn1VZLhc4bcgiStVRXlu/T56+f3iug3YCyqhf0/NV8Huqus=";
public static void genLicense(String privateKeyFilePath, String name, String url, DSAPublicKey publicKey) throws Exception {
String key = "SQT1-101010101010101010101010101010101010";
String licensePart = name + "\n" + url + "\n" + key + "\n";
byte[] licensePartBytes = licensePart.getBytes(StandardCharsets.UTF_8);
DSAPrivateKey privateKey = (DSAPrivateKey) getPrivateKey(privateKeyFilePath);
Signature signature = Signature.getInstance("SHA256withDSA", "SUN");
signature.initSign(privateKey);
signature.update(licensePartBytes);
byte[] signatureBytes = signature.sign();
String sigResultsBase64 = Base64.getEncoder().encodeToString(signatureBytes);
String start = "--- BEGIN SQUARETEST LICENSE ---";
String end = "--- END SQUARETEST LICENSE ---";
String license = start + "\n" + name + "\n" + url + "\n" + key + "\n" + sigResultsBase64 + "\n" + end + "\n";
System.out.println(license);
BigInteger r = ASN1Integer.getInstance(DLSequence.getInstance(signatureBytes).getObjectAt(0)).getValue();
BigInteger s = ASN1Integer.getInstance(DLSequence.getInstance(signatureBytes).getObjectAt(1)).getValue();
// 创建MessageDigest实例,并指定算法为SHA-256
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(licensePartBytes);
byte[] licensePartHashBytes = md.digest();
BigInteger licensePartHashBigInteger = new BigInteger(1, licensePartHashBytes);
BigInteger y1 = publicKey.getY();
BigInteger q1 = publicKey.getParams().getQ();
BigInteger g1 = publicKey.getParams().getG();
BigInteger p1 = publicKey.getParams().getP();
BigInteger w = s.modPow(BigInteger.valueOf(-1), q1);
BigInteger u1 = licensePartHashBigInteger.multiply(w).mod(q1);
BigInteger u2 = r.multiply(w).mod(q1);
BigInteger x = y1;
BigInteger y = u2;
BigInteger z = p1;
BigInteger kk = g1.modPow(u1, p1);
BigInteger pq = p1.multiply(q1);
BigInteger kkInvModPQ;
if (kk.gcd(pq).equals(BigInteger.ONE)) {
kkInvModPQ = kk.modInverse(pq);
} else {
System.out.println("无解,因为kk与pq不互质");
return;
}
BigInteger result = r.multiply(kkInvModPQ).mod(pq);
System.out.println("使用ja-netfilter并在power.conf中的 [result] 节下新增一行配置");
System.out.printf("EQUAL,%s,%s,%s->%s%n", x, y, z, result);
}
private static PrivateKey getPrivateKey(String privateKeyFilePath) throws IOException {
Security.addProvider(new BouncyCastleProvider());
PEMParser pemParser = new PEMParser(new FileReader(privateKeyFilePath));
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
Object object = pemParser.readObject();
KeyPair kp = converter.getKeyPair((PEMKeyPair) object);
return kp.getPrivate();
}
}
CertificateGeneration.java
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;
public class CertificateGeneration {
/**
* DiffieHellman (1024, 2048, 4096)
* DSA (1024, 2048)
* RSA (1024, 2048, 4096)
*
* @param algorithm 算法
* @param keySize 密钥长度
* @param signatureAlgorithm 签名算法
* @param CERTIFICATE_PATH 证书路径
* @param PRIVATE_KEY_PATH 私钥路径
*/
public static void genCertKey(String algorithm, Integer keySize, String signatureAlgorithm, String CERTIFICATE_PATH, String PRIVATE_KEY_PATH) throws NoSuchAlgorithmException, OperatorCreationException, CertificateException, IOException {
Security.addProvider(new BouncyCastleProvider());
//使用algorithm算法生成一个keySize位的密钥对
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(algorithm);
keyPairGen.initialize(keySize, new SecureRandom());
KeyPair keyPair = keyPairGen.generateKeyPair();
//获取私钥和公钥
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
//创建证书的发行者名称和主题名称
X500Name issuerName = new X500Name("CN=JetProfile CA");
X500Name subjectName = new X500Name("CN=Novice-from-2024-01-19");
//生成一个随机的主题标识
BigInteger subject = new BigInteger(String.valueOf(System.currentTimeMillis()));
//设置证书的生效时间和过期时间。
Date notBefore = new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000);//昨天
Date notAfter = new Date(System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000);//一年后
//使用JcaX509v3CertificateBuilder构建证书
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(issuerName, subject, notBefore, notAfter, subjectName, publicKey);
//使用证书的私钥对证书进行签名
ContentSigner signer = new JcaContentSignerBuilder(signatureAlgorithm).build(privateKey);
X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certBuilder.build(signer));
//写私钥文件
try (JcaPEMWriter pemWriter = new JcaPEMWriter(new FileWriter(PRIVATE_KEY_PATH))) {
pemWriter.writeObject(privateKey);
}
//写证书文件
try (JcaPEMWriter pemWriter = new JcaPEMWriter(new FileWriter(CERTIFICATE_PATH))) {
pemWriter.writeObject(cert);
}
}
}
致谢
感谢上述帖子、佬友、大佬的知识分享
别忘了点个赞再走哦