使用 ja-netfilter 激活 DBeaver Ultimate 26.0

最近想用一下 MongoDB 客户端,习惯用 DBeaver 了,还是最新的版本,发现很多教程都没法正常激活,干脆自己“照虎画猫”,顺带学习下始皇大佬的 ja-netfilter 的进阶用法,自己整了一下,有需要的自取。不够完善或有错误的地方,还望谅解。

目前在 MacOS ARM64 上验证通过,DBeaver 版本为:Version 26.0.0.202603091511

核心思路

  1. 自己生成 RSA 密钥对,密钥长度 2048,密钥指数为 65537,用自己的私钥生成 License 导入到 DBeaver 中
  2. 修改 dbeaver.ini 配置文件,使 DBeaver 使用 ja-netfilter 进行代理拦截
  3. 通过 ja-netfilter Power 插件绕过 RSA 密钥校验。
  4. 通过 ja-netfilter updns 插件绕过 License 证书网络校验

详细分析过程就不写了,可以看看文献,但是 26.0 版本的较 25.3 版本的有些变化,例如 LMECryption 类中的 keyPairs 方法被移除了,密钥对的生成要自己写(但也就几行的问题,easy)

生成 License

注意需要先从 plugins 中拷贝两个 jar 包(其他包是分析的时候用到的,但下面的代码只用到了两个包):

keys/dbeaver-ue-public.key 内容为:

--- PUBLIC KEY ---
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk7ciFU/aUCIgH5flBbGD0t7B3KOmfL0l
BMf2ENuLA0w/T8A1RvteUYk2EQo3UrZ7kMZ8rK93nmDjituN7jlv/bsxGyAox87BbKYSs9oH5f9P
hYHAiTE0PxoMODnl4NgR+Bpc+Ath8wDLHMC+BzYkOy4JQo8EX/ff58TT9UYP8eoDeGdSxQmW3FJC
i82UiC5zIk75dx20Al9ql0fdxnzo31q/2MbnNCAfSchsqrKtzBtheex4JvvqZjxn98wk5Te1QgZz
Caz4ay9dkLVjSt79QYm5hKb8Jt3O5SxSUsrjmYVeG+k2bQlidw8dENwLZmvJkIJi8kb94yEwY/dq
lENDkQIDAQAB

DBeaverLicenseCrack.java

package com.puppetdevz;

/**
 * DBeaver Ultimate 版本许可证实现方法研究
 * 注意事项:仅供学习研究使用
 */

import com.dbeaver.lm.api.*;
import org.jkiss.utils.Base64;

import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.*;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;

/**
 * DBeaver许可证主类
 *
 * 该类实现了完整的DBeaver Ultimate许可证生成流程
 * 包括密钥生成、许可证创建等功能
 */
public class DBeaverLicenseCrack {

    // ==================== 许可证基本信息配置 ====================

    /** 产品标识符 - DBeaver Ultimate Edition */
    private static final String PRODUCT_ID = "dbeaver-ue";

    /** 产品版本号 */
    private static final String PRODUCT_VERSION = "26.0";

    /** 所有者ID */
    private static final String OWNER_ID = "10006";

    /** 所有者公司名称 */
    private static final String OWNER_COMPANY = "DBeaver Corporation";

    /** 所有者姓名 */
    private static final String OWNER_NAME = "Ultimate User";

    /** 所有者邮箱 */
    private static final String OWNER_EMAIL = "developer@dbeaver.com";

    // ==================== 路径配置 ====================

    /** DBeaver安装目录路径 */
    private static final String DBEAVER_ECLIPSE_HOME = "/Applications/DBeaverUltimate.app/Contents/Eclipse";

    /** 项目主目录路径 - 用于存储生成的密钥文件 */
    private static final String CRACK_HOME = "~/codespace/crack-dbeaver";

    // ==================== 文件路径变量 ====================

    /** 私钥文件路径 */
    private static File privateKeyFile;

    /** 公钥文件路径 */
    private static File publicKeyFile;

    /** 许可证文件路径 */
    private static File licenseFile;

    /** JAR包中使用的公钥文件路径 */
    private static File jarPublicKey;

    // ==================== 静态初始化块 ====================

    /**
     * 静态初始化块 - 在类加载时执行
     *
     * 功能:
     * 1. 创建必要的目录结构
     * 2. 初始化所有文件路径变量
     * 3. 输出调试信息
     *
     * 执行时机:类第一次被加载时自动执行
     */
    static {
        try {
            // 构建密钥存储目录路径
            File keysDir = new File(CRACK_HOME, "keys");

            // 如果目录不存在则创建
            if (!keysDir.exists()) {
                if (keysDir.mkdirs()) {
                    System.out.println("[INFO] 成功创建密钥目录: " + keysDir.getAbsolutePath());
                } else {
                    System.err.println("[ERROR] 创建密钥目录失败: " + keysDir.getAbsolutePath());
                }
            }

            System.out.println("[DEBUG] 密钥存储目录: " + keysDir.getAbsolutePath());

            // 初始化各个文件的完整路径
            privateKeyFile = new File(keysDir, "private-key.txt");
            publicKeyFile = new File(keysDir, "public-key.txt");
            licenseFile = new File(keysDir, "license.txt");
            jarPublicKey = new File(keysDir, "dbeaver-ue-public.key");

            // 输出调试信息
            System.out.println("[DEBUG] 私钥文件路径: " + privateKeyFile.getAbsolutePath());
            System.out.println("[DEBUG] 公钥文件路径: " + publicKeyFile.getAbsolutePath());
            System.out.println("[DEBUG] 许可证文件路径: " + licenseFile.getAbsolutePath());
            System.out.println("[DEBUG] JAR公钥文件路径: " + jarPublicKey.getAbsolutePath());

        } catch (Exception e) {
            System.err.println("[ERROR] 初始化目录时发生错误: " + e.getMessage());
            e.printStackTrace();
        }
    }

    // ==================== 主方法 ====================

    /**
     * 程序入口点
     * @Param args 命令行参数(未使用)
     * @throws Exception 如果执行过程中发生任何错误
     */
    public static void main(String[] args) throws Exception {
        System.out.println("=========================================");
        System.out.println("    DBeaver Ultimate 许可证破解工具");
        System.out.println("=========================================");
        System.out.println("[INFO] 开始执行DBeaver许可证破解流程...");

        // ==================== 第一步:验证DBeaver安装环境 ====================
        System.out.println("\n[STEP 1] 验证DBeaver安装环境");

        // 检查DBeaver主目录是否存在
        File dbeaverHome = new File(DBEAVER_ECLIPSE_HOME);
        if (!dbeaverHome.exists()) {
            System.err.println("[ERROR] DBeaver安装目录不存在: " + DBEAVER_ECLIPSE_HOME);
            System.err.println("[SOLUTION] 请检查DBEAVER_ECLIPSE_HOME配置是否正确");
            return;
        }
        System.out.println("[SUCCESS] DBeaver安装目录验证通过");

        // 检查插件目录是否存在
        String dbeaverPluginDir = DBEAVER_ECLIPSE_HOME + File.separator + "plugins";
        File pluginDir = new File(dbeaverPluginDir);
        if (!pluginDir.exists()) {
            System.err.println("[ERROR] 插件目录不存在: " + dbeaverPluginDir);
            System.err.println("[SOLUTION] 请确认DBeaver安装完整");
            return;
        }
        System.out.println("[SUCCESS] 插件目录验证通过: " + dbeaverPluginDir);

        // ==================== 第三步:生成密钥和许可证 ====================
        System.out.println("\n[STEP 2] 生成密钥对和许可证");
        generateKeyLicenseAndPatch();

        // ==================== 完成 ====================
        System.out.println("\n=========================================");
        System.out.println("    执行完成!");
        System.out.println("=========================================");
        System.out.println("[NEXT STEP] 请启动DBeaver并输入生成的许可证");
    }

    /**
     * 生成RSA密钥对、创建许可证
     *
     * 核心功能流程:
     * 1. 生成2048位RSA密钥对
     * 2. 将公钥保存到文件
     * 3. 使用私钥加密生成许可证
     * @throws LMException 如果许可证生成过程出错
     * @throws IOException 如果文件操作失败
     */
    public static void generateKeyLicenseAndPatch() throws LMException, IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidAlgorithmParameterException {
        // 判断 RSA 密钥对是否已经存在,如果不存在,则生成
        if (!(privateKeyFile.exists() && publicKeyFile.exists())) {
            System.out.println("[INFO] 未检测到现成的RSA密钥对文件,开始生成 RSA 密钥对...");

            // 生成RSA密钥对
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");

            // 使用 RSAKeyGenParameterSpec 显式指定密钥长度 (2048) 和 公钥指数 (65537)
            // RSAKeyGenParameterSpec.F4 就是内置的常量 BigInteger.valueOf(65537)
            RSAKeyGenParameterSpec spec = new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4);
            keyPairGenerator.initialize(spec);
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            PrivateKey privateKey = keyPair.getPrivate();
            PublicKey publicKey = keyPair.getPublic();

            // 通过 writeFileToPath 将密钥对写入到 keys/ 中
            writeFileToPath(Base64.encode(publicKey.getEncoded()), publicKeyFile, "公钥", true);
            writeFileToPath(Base64.encode(privateKey.getEncoded()), privateKeyFile, "私钥", true);
        }

        // 如果 RSA 密钥对已经存在,则直接读取为 Key 使用
        System.out.println("[INFO] 检测到现成的RSA密钥对文件,开始读取密钥...");
        PublicKey publicKey = LMEncryption.generatePublicKey(Base64.decode(Files.readString(publicKeyFile.toPath()).trim()));
        PrivateKey privateKey = LMEncryption.generatePrivateKey(Base64.decode(Files.readString(privateKeyFile.toPath()).trim()));

        LMProduct TEST_PRODUCT = new LMProduct(PRODUCT_ID, "DB", "DBeaver Ultimate", "DBeaver Ultimate Edition", PRODUCT_VERSION, LMProductType.DESKTOP, new Date(), new String[0]);
        String licenseID = LMUtils.generateLicenseId(TEST_PRODUCT);
        // System.out.println("[INFO] 生成的 License ID: " + licenseID);
        LMLicense license = new LMLicense(licenseID, LMLicenseType.ULTIMATE, new Date(), new Date(), (Date) null, LMLicense.FLAG_UNLIMITED_SERVERS, PRODUCT_ID, PRODUCT_VERSION, OWNER_ID, OWNER_COMPANY, OWNER_NAME, OWNER_EMAIL);
        byte[] licenseData = license.getData();
        byte[] licenseEncrypted = LMEncryption.encrypt(licenseData, privateKey);

        PowerArgsGenerator powerArgsGenerator = new PowerArgsGenerator((RSAPublicKey) publicKey, jarPublicKey.getAbsolutePath());
        String patch = powerArgsGenerator.generatePowerArgs();
        System.out.println("\n--- 生成的 PowerArgs ---");
        System.out.println(patch);
        writeFileToPath(patch, new File(CRACK_HOME, "keys/power-args.txt"), "PowerArgs", true);

        String licenseContent = Base64.splitLines(Base64.encode(licenseEncrypted), 76);
        System.out.println("\n--- LICENSE ---");
        System.out.println(licenseContent);
        System.out.println("--- 处理完成,请打开软件使用以上授权码(已复制到剪切板) ---");
        copyToClipboard(licenseContent);
        writeFileToPath(licenseContent, licenseFile, "授权码", true);
    }

    /**
     * 写入文件内容(提供默认参数),默认为不覆盖
     *
     * @param content  待写入的内容
     * @param filePath 要写入的文件路径
     * @param tip      提示
     */
    public static void writeFileToPath(String content, File filePath, String tip) {
        // 通过方法重载实现默认值为 false
        writeFileToPath(content, filePath, tip, false);
    }

    /**
     * 写入文件内容,可选强制覆盖
     *
     * @param content   待写入的内容
     * @param filePath  要写入的文件路径
     *@param tip       提示
     * @param overwrite 是否强制覆盖(true:覆盖现有文件;false:若文件存在则不写入)
     */
    public static void writeFileToPath(String content, File filePath, String tip, boolean overwrite) {
        if (filePath == null || content == null){
            System.err.println("写入中止:文件路径或内容不能为空");
            return;
        }
        Path path = filePath.toPath();
        // 1. 处理文件已存在且未开启覆盖的情况
        if (!overwrite && Files.exists(path)) {
            System.out.printf("[%s内容] 写入跳过:文件已存在且未开启覆盖模式(%s)%n", tip, path);
            return;
        }
        // 2. 严格检查父目录是否存在:不存在则打印日志并中止
        Path parentDir = path.getParent();
        if (parentDir != null && !Files.exists(parentDir)) {
            System.err.printf("[%s内容] 写入中止:父目录不存在,已拒绝自动创建(%s)%n", tip, parentDir);
            return;
        }
        try {
            //3. 规范化处理:显式指定 UTF-8 字符集
            Files.write(path, content.getBytes(StandardCharsets.UTF_8));
            System.out.printf("[%s内容] 已成功写入文件:%s%n", tip, path);

        }catch (IOException e) {
            System.err.printf("[%s内容] 写入文件发生异常:%s%n", tip, path);
            e.printStackTrace();
        }
    }

    /**
     * 将指定字符串复制到剪切板
     *
     * @param text
     */
    public static void copyToClipboard(String text) {
        StringSelection stringSelection = new StringSelection(text);
        Clipboard clipboard = Toolkit.getDefaultToolkit()
                .getSystemClipboard();
        clipboard.setContents(stringSelection, null);
    }
}

PowerArgsGenerator.java

package com.puppetdevz;

import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class PowerArgsGenerator {

    private static final String POWER_ARGS_TEMPLATE = "EQUAL,65537,%s->65537,%s";

    private final RSAPublicKey currentPublicKey;

    private final String oldPublicKey;

    public PowerArgsGenerator(RSAPublicKey currentPublicKey, String oldPublicKey) {
        this.currentPublicKey = currentPublicKey;
        this.oldPublicKey = oldPublicKey;
    }

    /**
     * 从 PEM 文件中加载 RSA 公钥
     * 兼容多种 PEM 头尾格式:
     *   - 标准格式:-----BEGIN PUBLIC KEY----- / -----END PUBLIC KEY-----
     *   - 自定义格式:--- PUBLIC KEY --- 等
     */
    public static RSAPublicKey loadPublicKeyFromPem(Path pemPath)
            throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {

        String pemContent = Files.readString(pemPath);

        // 用正则去除所有形如 "-{2,5}\s*PUBLIC KEY\s*-{2,5}" 的头尾标记行
        String base64Key = pemContent
                .replaceAll("-{2,5}\\s*(BEGIN\\s+)?PUBLIC\\s+KEY\\s*-{2,5}", "")
                .replaceAll("\\s+", "");

        byte[] keyBytes = Base64.getDecoder().decode(base64Key);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");

        return (RSAPublicKey) keyFactory.generatePublic(keySpec);
    }

    /**
     * 生成 power[Args] 字符串
     */
    public String generatePowerArgs()
            throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {

        RSAPublicKey oldKey = loadPublicKeyFromPem(Path.of(oldPublicKey));
        BigInteger oldN = oldKey.getModulus();
        BigInteger newN = currentPublicKey.getModulus();

        return String.format(POWER_ARGS_TEMPLATE, oldN.toString(), newN.toString());
    }
}

修改 dbeaver.ini

/Applications/DBeaverUltimate.app/Contents/Eclipse/dbeaver.ini

-startup
....中间省略
../Eclipse/plugins/org.eclipse.equinox.launcher.cocoa.macosx.aarch64_1.2.1400.v20250801-0854
-vm /Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home/bin/java
-vmargs
....中间省略
-Dlm.debug.mode=true
-javaagent:/path-to-ja-netfilter/ja-netfilter.jar

核心只有三行配置:

-vm /Library/Java/JavaVirtualMachines/openjdk-21.jdk/Contents/Home/bin/java
-Dlm.debug.mode=true
-javaagent:/path-to-ja-netfilter/ja-netfilter.jar

一般遇到启动不了的,原因可能有以下三个:

  • ja-netfilter 路径写错了
  • vm 没改或路径写错了(之所以要改 vm,是因为 DBeaver 自带的 vm 为 阉割版,缺少部分 ja-netfilter 依赖)
  • 有多个 vm

修改power.conf和updns.conf

将上面生成的 Power Patch 粘贴到 power.conf 末尾

在 updns.conf 中添加网络解析拦截(为什么不用 dns.conf 或 url.conf 呢?好像是因为 ohttp3 被 dns 和 url 插件捕获)

调试小tips

直接在终端启动 DBeaver 可查看程序运行过程日志,例如:/Applications/DBeaverUltimate.app/Contents/MacOS/dbeaver

参考文献

102 个赞

佬友太强了 :+1:,感谢佬友分享 :folded_hands:

2 个赞

感谢佬友分享,写的很详细 :heart:

3 个赞

看看{:4_90:}

1 个赞

我感觉免费的就够用了

感谢分享

感谢分享

感谢大佬。

1 个赞

感谢大佬!感谢引用

1 个赞

感谢分享,学习了

1 个赞

果然除了我全是大佬啊

1 个赞

大佬,请问dbeaver有没有像navicat一样直接导出数据库结构和数据到sql文件的功能?用navicat导出一个sql文件,然后导入的时候直接运行sql就完成数据库迁移了。
我之前看datagrip还要用dump之类的工具,而且是每个表一个sql文件,感觉很不方便。
dbeaver用的不多,好像没有看到直接导出数据库到sql文件的地方 :tieba_087:

1 个赞

very good!

1 个赞

DBeaver不是开源的吗

大佬太强了

1 个赞

是的,之前也一直在用的免费版,但是发现部分数据库用不了,例如MongoDB

1 个赞

部分数据库需要证书激活才能用

有的,不过DBeaver分得比较细,可以先导出 DDL,再导出数据。

导出 DDL

选中需要数据表以导出数据

佬,这个updns插件从哪搞的,我没找到啊!

updns.jar.zip (23.7 KB)

需要自行在config目录下新增一个同名的配置文件:updns.conf

1 个赞