前言
本文为个人通过拜读 novice.li大佬的帖子进行的汇总整理,从证书生成到最后的激活码生成均已java实现(python也写了一份不过没试验过)
代码
依赖
<properties>
<bouncycastle.version>1.74</bouncycastle.version>
</properties>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
</dependencies>
证书生成
注:注意将ca.key和ca.crt换成自己的存储路径
import cn.hutool.core.io.FileUtil;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
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.jcajce.JcaContentSignerBuilder;
import java.io.StringWriter;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.Date;
public class CertificateGenerator {
public static void genCrtKey() throws Exception {
Security.addProvider(new BouncyCastleProvider());
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(4096, new SecureRandom());
KeyPair keyPair = keyGen.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 serialNumber = BigInteger.valueOf(System.currentTimeMillis());
Date notBefore = new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000); // Yesterday
Date notAfter = new Date(System.currentTimeMillis() + 3650L * 24 * 60 * 60 * 1000); // 10 years later
SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
issuerName, serialNumber, notBefore, notAfter, subjectName, subPubKeyInfo);
ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(privateKey);
X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certBuilder.build(signer));
// 将私钥写入 PEM 文件
try (JcaPEMWriter pemWriter = new JcaPEMWriter(new FileWriter("ca.key"))) {
pemWriter.writeObject(privateKey);
}
// 将证书写入 PEM 文件
try (JcaPEMWriter pemWriter = new JcaPEMWriter(new FileWriter("ca.crt"))) {
pemWriter.writeObject(cert);
}
}
public static void main(String[] args) {
try {
genCrtKey();
} catch (Exception e) {
e.printStackTrace();
}
}
}
生成ja-netfilter
的power.conf
中[Result]
配置
生成
注:其中的ca.crt改为上面生成ca.crt的绝对路径
import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
public class PowerConfRuleGen {
private static final String ROOT_CERTIFICATE = "-----BEGIN CERTIFICATE-----\n" +
"MIIFOzCCAyOgAwIBAgIJANJssYOyg3nhMA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV\n" +
"BAMMDUpldFByb2ZpbGUgQ0EwHhcNMTUxMDAyMTEwMDU2WhcNNDUxMDI0MTEwMDU2\n" +
"WjAYMRYwFAYDVQQDDA1KZXRQcm9maWxlIENBMIICIjANBgkqhkiG9w0BAQEFAAOC\n" +
"Ag8AMIICCgKCAgEA0tQuEA8784NabB1+T2XBhpB+2P1qjewHiSajAV8dfIeWJOYG\n" +
"y+ShXiuedj8rL8VCdU+yH7Ux/6IvTcT3nwM/E/3rjJIgLnbZNerFm15Eez+XpWBl\n" +
"m5fDBJhEGhPc89Y31GpTzW0vCLmhJ44XwvYPntWxYISUrqeR3zoUQrCEp1C6mXNX\n" +
"EpqIGIVbJ6JVa/YI+pwbfuP51o0ZtF2rzvgfPzKtkpYQ7m7KgA8g8ktRXyNrz8bo\n" +
"iwg7RRPeqs4uL/RK8d2KLpgLqcAB9WDpcEQzPWegbDrFO1F3z4UVNH6hrMfOLGVA\n" +
"xoiQhNFhZj6RumBXlPS0rmCOCkUkWrDr3l6Z3spUVgoeea+QdX682j6t7JnakaOw\n" +
"jzwY777SrZoi9mFFpLVhfb4haq4IWyKSHR3/0BlWXgcgI6w6LXm+V+ZgLVDON52F\n" +
"LcxnfftaBJz2yclEwBohq38rYEpb+28+JBvHJYqcZRaldHYLjjmb8XXvf2MyFeXr\n" +
"SopYkdzCvzmiEJAewrEbPUaTllogUQmnv7Rv9sZ9jfdJ/cEn8e7GSGjHIbnjV2ZM\n" +
"Q9vTpWjvsT/cqatbxzdBo/iEg5i9yohOC9aBfpIHPXFw+fEj7VLvktxZY6qThYXR\n" +
"Rus1WErPgxDzVpNp+4gXovAYOxsZak5oTV74ynv1aQ93HSndGkKUE/qA/JECAwEA\n" +
"AaOBhzCBhDAdBgNVHQ4EFgQUo562SGdCEjZBvW3gubSgUouX8bMwSAYDVR0jBEEw\n" +
"P4AUo562SGdCEjZBvW3gubSgUouX8bOhHKQaMBgxFjAUBgNVBAMMDUpldFByb2Zp\n" +
"bGUgQ0GCCQDSbLGDsoN54TAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkq\n" +
"hkiG9w0BAQsFAAOCAgEAjrPAZ4xC7sNiSSqh69s3KJD3Ti4etaxcrSnD7r9rJYpK\n" +
"BMviCKZRKFbLv+iaF5JK5QWuWdlgA37ol7mLeoF7aIA9b60Ag2OpgRICRG79QY7o\n" +
"uLviF/yRMqm6yno7NYkGLd61e5Huu+BfT459MWG9RVkG/DY0sGfkyTHJS5xrjBV6\n" +
"hjLG0lf3orwqOlqSNRmhvn9sMzwAP3ILLM5VJC5jNF1zAk0jrqKz64vuA8PLJZlL\n" +
"S9TZJIYwdesCGfnN2AETvzf3qxLcGTF038zKOHUMnjZuFW1ba/12fDK5GJ4i5y+n\n" +
"fDWVZVUDYOPUixEZ1cwzmf9Tx3hR8tRjMWQmHixcNC8XEkVfztID5XeHtDeQ+uPk\n" +
"X+jTDXbRb+77BP6n41briXhm57AwUI3TqqJFvoiFyx5JvVWG3ZqlVaeU/U9e0gxn\n" +
"8qyR+ZA3BGbtUSDDs8LDnE67URzK+L+q0F2BC758lSPNB2qsJeQ63bYyzf0du3wB\n" +
"/gb2+xJijAvscU3KgNpkxfGklvJD/oDUIqZQAnNcHe7QEf8iG2WqaMJIyXZlW3me\n" +
"0rn+cgvxHPt6N4EBh5GgNZR4l0eaFEV+fxVsydOQYo1RIyFMXtafFBqQl6DDxujl\n" +
"FeU3FZ+Bcp12t7dlM4E0/sS1XdL47CfGVj4Bp+/VbF862HmkAbd7shs7sDQkHbU=\n" +
"-----END CERTIFICATE-----\n";
public static void main(String[] args) throws Exception {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(Files.newInputStream(Paths.get("./ca.crt")));
// x:证书的签名密文
BigInteger x = new BigInteger(1, cert.getSignature());
// y:证书指数 固定65537
BigInteger y = new BigInteger("65537");
// z:内置根证书的公钥
X509Certificate rootCertificate = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(ROOT_CERTIFICATE.getBytes(StandardCharsets.UTF_8)));
RSAPublicKey publicKey = (RSAPublicKey) rootCertificate.getPublicKey();
BigInteger z = publicKey.getModulus();
// r:fake result
RSAPublicKey certPublicKey = (RSAPublicKey) cert.getPublicKey();
BigInteger r = x.modPow(certPublicKey.getPublicExponent(), certPublicKey.getModulus());
System.out.printf("EQUAL,%s,%s,%s->%s%n", x, y, z, r);
}
}
使用
使用ja-netfilter并在power.conf中的 [result] 节下新增一行配置
EQUAL,x,y,z->r
生成激活码
所需实体类
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author Hua
* @since 2024-02-01 09:05
*/
public class LicensePart {
String licenseId;
String licenseeName;
String assigneeName;
String assigneeEmail;
String licenseRestriction;
boolean checkConcurrentUse = false;
List<Product> products;
String metadata = "0120230914PSAX000005";
String hash = "TRIAL:-1920204289";
int gracePeriodDays = 7;
boolean isAutoProlongated = true;
@Override
public String toString() {
return "{" +
"\"licenseId\":\"" + licenseId + "\"," +
"\"licenseeName\":\"" + licenseeName + "\"," +
"\"assigneeName\":\"" + assigneeName + "\"," +
"\"assigneeEmail\":\"" + assigneeEmail + "\"," +
"\"licenseRestriction\":\"" + licenseRestriction + "\"," +
"\"checkConcurrentUse\":" + checkConcurrentUse + "," +
"\"products\":" + products + "," +
"\"metadata\":\"" + metadata + "\"," +
"\"hash\":\"" + hash + "\"," +
"\"gracePeriodDays\":" + gracePeriodDays + "," +
"\"isAutoProlongated\":" + isAutoProlongated +
"}";
}
public LicensePart(String licenseId, String[] codes, String date) {
this.licenseId = licenseId;
this.licenseeName = licenseId;
this.assigneeName = licenseId;
this.products = Arrays.stream(codes).map(code -> new Product(code, date)).collect(Collectors.toList());
}
public class Product {
String code;
String fallbackDate;
String paidUpTo;
boolean extended = true;
public Product(String code, String date) {
this.code = code;
this.fallbackDate = date;
this.paidUpTo = date;
}
@Override
public String toString() {
return "{" +
"\"code\":\"" + code + "\"," +
"\"fallbackDate\":\"" + fallbackDate + "\"," +
"\"paidUpTo\":\"" + paidUpTo +
"\"," +
"\"extended\":" + extended +
"}";
}
}
}
生成
注:其中的ca.crt改为上面生成ca.crt的绝对路径
我将佬友提供的code值汇总到了DEFAULT_CODES
中,默认是将所有的code都跑出来,这样一个激活码即可激活所有,如果想单独激活某些code的,也可以在调用的generator
方法中传入你要生成的code即可,例:generator("code1", "code2")
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
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.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Calendar;
import java.util.Date;
public class LicenseGenerator {
public static final String[] DEFAULT_CODES = {
"II", "PS", "AC", "DB", "RM", "WS", "RD", "CL", "PC", "GO", "DS", "DC", "DPN", "DM",
"PSYMFONYPLUGIN", "PWLANG", "PSWPLUGIN", "PGITTOOLBOX", "PHYBRISCOMMERCE", "PMATERIALUI",
"PSEQUENCEDIAGRA", "PJETFORCER", "PAEMIDE", "PRNCONSOLE", "PANSIHIGHLIGHT", "PYAOQIANGBPMN",
"PAEM", "PRAINBOWBRACKET", "PGITSCOPE", "PVLOG", "PCODEMRBASE", "PJDCLEANREAD", "PBRWJV",
"PDB", "PEXTRAICONS", "PBISJ", "PSCIPIO", "PBISAA", "PZENUML", "PJFORMDESIGNER", "PORCHIDE",
"PIEDIS", "PCMAKEPLUS", "POPENAPI", "PBETTERHIGHLIGH", "PATOMONEDARK", "PGDOC", "POFFICEFLOOR",
"PWIFIADB", "PLARAVEL", "PODOO", "PCREVIEW", "PMRINTEGEE", "PSFCC", "PMINBATIS", "PPOJOTOJSONSCH",
"PRDFANDSPARQL", "PBASHSUPPORTPRO", "PMYBATISLOG", "PSMARTJUMP", "PJAVACODESUGG", "PGOLANGCODESUGG",
"PRUBYCODESUGG", "PVCS", "PJSCODESUGG", "PPHPCODESUGG", "PSVERILOG", "PSPARQL", "PTOOLSET", "PJSONTOTS",
"PQMLEDITOR", "PSTRKER", "PELASTICSEARCH", "PVISUALGC", "PPYCODESUGG", "PFLUTTER", "PRESTKIT",
"PAWSLAMBDADEPLR", "PPUMLSTUDIO", "PCWMP", "PFIREHIGHLIGHT", "PJPASQL", "PGODRUNNER", "PLEDGER",
"PREGEXTOOL", "PAPH", "PGITLABCI", "PCIRCLECI", "PHEROKU", "PREDISMANAGER", "PZEROCODE", "PSTORMSECTIONS",
"PSENTRYINTEG", "PREDISTOOLS", "PFUZYFIPC", "PBITRISECI", "PQTSQSSEDITOR", "PAPPLETRUNNER", "PDATABASE",
"PHPEAPLUGIN", "PLEP", "PHPBUILDER", "PMATERIALHC", "PCDMQTTCLIENT", "PISCRATCH", "PRSMGNL", "PCAPELASTIC",
"PASTOCK", "PCAPREDIS", "PBEANCONVERTER", "PELSA", "PDJANGOTPLPEP", "PQUERYFLAG", "PNGINX", "PKSEXPLORER",
"PZKA", "PCDAPIRUNNER", "PNEONPRO", "PMBCODEHELPPRO", "PCODEREFACTORAI", "PXSDVISUALIZER", "PSPRINGBOOTIDEA",
"PEXCELEDITOR", "PGITLAB", "PYAPIQUICKTYPE", "PTERMINAL", "PWIREMOCHA", "PDYNAMODB", "PFASTSHELL", "PJSONNETEMLSUP",
"PPHPHOUDINI", "POXYXSDJSONSCH", "PQUARKUSHELPER", "PWGCODECREATOR", "PCIINTG", "PDBDATABASETOOL", "PNGROK",
"PKARATE", "PMATERIALEXTRAS", "PJSONTOANYLANGU", "PMATERIALCUSTOM", "PMATERIALLANG", "PMATERIALFRAME", "PRANCHER",
"PREDISCLIHELPER", "PSCREENCODEPRO", "PCODEKITS", "PREDISS", "PAWSQLADVISOR", "PLATTEPRO", "PGERRYTHEMESPRO",
"PUNIAPPSUPPORT", "POPENAPICRUDWIZ", "PGOPARSER", "PNEXTSKETCH", "PNETLIFY", "PGERRYCYBERPUNK", "PTLDRAI", "PBREWBUNDLE",
"PGERRYSPACE", "PKAFKAIDE", "PGITHUBCI", "PGERRYNATURE", "PEXTENSION", "PSKOL", "PGERRYCHERRY", "PGERRYCOFFEE",
"PCONNECTUI", "POXYJSONCONVERT", "PDOYTOWIN", "PGERRYAURORA", "PWXUFQYRHZCRSEO", "PWAUFKYVHQCRXEO", "PSQLFLUFFLINTER",
"PMAGE", "PTAILWINDTOOLS", "PTRAVISCI", "PMONGOEXPERT", "PNEXTSKETCHTWO", "PWXUQQYVOXCRSEO", "PBUILDMON", "PJETCLIENT",
"PAICODING", "PCAICOMMITAPP", "PCHATGPTCODING", "POLYBPMNGDNEXT", "PARMADILLO", "PVERILOGLANGUAG", "PNOSQLNAVMDB",
"PCUEFY", "PCOMPOSEHAMMER", "PGPTASSISTANT", "PDTOBUDDY", "PNPMPACKAGEJSON", "PAZURECODING", "PGITLABCICD", "PSENTRY",
"PKAFKA", "PSRCODEGEN", "PSOURCESYNCPRO", "PAZD", "PWXUQRYTOXCRSEO", "PPOLARISTOMCATS", "PMYBATISFIELDAD", "PIMAGETOVECTOR",
"PDATAGRAPH", "POXYJSONSCHGEN", "PSPEECHTOTEXT", "PMYSQLPROXY", "PFASTREQUEST", "PMYBATISHELPER", "PREDIS"
};
public static void main(String[] args) throws Exception {
generator();
}
private static void generator(String... codes) throws Exception {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(Files.newInputStream(Paths.get("./ca.crt")));
// 自己修改 license内容
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.add(Calendar.YEAR, 10);
String date = DateUtil.formatDate(calendar.getTime());
codes = codes == null ? DEFAULT_CODES : codes;
String licenseId = "HuaGCS";
LicensePart licensePart = new LicensePart(licenseId, codes, date);
byte[] licensePartBytes = licensePart.toString().getBytes(StandardCharsets.UTF_8);
String licensePartBase64 = Base64.getEncoder().encodeToString(licensePartBytes);
PrivateKey privateKey = getPrivateKey();
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initSign(privateKey);
signature.update(licensePartBytes);
byte[] signatureBytes = signature.sign();
String sigResultsBase64 = Base64.getEncoder().encodeToString(signatureBytes);
// Combine results as needed
String result = licenseId + "-" + licensePartBase64 + "-" + sigResultsBase64 + "-" + Base64.getEncoder().encodeToString(cert.getEncoded());
System.out.println(result);
}
static PrivateKey getPrivateKey() throws Exception {
Security.addProvider(new BouncyCastleProvider());
PEMParser pemParser = new PEMParser(new FileReader(FileUtil.getAbsolutePath("./ca.key")));
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
Object object = pemParser.readObject();
KeyPair kp = converter.getKeyPair((PEMKeyPair) object);
return kp.getPrivate();
}
}
最后
感谢各位大佬的慷慨分享