文档
/
实用方案
/

从后端获取字节码数组密钥

从后端获取字节码数组密钥

Pro
v7.3.0+

让 VM 字节码解密密钥不出现在客户端:在加载时用异步密钥获取器从您的后端拉取它,并置于鉴权之后,从而让被窃取的包无法运行。

问题

vmBytecodeArrayEncoding 用一个密钥对 VM 字节码加密。如果该密钥随包 一起分发,任何拿到该文件的人就掌握了解密它所需的一切。vmBytecodeArrayEncodingKeyGetter 让 密钥可以存放在别处,并在运行时产生 —— 但同步获取器只能读取客户端上已经存在的 内容(一个全局变量、一个 cookie、localStorage)。要从您的服务器获取密钥 —— 以便您能将其置于 鉴权之后并可撤销 —— 获取器就必须是 异步的

解决方案

启用 vmAsyncExecutor(v7.3.0+)。有了异步执行器,密钥获取器就可以 返回一个 Promise,因此它能在 VM 运行之前从您的后端 fetch 密钥。三个选项协同工作:

  • vmBytecodeArrayEncoding: true —— 对字节码数组加密。
  • vmBytecodeArrayEncodingKey —— 编译期使用的密钥(保存在您的服务器上,而非包内)。
  • vmBytecodeArrayEncodingKeyGetter —— 一个在运行时返回同一密钥的 JS 表达式。在启用 vmAsyncExecutor 时它可以返回 Promise;不启用时获取器必须同步返回密钥。

客户端混淆配置

JavaScriptObfuscator.obfuscate(sourceCode, {
    vmObfuscation: true,
    vmAsyncExecutor: true,
    vmBytecodeArrayEncoding: true,
    vmBytecodeArrayEncodingKey: process.env.VM_KEY,            // e.g. 'mySecretKey123'
    vmBytecodeArrayEncodingKeyGetter:
        'fetch("/api/vm-key", { credentials: "include" }).then((res) => res.text())'
});

获取器表达式会被原样嵌入并在浏览器中求值。请让编译期的 vmBytecodeArrayEncodingKey 不出现在您的客户端代码仓库中 —— 在构建时从环境变量或密钥注入它, 并从下面的端点提供完全相同的字符串。

两个密钥如何协作

您的密钥从不单独使用 —— 在两侧它都会与混淆器控制的一个内部密钥混合:

  • 编译期。 vmBytecodeArrayEncodingKey 会与混淆器推导出的一个内部密钥结合,然后用 由此得到的混合密钥对字节码数组进行编码。
  • 运行时。 您的 vmBytecodeArrayEncodingKeyGetter 解析出的值 —— 由您的服务器返回 —— 会与 同一个内部密钥(在客户端从各种运行时因素中重建)结合,以解码字节码。

由于两侧都会将您的密钥与内部密钥混合,获取器必须解析为与您作为 vmBytecodeArrayEncodingKey 传入的 完全相同的字符串。任一部分单独都不够:没有内部密钥,您的密钥无法解码 字节码;而没有您的密钥,内部密钥也毫无用处 —— 这正是为什么只向已鉴权的调用方提供您的密钥 能让被窃取的包无法运行。

服务端

该端点根据您的应用所信任的任何依据来决定返回哪个密钥 —— 有效会话、预期的 OriginReferer、授权校验,等等。妙处在于:与其拒绝不受信任的调用方,不如返回一个 错误的 密钥。字节码随后会解码成乱码,受保护的代码便自行失败,这比一个 明显的 401(它会直接告诉攻击者该绕过什么)更为隐蔽。

// Express example — the exact checks depend on your app
app.get('/api/vm-key', (req, res) => {
    const origin = req.get('origin');
    const trusted =
        req.session?.user &&                       // a valid session, and
        origin === 'https://app.example.com';      // the expected production origin

    res.type('text/plain').send(
        // Real key for valid users; a decoy for everyone else
        // (no session, or a localhost / unexpected origin).
        trusted ? process.env.VM_KEY : process.env.VM_DECOY_KEY
    );
});