Đưa khóa mã hóa mảng bytecode ra bên ngoài
Cung cấp khóa mã hóa bytecode VM của riêng bạn bằng vmBytecodeArrayEncodingKey và trả lại nó tại thời điểm chạy thông qua một key getter — không nằm trong bundle, được đọc từ bộ nhớ phía client, hoặc được lấy từ backend của bạn.
Các tùy chọn này làm gì
vmBytecodeArrayEncoding mã hóa mảng bytecode của VM để nó không nằm trong đầu ra
dưới dạng văn bản thuần. Theo mặc định, khóa mã hóa được suy ra từ môi trường và được tái tạo trên client, nên bạn không
bao giờ phải xử lý nó. Điều đó thuận tiện, nhưng nguyên liệu khóa vẫn nằm trong bundle.
Hai tùy chọn cho phép bạn đưa khóa ra khỏi bundle và tự kiểm soát nó:
vmBytecodeArrayEncodingKey— khóa mà bạn cung cấp tại thời điểm biên dịch. Khi được đặt, nó được dùng thay cho khóa mặc định suy ra từ môi trường, và nó không được nhúng vào đầu ra đã làm rối.vmBytecodeArrayEncodingKeyGetter— một biểu thức JavaScript trả về chính khóa đó tại thời điểm chạy. Nó được nhúng nguyên văn và được đánh giá trong trình duyệt khi mã đã làm rối được tải.
Điểm mấu chốt là sự tách biệt: vì khóa không nằm trong mã, một lần quét thuần tĩnh của bundle không thể khôi phục nó. Nó vẫn phải hiện diện tại thời điểm chạy để mã hoạt động, nên nó không thực sự bí mật — nhưng bạn quyết định nó đến từ đâu và ai được thấy nó.
Hai khóa kết hợp như thế nào
Khóa của bạn không bao giờ được dùng một mình — ở cả hai phía, nó được trộn với một khóa nội bộ mà trình làm rối kiểm soát:
- Thời điểm biên dịch.
vmBytecodeArrayEncodingKeyđược kết hợp với một khóa nội bộ mà trình làm rối suy ra, và mảng bytecode được mã hóa bằng khóa trộn thu được. - Thời điểm chạy. Giá trị mà
vmBytecodeArrayEncodingKeyGettercủa bạn phân giải ra được kết hợp với cùng khóa nội bộ đó, được tái tạo trên client từ nhiều yếu tố tại thời điểm chạy, để giải mã bytecode.
Vì cả hai phía đều trộn khóa của bạn với khóa nội bộ, getter phải phân giải ra đúng cùng một chuỗi mà bạn đã truyền
vào làm vmBytecodeArrayEncodingKey. Không phần nào tự nó đủ: khóa của bạn mà không có khóa nội bộ thì không thể giải mã
bytecode, và khóa nội bộ thì vô dụng nếu không có khóa của bạn — đó là lý do việc kiểm soát ai nhận được khóa của bạn mới
thực sự là điều bảo vệ mã.
Cung cấp khóa tại thời điểm chạy
Theo mặc định, getter là đồng bộ: biểu thức phải trả về khóa ngay lập tức khi mã đã làm rối được tải. Hãy đọc nó từ
bất kỳ nguồn nào đã có sẵn trên client — một cookie, localStorage, một biến toàn cục, hoặc một phần tử DOM được máy chủ
chèn vào.
JavaScriptObfuscator.obfuscate(sourceCode, {
vmObfuscation: true,
vmBytecodeArrayEncoding: true,
vmBytecodeArrayEncodingKey: process.env.VM_KEY, // e.g. 'mySecretKey123'
vmBytecodeArrayEncodingKeyGetter: "window.__VM_KEY__" // returns the key at runtime
});
Khóa phải tồn tại trước khi mã đã làm rối chạy:
// Set by a different script, a server-injected inline script, etc.
window.__VM_KEY__ = 'mySecretKey123';
Các nguồn đồng bộ khác cũng hoạt động theo cách tương tự — hãy chọn nguồn nào mà ứng dụng của bạn đã điền sẵn:
// From a cookie
vmBytecodeArrayEncodingKeyGetter: "document.cookie.match(/vmKey=([^;]+)/)?.[1]"
// From localStorage
vmBytecodeArrayEncodingKeyGetter: "localStorage.getItem('vmKey')"
// From a server-injected meta tag
vmBytecodeArrayEncodingKeyGetter: "document.querySelector('meta[name=\"vm-key\"]').content"
// From a nested object
vmBytecodeArrayEncodingKeyGetter: "window.config.encryption.key"
Lấy khóa từ backend của bạn (bất đồng bộ)
Yêu cầu vmAsyncExecutor · v7.3.0+Một getter đồng bộ chỉ có thể đọc những gì đã có sẵn trên client. Để lấy khóa từ máy chủ của bạn — nhờ đó bạn có thể
đặt nó sau lớp xác thực và thu hồi nó — getter phải là bất đồng bộ, và điều đó đòi hỏi
vmAsyncExecutor. Khi trình thực thi bất đồng bộ được bật, getter có thể trả về
một Promise, và VM sẽ chờ nó trước khi chạy.
JavaScriptObfuscator.obfuscate(sourceCode, {
vmObfuscation: true,
vmAsyncExecutor: true,
vmBytecodeArrayEncoding: true,
vmBytecodeArrayEncodingKey: process.env.VM_KEY, // kept on your server, not in the bundle
vmBytecodeArrayEncodingKeyGetter:
'fetch("/api/vm-key", { credentials: "include" }).then((res) => res.text())'
});
Trên máy chủ, hãy quyết định trả về khóa nào dựa trên bất cứ điều gì mà ứng dụng của bạn tin cậy — một phiên hợp lệ, một
Origin hoặc Referer như mong đợi, một lần kiểm tra giấy phép, v.v. Điểm bất ngờ: thay vì từ chối những bên gọi không
đáng tin, hãy trả về một khóa sai. Khi đó bytecode sẽ giải mã ra rác và mã được bảo vệ tự nó thất bại, điều này kín
đáo hơn một mã 401 lộ liễu vốn cho kẻ tấn công biết chính xác cần vượt qua cái gì.
// 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
);
});
Hãy phục vụ từ endpoint này đúng cùng một chuỗi mà bạn đã truyền vào làm vmBytecodeArrayEncodingKey tại thời điểm build.
Một bản sao của bundle chạy bên ngoài môi trường của bạn sẽ nhận được khóa mồi nhử, giải mã ra chẳng gì cả, và trở nên vô
hại.
Khi khóa không khớp
Mã đã làm rối chỉ hoạt động khi getter trả về đúng chính xác cùng khóa đã dùng trong quá trình làm rối. Nếu các khóa
khác nhau — hoặc getter trả về undefined, null, hoặc một chuỗi rỗng — việc giải mã tạo ra một keystream sai và mã thất
bại tại thời điểm chạy với đầu ra rác hoặc một lỗi runtime thông thường.
Cố ý không có một thông báo lỗi riêng biệt, đặc thù cho khóa: một khóa thất bại không thể phân biệt với bất kỳ sự cố runtime nào khác. Vì vậy, khi một bundle được VM bảo vệ chỉ ném lỗi khi tùy chọn này được bật, hãy kiểm tra đường dẫn khóa trước tiên — rằng getter phân giải được trên trang, trả về một chuỗi không rỗng, và trả về cùng giá trị mà bạn đã build.
