IDEA Squaretest 插件激活原理分析

起因

在论坛高强度水贴学习过程中,我无意间浏览到了这个帖子

因为强迫症想修改激活码内容,又在不久前学习了 @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 吾皇求助无果后,我开始在演算纸上反复变换公式,直到用模运算的分配律尝试出眉目

模运算的分配律

(a * b)\space mod\space m = [(a\space mod\space m) * (b\space mod\space m)] \space mod\space m

根据模运算的分配律我们可以将验签公式

v=(g^{u_1}*y^{u_2}\mod p)\mod q

变化成

v=\{ [(g^{u_1}\mod p) * (y^{u_2}\mod p)] \mod p\}\mod q

我们已知plugin-power插件 会帮我们将 x^y\mod z 替换为 fakeResult
那我们就将目标就放在了 (y^{u_2}\mod p)
再设 KK= (g^{u_1}\mod p)
我们验签问题就变成了

v=[(KK * fakeResult) \mod p]\ mod q

朝闻道 ,夕死可矣

还记得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),问物几何?”这个问题称为“孙子问题”,该问题的一般解法国际上称为“中国剩余定理”。具体解法分三步:

  1. 找出三个数:从3和5的公倍数中找出被7除余1的最小数15,从3和7的公倍数中找出被5除余1 的最小数21,最后从5和7的公倍数中找出除3余1的最小数70。
  2. 用15乘以2(2为最终结果除以7的余数),用21乘以3(3为最终结果除以5的余数),同理,用70乘以2(2为最终结果除以3的余数),然后把三个乘积相加15∗2+21∗3+70∗215∗2+21∗3+70∗2得到和233。
  3. 用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);
        }
    }
}

致谢

感谢上述帖子、佬友、大佬的知识分享

别忘了点个赞再走哦:full_moon_with_face:

44 个赞

厉害的佬

4 个赞

常规话题软件开发

666

4 个赞

技术文看起来太爽

5 个赞

啊这,这个插件我某天一时兴起弄的。根本没有这么复杂的运算,我也没去研究什么DSA :crazy_face:

我只是把它拉起来,在modPow方法那里打个断点,执行一次它的key验证,再执行一次我fake key的验证。拿到两个结果之后power里替换一下就行了。。

18 个赞

专业

2 个赞

好好好【不知道有什么表情表示心情又不违规】

2 个赞

发小丑会被举报的,少发小丑

2 个赞

太骚了 点赞

2 个赞

为啥啊?是ai还是有什么互联网消息俺不知道?

3 个赞

我引入的友善度判别 AI,会把小丑表情认为不友善给举报了

6 个赞

AI觉得你发小丑是有毒

5 个赞

技术大佬

4 个赞

干货满满

2 个赞

大佬大佬

2 个赞

不明觉厉

2 个赞

nb, 太强了

2 个赞

学习了

2 个赞

返璞归真了属于是

4 个赞