Skip to content

Commit

Permalink
Encrypt existing asar file (closes #42)
Browse files Browse the repository at this point in the history
  • Loading branch information
sleeyax committed Apr 19, 2024
1 parent 6e4897e commit c0890a8
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 48 deletions.
31 changes: 12 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,20 @@ Usage:

```javascript
const asarmor = require('asarmor');
const { encrypt } = require('asarmor/encryption');

(async () => {
// Encrypt the contents of the asar archive.
await encrypt({
src: './dist', // source or transpiled code to encrypt
dst: './app.asar', // target asar file
await asarmor.encrypt({
src: './app.asar', // target asar file to encrypt
dst: './encrypted.asar', // output asar file
});

// Read & parse the (optionally encrypted) asar file.
// This can take a while depending on the size of your file.
const archive = await asarmor.open('app.asar');
const archive = await asarmor.open('encrypted.asar');

// Create a backup, which can be restored at any point in time through CLI or code.
await archive.createBackup({backupPath: '~/Documents/backups/app.asar.backup'});
await archive.createBackup({backupPath: '~/Documents/backups/encrypted.asar.backup'});

// Apply customized bloat patch.
// The bloat patch by itself will write randomness to disk on extraction attempt.
Expand Down Expand Up @@ -99,22 +98,16 @@ Steps:
```diff
exports.default = async ({ appOutDir, packager }) => {
try {
+ const asarPath = join(packager.getResourcesDir(appOutDir), 'app.asar');
+
+ // encrypt file contents first
+ const src = join(packager.info.projectDir, 'release', 'app');
+ const dst = asarPath;
+ console.log(`asarmor encrypting contents of ${src} to ${dst}`);
+ const asarPath = join(packager.getResourcesDir(appOutDir), 'app.asar');
+ console.log(`asarmor encrypting JS contents of ${asarPath} to ${dst}`);
+ await encrypt({
+ // path to your source code (e.g. src, build or dist)
+ src,
+ // destination asar file to write to
+ dst,
+ // path to the input asar file
+ src: asarPath,
+ // path to the destination asar file
+ dst: asarPath,
+ // path to the encryption key file; asarmor should generate a new one every time it's installed as a dev-dependency.
+ keyFilePath: join(__dirname, '..', 'node_modules', 'asarmor', 'src', 'encryption', 'key.txt'),
+ key: join(__dirname, '..', 'node_modules', 'asarmor', 'src', 'encryption', 'key.txt'),
+ });
+
+ // then patch the header
- const asarPath = join(packager.getResourcesDir(appOutDir), 'app.asar');
console.log(`asarmor applying patches to ${asarPath}`);
const archive = await asarmor.open(asarPath);
Expand Down
19 changes: 8 additions & 11 deletions bin/asarmor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ const program = new Command()
'fill the drive with useless data on extraction attempt',
(value) => parseNumber(value, false)
)
.option('-e, --encrypt <src>', 'encrypt file contents')
.option('-k, --key <file path>', 'key file to use for encryption')
.option(
'-e, --encryption [key.txt file path or raw string]',
'encrypt the archive'
)
.addHelpText(
'after',
`
Expand All @@ -49,16 +51,11 @@ if (!options.archive || !options.output) {
}

async function main() {
if (options.encrypt) {
if (!options.key) {
program.help();
process.exit();
}

if (options.encryption) {
await encrypt({
src: options.encrypt,
dst: options.archive,
keyFilePath: options.key,
src: options.archive,
dst: options.output,
key: options.encryption === true ? undefined : options.encryption,
});
}

Expand Down
96 changes: 96 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@
"@electron/asar": "^3.2.9",
"chromium-pickle-js": "^0.2.0",
"commander": "^12.0.0",
"fs-extra": "^11.2.0",
"node-addon-api": "^8.0.0",
"node-gyp": "^10.1.0"
},
"devDependencies": {
"@types/fs-extra": "^11.0.4",
"@types/jest": "^29.5.12",
"@types/node": "^20.12.7",
"@typescript-eslint/eslint-plugin": "^7.7.0",
Expand Down
2 changes: 1 addition & 1 deletion scripts/keygen.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Generates a new random AES key to encrypt the asar archive files with if, it doesn't exist yet.
* Generates a new random AES key to encrypt the asar archive files with, if it doesn't exist yet.
*/

const fs = require('fs');
Expand Down
34 changes: 17 additions & 17 deletions src/encryption/encryption.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,27 @@
import crypto from 'crypto';
import { join, extname } from 'path';
import { createPackageWithOptions } from '@electron/asar';
import { createPackageWithOptions, extractAll } from '@electron/asar';
import { fromHex } from './helpers';
import { readFile } from 'fs/promises';
import { pathExists, remove } from 'fs-extra';

export type EncryptionOptions = {
/**
* Source code to package into an asar archive.
* File path to an input asar.
*
* E.g `src`
* @example `app.asar`.
*/
src: string;

/**
* Destination file asar file path.
* File path to the output asar.
*
* E.g `app.asar`
* @example `encrypted.asar`.
*/
dst: string;

/**
* File path to a hex-encoded encryption key.
*/
keyFilePath?: string;

/**
* Encryption key in plaintext.
* File path to a hex-encoded encryption key or the encryption key in plaintext.
*/
key?: string;
};
Expand All @@ -37,16 +33,18 @@ export type EncryptionOptions = {
* Encrypts and packages all files into an asar archive.
*/
export async function encrypt({
keyFilePath = join(__dirname, 'key.txt'),
key: keyPlaintext,
key: keyOrFile = join(__dirname, 'key.txt'),
src,
dst,
}: EncryptionOptions) {
const key = keyPlaintext
? Buffer.from(keyPlaintext)
: Buffer.from(fromHex(await readFile(keyFilePath)));
const key = (await pathExists(keyOrFile))
? Buffer.from(fromHex(await readFile(keyOrFile)))
: Buffer.from(keyOrFile.includes(',') ? fromHex(keyOrFile) : keyOrFile);
const extractedPath = `${src}.extracted`;

return createPackageWithOptions(src, dst, {
extractAll(src, extractedPath);

await createPackageWithOptions(extractedPath, dst, {
unpack: '*.node', // C++ modules should not be packed
transform(filename) {
if (extname(filename) == '.js') {
Expand Down Expand Up @@ -75,4 +73,6 @@ export async function encrypt({
}
},
});

await remove(extractedPath);
}

0 comments on commit c0890a8

Please sign in to comment.