asarmor
TypeScript icon, indicating that this package has built-in type declarations

3.0.0 • Public • Published

asarmor

Protects asar files from extraction (with asar extract). The methods provided by asarmor are not bulletproof, but can be useful as a first level of protection.

Usage

You can use asarmor as a CLI tool or as a library in your project. The CLI tool is useful for quick and easy protection of your asar files or for trying out asarmor. The library is useful for more advanced use cases, such as integrating asarmor into your Electron project.

CLI

Installation:

npm install --save-dev asarmor

Usage:

Usage: asarmor [options]

Options:
  -V, --version             output the version number
  -a, --archive <archive>   input asar file (required)
  -o, --output <output>     output asar file (required)
  -b, --backup              create backup
  -r, --restore             restore backup
  -bl, --bloat [gigabytes]  fill the drive with useless data on extraction attempt
  -e, --encryption          encrypt the JavaScript files stored in the archive
  -h, --help                display help for command

Examples:
  $ asarmor -a app.asar -o asarmor.asar --backup --bloat 1000
  $ asarmor -a plaintext.asar -o encrypted.asar --encryption

Library

Installation:

npm install -g asarmor

Usage:

const asarmor = require('asarmor');

(async () => {
  // Encrypt the JavaScript file contents stored withing the 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('encrypted.asar');

  // Create a backup, which can be restored at any point in time through CLI or code.
  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.
  archive.patch(asarmor.createBloatPatch(50)); // adds 50 GB of bloat in total

  // Write changes back to disk.
  const outputPath = await archive.write('app.asar');
  console.log('successfully wrote changes to ' + outputPath);
})();

electron-builder

You can easily include asarmor in your packaging process using an afterPack hook:

const asarmor = require('asarmor');
const { join } = require("path");

exports.default = async ({ appOutDir, packager }) => {
  try {
    const asarPath = join(packager.getResourcesDir(appOutDir), 'app.asar');
    console.log(`asarmor applying patches to ${asarPath}`);
    const archive = await asarmor.open(asarPath);
    archive.patch(); // apply default patches
    await archive.write(asarPath);
  } catch (err) {
    console.error(err);
  }
};

Encryption

Asarmor can encrypt the contents of your asar file. No Electron recompilation required! Huge thanks to toyobayashi's wonderful electron-asar-encrypt-demo for making this possible. I won't be going into too many details on how this works exactly. If you're interested in the details I highly recommend you check out the electron-asar-encrypt-demo repository.

There's a few more steps involved to make this work. See example/electron if you'd like to skip ahead to the code.

Steps:

  1. Update afterPack.js:
exports.default = async ({ appOutDir, packager }) => {
  try {
+   const asarPath = join(packager.getResourcesDir(appOutDir), 'app.asar');   
+   console.log(`asarmor is encrypting all JavaScript files stored in ${asarPath}`);
+   await encrypt({
+     // path to the input asar file
+     src: asarPath,
+     // path to the output asar file
+     dst: asarPath,
+   });
-   const asarPath = join(packager.getResourcesDir(appOutDir), 'app.asar');
    console.log(`asarmor applying patches to ${asarPath}`);
    const archive = await asarmor.open(asarPath);
    archive.patch(); // apply default patches
    await archive.write(asarPath);
  } catch (err) {
    console.error(err);
  }
};
  1. Create beforePack.js:
const { join } = require('path');
const { copyFile } = require('fs/promises');

exports.default = async (context) => {
  try {
    console.log('copying native dependencies');

    const release = join(__dirname, '..', 'node_modules', 'asarmor', 'Release');

    // copy main.node from asarmor to our dist/build/release folder; this will become the entrypoint later on.
    await copyFile(
      join(release, 'main.node'),
      join(
        context.packager.info.projectDir,
        'release',
        'app',
        'dist',
        'main',
        'main.node'
      )
    );

    // copy renderer.node to our dist/build/release folder; the render process will be bootstrapped from the main process later on.
    await copyFile(
      join(release, 'renderer.node'),
      join(
        context.packager.info.projectDir,
        'release',
        'app',
        'dist',
        'renderer',
        'renderer.node'
      )
    );
  } catch (err) {
    console.error(err);
  }
};

Don't forget to update package.json as well:

"afterPack": "./afterPack.js",
+ "beforePack": "./beforePack.js",
  1. Update your project's package.json entrypoint:
+ "main": "./dist/main/main.node",
- "main": "./dist/main/main.js",
  1. Optional: load configuration hooks at the start of the main process file:
// main.ts
import { allowUnencrypted } from 'asarmor';

allowUnencrypted(['node_modules']); // enables resolution of non-encrypted dependencies from `node_modules.asar`
  1. Update your BrowserWindow.webPreferences configuration settings:
const mainWindow = new BrowserWindow({
    // ...
    webPreferences: {
      nodeIntegration: true,   // MUST BE ENABLED
      contextIsolation: false, // MUST BE DISABLED
    },
  });
  1. Bootstrap the render process:
await mainWindow.webContents.executeJavaScript(`!function () {
  require('../renderer/renderer.node');
  require('../renderer/renderer.js');
}()`);
  1. Export a default function in the main process, accepting the decryption key as a parameter.
module.exports = function bootstrap(k: Uint8Array) {
  // sanity check
  if (!Array.isArray(k) || k.length === 0) {
    throw new Error('Failed to bootstrap application.');
  }

  // key should be valid at this point, but you can access it here to perform additional checks.
  console.log('decryption key: ' + k);

  // start the app
  if (!process.env.ELECTRON_RUN_AS_NODE) {
    app
      .whenReady()
      .then(() => {
        createWindow();
        app.on('activate', () => {
          if (mainWindow === null) createWindow();
        });
      })
      .catch(console.log);
  } else {
    console.error('failed to bootstrap main process');
  }
};

Examples

See examples for detailed code examples.

FAQ

Do protections affect my (electron) app performance?

It depends. If you have a huge archive and applied encryption, then yes. Otherwise, electron should still be able read your asar file at the same speed as if nothing changed. The same should be true for other frameworks that utilize the asar format (unless the implementation differs drastically for some reason, which is out of my control).

Sponsors


Maintenance of this project is made possible by all the lovely contributors and sponsors. If you'd like to sponsor this project and have your avatar or company logo appear in this section, click here. 💖

Support

Found a bug or have a question? Open an issue if it doesn't exist yet. Pull Requests are welcome, but please open an issue first if you're adding major changes!

Credits

A special thanks to the following projects for making this project possible:

Package Sidebar

Install

npm i asarmor

Weekly Downloads

1,226

Version

3.0.0

License

MIT

Unpacked Size

94.8 kB

Total Files

48

Last publish

Collaborators

  • sleeyax