npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@lumiastream/electron-armor

v2.1.0-beta.12

Published

Protect asar archive file from extraction

Downloads

71

Readme

asarmor

CLI tool and library that can patch and encrypt your asar file to protect it from extraction (e.g by executing asar extract). This is not bulletproof, but can be useful as a first level of protection.

installation

Install as local library: $ npm install --save-dev asarmor

Install as global CLI app: $ npm install -g asarmor

usage

CLI

$ asarmor --help

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]       add huge random files to disk on extraction attempt
  -t, --trashify [junkfiles...]  add fake files to the archive
  -e, --encrypt <src>            encrypt file contents
  -ek, --key <file path>         key file to use for encryption
  -h, --help                     display help for command

Examples:
  $ asarmor -a app.asar -o asarmor.asar --bloat 1000
  $ asarmor -a app.asar -o asarmor.asar --trashify bee-movie.txt foo.js bar.ts
  $ asarmor -a app.asar -o asarmor.asar --trashify --backup
  $ asarmor -a app.asar --restore

library

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
  });

  // 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');

  // 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'});

  // Apply customized trash patch.
  // The trash patch by itself will make sure `asar extract` fails.
  archive.patch(asarmor.createTrashPatch({
    filenames: ['foo', 'bar'],
    beforeWrite: (filename) => {
      const extensions = ['js', 'ts', 'tsx', 'txt'];
      const extension = extensions[Math.floor(Math.random() * extensions.length)];
      return filename + '.' + extension;
    }
  }));

  // 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);
})();

advanced

const asarmor = require('asarmor');

(async () => {
  const archive = await asarmor.open('app.asar');

  // Apply a fully customized patch.
  // Play around with the different values to see what works best for you.
  archive.patch({
    header: {
      files: {
        'foo.js': {offset: 0, size: -999},
        'bar.js': {offset: -123, size: 1337},
      }
    },
  });

  // Write result back to file.
  await archive.write('protected.asar');
})();

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 now finally supports file content encryption. 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 higly recommend you to check out the electron-asar-encrypt-demo repository.

There's a few more steps involved to make this work, though. 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');
+   
+   // encrypt file contents first
+   const src = join(packager.info.projectDir, 'release', 'app');
+   const dst = asarPath;
+   console.log(`asarmor encrypting contents of ${src} 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 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'),
+   });
+
+   // 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);
    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. Load any hooks at the start of the main process file (optional):
// main.ts
import { hookNodeModulesAsar } from 'asarmor/src/encryption/hooks';

// load hooks at the start of the file
hookNodeModulesAsar(); // 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 paramater.
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 utilise the asar format (unless the implementation differs drastically for some reason, which is out of my control).

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!

related projects