diff --git a/.gitignore b/.gitignore
index 5100467..a10bedf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,5 @@
node_modules
.staticrypt.json
.env
+encrypted/
+!example/encrypted/
diff --git a/MIGRATING.md b/MIGRATING.md
new file mode 100644
index 0000000..6694228
--- /dev/null
+++ b/MIGRATING.md
@@ -0,0 +1,34 @@
+# Migration guide
+
+## From 2.x to 3.x
+
+StatiCrypt 3.x brings a number of improvements: strong default security with WebCrypto, cleaner CLI options and a much simpler `password_template`. This has been done while preserving auto-decrypt "share" links and remember-me functionality: if you used those with StatiCrypt 2.x, your links will still work with 3.x and you'll still be logged in.
+
+There are a few breaking changes, but they should be easy to fix. If you have any trouble, feel free to open an issue.
+
+### Breaking changes
+
+#### The CLI
+
+When encrypting `secret.html`, the CLI will now create a folder with your encrypted file `encrypted/secret.html`. It will not create a `secret_encrypted.html` file anymore.
+
+Passwords shorter than 14 characters used to trigger a warning, now they trigger a blocking promp ("Do you want to use that password [yn]"). Add `--short` to hide that prompt.
+
+The options and parameters have been changed:
+- all template related options have been renamed to `--template-*`: pick your file with `--template`, set title with `--template-title`, etc.
+- the password is now an optional argument: set with `-p `, or leave blank to be prompted for it.
+- many other options have been renamed, refer to the help (`--help`) or documentation for the full reference.
+
+#### The password template
+
+If you don't use a custom password template, you don't need to do anything.
+
+If you do, you need to update your template. To do so:
+- get `lib/password_template.html`
+- replace the javascript part from this file in your custom template (the new template is logic is much simpler)
+- update the injected variables in your template (notice we use new template tags, they now are `/*[|variable|]*/0` instead of `{variable}`):
+ - `{title}` => `/*[|template_title|]*/0`
+ - `{instructions}` => `/*[|template_instructions|]*/0`
+ - `{remember_me}` => `/*[|template_remember|]*/0`
+ - `{passphrase_placeholder}` => `/*[|template_placeholder|]*/0`
+ - `{decrypt_button}` => `/*[|template_button|]*/0`
diff --git a/README.md b/README.md
index 20140d0..9e5f27d 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
# StatiCrypt
-StatiCrypt uses AES-256 to encrypt your HTML file with your long password and return a static page including a password prompt and the javascript decryption logic that you can safely upload anywhere (see [what the page looks like](https://robinmoisson.github.io/staticrypt/example/example_encrypted.html)).
+StatiCrypt uses AES-256 and WebCrypto to encrypt your HTML file with your long password and return a static page including a password prompt and the javascript decryption logic that you can safely upload anywhere (see [what the page looks like](https://robinmoisson.github.io/staticrypt/example/encrypted/example.html)).
This means you can **password protect the content of your _public_ static HTML file, without any back-end** - serving it over Netlify, GitHub pages, etc. (see the detail of [how it works](#how-staticrypt-works)).
@@ -10,6 +10,8 @@ You can encrypt a file online in your browser (client side) at https://robinmois
## CLI
+**Migration:** v3 brings many improvements, a clearer CLI and simpler `password_template` over v2. See the [migration guide from v2 to v3](MIGRATING.md). v3 uses WebCrypto which is only available in HTTPS or localhost contexts, so if you need to use it in HTTP you'll need to use v2.
+
### Installation
Staticrypt is available through npm as a CLI, install with
@@ -22,96 +24,84 @@ You can then run it with `npx staticrypt ...`. You can also install globally wit
### Examples
-> If you're viewing your file over HTTPS or localhost, you should use the `--engine webcrypto` flag to use the WebCrypto engine, which is more secure here. Otherwise the CryptoJS engine will be used.
->
> These examples will create a `.staticrypt.json` file in the current directory, see the FAQ as to why. You can prevent it by setting the `--config` flag to "false".
-**Encrypt a file:** Encrypt `test.html` and create a `test_encrypted.html` file (add `-o my_encrypted_file.html` to change the name of the output file):
+**Encrypt a file:** encrypt `test.html` and create a `encrypted/test.html` file (use `-d my_directory` to change the output directory):
```bash
-staticrypt test.html MY_LONG_PASSWORD --engine webcrypto
+staticrypt test.html -p MY_LONG_PASSWORD
```
**Encrypt a file with the password in an environment variable:** set your long password in the `STATICRYPT_PASSWORD` environment variable ([`.env` files](https://www.npmjs.com/package/dotenv#usage) are supported):
```bash
# the password is in the STATICRYPT_PASSWORD env variable
-staticrypt test.html --engine webcrypto
+staticrypt test.html
```
**Encrypt a file and get a shareable link containing the hashed password** - you can include your file URL or leave blank:
```bash
# you can also pass '--share' without specifying the URL to get the `#staticrypt_pwd=...`
-staticrypt test.html MY_LONG_PASSWORD --share https://example.com/test_encrypted.html --engine webcrypto
+staticrypt test.html MY_LONG_PASSWORD --share https://example.com/test_encrypted.html
# => https://example.com/test_encrypted.html#staticrypt_pwd=5bfbf1343c7257cd7be23ecd74bb37fa2c76d041042654f358b6255baeab898f
```
-**Encrypt all html files in a directory** and replace them with encrypted versions (`{}` will be replaced with each file name by the `find` command - if you wanted to move the encrypted files to an `encrypted/` directory, you could use `-o encrypted/{}`):
-
-```bash
-find . -type f -name "*.html" -exec staticrypt {} MY_LONG_PASSWORD -o {} --engine webcrypto \;
-```
-
-**Encrypt all html files in a directory except** the ones ending in `_encrypted.html`:
+**Encrypt all html files from a directory** and put them in a `encrypted/` directory:
```bash
-find . -type f -name "*.html" -not -name "*_encrypted.html" -exec staticrypt {} MY_LONG_PASSWORD --engine webcrypto \;
+find . -type f -name "*.html" -exec staticrypt {} MY_LONG_PASSWORD \;
```
### CLI Reference
The password argument is optional if `STATICRYPT_PASSWORD` is set in the environment or `.env` file.
- Usage: staticrypt [] [options]
+ Usage: staticrypt [options]
Options:
- --help Show help [boolean]
- --version Show version number [boolean]
- -c, --config Path to the config file. Set to "false" to
- disable. [string] [default: ".staticrypt.json"]
- --decrypt-button Label to use for the decrypt button. Default:
- "DECRYPT". [string] [default: "DECRYPT"]
- -e, --embed Whether or not to embed crypto-js in the page
- (or use an external CDN).
- [boolean] [default: true]
- --engine The crypto engine to use. WebCrypto uses 600k
- iterations and is more secure, CryptoJS 15k.
- Possible values: 'cryptojs', 'webcrypto'.
- [string] [default: "cryptojs"]
- -f, --file-template Path to custom HTML template with password
- prompt.
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ -c, --config Path to the config file. Set to "false" to
+ disable. [string] [default: ".staticrypt.json"]
+ -d, --directory Name of the directory where the encrypted files
+ will be saved. [string] [default: "encrypted/"]
+ -p, --password The password to encrypt your file with. Leave
+ empty to be prompted for it. If
+ STATICRYPT_PASSWORD is set in the env, we'll use
+ that instead. [string] [default: null]
+ --remember Expiration in days of the "Remember me" checkbox
+ that will save the (salted + hashed) password in
+ localStorage when entered by the user. Set to
+ "false" to hide the box. Default: "0", no
+ expiration. [number] [default: 0]
+ -s, --salt Set the salt manually. It should be set if you
+ want to use "Remember me" through multiple pages.
+ It needs to be a 32-character-long hexadecimal
+ string.
+ Include the empty flag to generate a random salt
+ you can use: "statycrypt -s". [string]
+ --share Get a link containing your hashed password that
+ will auto-decrypt the page. Pass your URL as a
+ value to append "#staticrypt_pwd=",
+ or leave empty to display the hash to append.
+ [string]
+ --short Hide the "short password" warning.
+ [boolean] [default: false]
+ -t, --template Path to custom HTML template with password
+ prompt.
[string] [default: "/code/staticrypt/lib/password_template.html"]
- -i, --instructions Special instructions to display to the user.
+ --template-button Label to use for the decrypt button. Default:
+ "DECRYPT". [string] [default: "DECRYPT"]
+ --template-instructions Special instructions to display to the user.
[string] [default: ""]
- --label-error Error message to display on entering wrong
- password. [string] [default: "Bad password!"]
- --noremember Set this flag to remove the "Remember me"
- checkbox. [boolean] [default: false]
- -o, --output File name/path for the generated encrypted file.
- [string] [default: null]
- --passphrase-placeholder Placeholder to use for the password input.
+ --template-error Error message to display on entering wrong
+ password. [string] [default: "Bad password!"]
+ --template-placeholder Placeholder to use for the password input.
[string] [default: "Password"]
- -r, --remember Expiration in days of the "Remember me" checkbox
- that will save the (salted + hashed) password in
- localStorage when entered by the user. Default:
- "0", no expiration. [number] [default: 0]
- --remember-label Label to use for the "Remember me" checkbox.
+ --template-remember Label to use for the "Remember me" checkbox.
[string] [default: "Remember me"]
- -s, --salt Set the salt manually. It should be set if you
- want to use "Remember me" through multiple
- pages. It needs to be a 32-character-long
- hexadecimal string.
- Include the empty flag to generate a random salt
- you can use: "statycrypt -s". [string]
- --share Get a link containing your hashed password that
- will auto-decrypt the page. Pass your URL as a
- value to append "#staticrypt_pwd=",
- or leave empty to display the hash to append.
- [string]
- --short Hide the "short password" warning.
- [boolean] [default: false]
- -t, --title Title for the output HTML page.
+ --template-title Title for the output HTML page.
[string] [default: "Protected Page"]
@@ -119,43 +109,35 @@ The password argument is optional if `STATICRYPT_PASSWORD` is set in the environ
So, how can you password protect html without a back-end?
-StatiCrypt uses the [crypto-js](https://github.com/brix/crypto-js) library or WebCrypto to generate a static, password protected page that can be decrypted in-browser. You can then just send or upload the generated page to a place serving static content (github pages, for example) and you're done: the page will prompt users for a password, and the javascript will decrypt and load your HTML, all done in the browser.
+StatiCrypt uses WebCrypto to generate a static, password protected page that can be decrypted in-browser. You can then just send or upload the generated page to a place serving static content (github pages, for example) and you're done: the page will prompt users for a password, and the javascript will decrypt and load your HTML, all done in the browser.
-So it basically encrypts your page and puts everything in a user-friendly way to use a password in the new file.
+So it basically encrypts your page and puts everything in a user-friendly way to enter the password in the new file.
## FAQ
### Is it secure?
-Simple answer: your file content has been encrypted with AES-256, a popular and strong encryption algorithm. You can now upload it in any public place and no one will be able to read it without the password. So if you used a long, strong password, then yes it should be pretty secure.
+Simple answer: your file content has been encrypted with AES-256, a popular and strong encryption algorithm. You can now upload it to any public place and no one will be able to read it without the password. So if you used a long, strong password, then yes it should be pretty secure.
That being said, actual security always depends on a number of factors and on the threat model you want to protect against. Because your full encrypted file is accessible client side, brute-force/dictionary attacks would be easy to do at a really fast pace: **use a long, unusual password**. We recommend 16+ alphanum characters, [Bitwarden](https://bitwarden.com/) is a great open-source password manager if you don't have one already.
-On the technical aspects: we use AES in CBC mode (see a discussion on why it's appropriate for StatiCrypt in [#19](https://github.com/robinmoisson/staticrypt/issues/19)) and 600k PBKDF2 iterations when using the WebCrypto engine (it's 15k when using CryptoJS, read a detailed report on why these numbers in [#159](https://github.com/robinmoisson/staticrypt/issues/159)).
+On the technical aspects: we use AES in CBC mode (see a discussion on why this mode is appropriate for StatiCrypt in [#19](https://github.com/robinmoisson/staticrypt/issues/19)) and 600k PBKDF2-SHA256 iterations (which is the [recommended number](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2) by OWASP - read a detailed report on why this number and the security model of StatiCrypt in [#159](https://github.com/robinmoisson/staticrypt/issues/159)).
-**Also, disclaimer:** I am not a cryptographer - the concept is simple and I try my best to implement it correctly but please adjust accordingly: if you are an at-risk activist or have sensitive crypto data to protect, you might want to use something else.
+**Also, disclaimer:** I am not a cryptographer - I try my best to get the implementation right, listen to feedback and be transparent but please adjust accordingly depending on your threat model. If you are an at-risk activist or have sensitive crypto assets to protect, you might want to use something else.
### Can I customize the password prompt?
-Yes! Just copy `lib/password_template.html`, modify it to suit your style and point to your template file with the `-f path/to/my/file.html` flag. Be careful to not break the encrypting javascript part, the variables replaced by StatiCrypt are between curly brackets: `{salt}`.
-
-### Can I remove the "Remember me" checkbox?
-
-If you don't want the checkbox to be included, you can add the `--noremember` flag to disable it.
+Yes! Just copy `lib/password_template.html`, modify it to suit your style and point to your template file with the `-t path/to/my/file.html` flag.
-### Should I use the WebCrypto or CryptoJS engine?
+Be careful to not break the encrypting javascript part, the variables replaced by StatiCrypt are in this format: `/*[|variable|]*/0`. Don't leave out the `0` at the end, this weird syntax is to avoid conflict with other templating engines while still being read as valid JS to parsers so we can use auto-formatting on the template files.
-CryptoJS is the JS library that StatiCrypt used at first to do its crypto operations. WebCrypto is a browser API which exposes crypto methods, without having to rely on an external library.
-
-WebCrypto is faster, which allows us to do more hashing rounds and make StatiCrypt more robust against brute-force attacks - if you can, **you should use WebCrypto**. The only limitation is it's only available in HTTPS context (which [is annoying people](https://github.com/w3c/webcrypto/issues/28)) or on localhost and on non-ancient browsers, so if you need that you can use `--engine cryptojs` which works everywhere. WebCrypto will be the only available option in our next major version.
+### Can I remove the "Remember me" checkbox?
-> **Will switching break share links/remember-me?** If you encrypted a file with the CryptoJS engine and shared auto-decrypt links, or activated the remember-me flag, then switch to WebCrypto, the change is backward compatible and the file should still autodecrypt. The reverse isn't true - don't create an auto-decrypt link with WebCrypto then encrypt your file with CryptoJS.
->
-> This is because we use more hashing rounds with the faster WebCrypto, making it more secure, but we can't remove hashing rounds to convert back (which is the whole point of a hash).
+If you don't want the checkbox to be included, you can set the `--remember false` flag to disable it.
-### Why do we embed the whole crypto-js library in each encrypted file when using the CryptoJS engine by default?
+### Why doesn't StatiCrypt work in HTTP?
-Some adblockers used to see the `crypto-js.min.js` served by CDN, think that's a crypto miner and block it. If you don't want to include it and serve from a CDN instead, you can add `--embed false`.
+From version 3.x StatiCrypt only uses the browser WebCrypto API, which makes it more secure but is only available in HTTPS or on localhost. If you need to use it in HTTP, you can use version 2.x which offers the CryptoJS engine as an option, and will work everywhere.
### Why does StatiCrypt create a config file?
@@ -169,13 +151,13 @@ The salt isn't secret, so you don't need to worry about hiding the config file.
### How does the "Remember me" checkbox work?
-The CLI will add a "Remember me" checkbox on the password prompt by default (`--noremember` to disable). If the user checks it, the (salted + hashed) password will be stored in their browser's localStorage and the page will attempt to auto-decrypt when they come back.
+The CLI will add a "Remember me" checkbox on the password prompt by default (`--remember false` to disable). If the user checks it, the (salted + hashed) password will be stored in their browser's localStorage and the page will attempt to auto-decrypt when they come back.
-If no value is provided the stored password doesn't expire, you can also give it a value in days for how long should the store value be kept with `-r NUMBER_OF_DAYS`. If the user reconnects to the page after the expiration date the stored value will be cleared.
+If no value is provided the stored password doesn't expire, you can also give it a value in days for how long should the store value be kept with `--remember NUMBER_OF_DAYS`. If the user reconnects to the page after the expiration date the stored value will be cleared.
#### "Logging out"
-You can clear StatiCrypt values in localStorage (effectively "logging out") at any time by appending `staticrypt_logout` to the URL fragment (`mysite.com#staticrypt_logout`).
+You can clear StatiCrypt values in localStorage (effectively "logging out") at any time by appending `staticrypt_logout` to the URL fragment (`https://mysite.com#staticrypt_logout`).
#### Encrypting multiple pages
@@ -202,6 +184,10 @@ It's fine to open issues with suggestions and bug reports.
If you find a serious security bug please open an issue, I'll try to fix it relatively quickly.
+### Security
+
+You can find the security policy and secure contact details in [SECURITY.md](SECURITY.md). If you have general ideas or feedback around the implementation or StatiCrypt security model they are very welcome, if it's not extra sensitive feel free to open an issue. A couple of place where security was discussed previously are [#19](https://github.com/robinmoisson/staticrypt/issues/19) and [#159](https://github.com/robinmoisson/staticrypt/issues/159).
+
### Guidelines to contributing
#### Source map
@@ -209,12 +195,12 @@ If you find a serious security bug please open an issue, I'll try to fix it rela
- `cli/` - The command-line interface published to NPM.
- `example/` - Example encrypted files, used as an example in the public website and for manual testing.
- `lib/` - Files shared across www and cli.
-- `scripts/` - Build, test, deploy, CI, etc. See `npm run-script`.
+- `scripts/` - Convenient scripts for building the project.
- `index.html` - The root of the in-browser encryption site hosted at https://robinmoisson.github.io/staticrypt. Kept in the root of the repo for easy deploys to GitHub Pages.
#### Build
-Built assets are committed to main. Run build before submitting a PR or publishing to npm.
+When editing StatiCrypt logic, we want to reflect the changes in the browser version, the CLI and the example files. To do so, run:
```
npm install
@@ -223,7 +209,7 @@ npm run build
#### Test
-The testing is done manually for now - run [build](#build), then open `example/example_encypted.html` and check everything works correctly.
+The testing is done manually for now - you can run [build](#build), then open `example/encrypted/example.html` and check everything works correctly. There is an open issue to automate this in [#136](https://github.com/robinmoisson/staticrypt/issues/136), feel free to contribute to setting up a test framework if you'd like!
## Community and alternatives
diff --git a/cli/helpers.js b/cli/helpers.js
index 89b1662..7d7491f 100644
--- a/cli/helpers.js
+++ b/cli/helpers.js
@@ -1,8 +1,9 @@
+const path = require("path");
const fs = require("fs");
+const readline = require('readline');
-const { generateRandomSalt } = require("../lib/cryptoEngine/webcryptoEngine.js");
-const path = require("path");
-const {renderTemplate} = require("../lib/formater.js");
+const { generateRandomSalt, generateRandomString} = require("../lib/cryptoEngine.js");
+const { renderTemplate } = require("../lib/formater.js");
const Yargs = require("yargs");
const PASSWORD_TEMPLATE_DEFAULT_PATH = path.join(__dirname, "..", "lib", "password_template.html");
@@ -51,26 +52,87 @@ function isOptionSetByUser(option, yargs) {
exports.isOptionSetByUser = isOptionSetByUser;
/**
- * Get the password from the command arguments
+ * Prompts the user for input on the CLI.
*
- * @param {string[]} positionalArguments
- * @returns {string}
+ * @param {string} question
+ * @returns {Promise}
*/
-function getPassword(positionalArguments) {
- let password = process.env.STATICRYPT_PASSWORD;
- const hasEnvPassword = password !== undefined && password !== "";
+function prompt (question) {
+ const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ });
+
+ return new Promise((resolve) => {
+ return rl.question(question, (answer) => {
+ rl.close();
+ return resolve(answer);
+ });
+ });
+}
+
+async function getValidatedPassword(passwordArgument, isShortAllowed) {
+ const password = await getPassword(passwordArgument);
+
+ if (password.length < 14 && !isShortAllowed) {
+ const shouldUseShort = await prompt(
+ `WARNING: Your password is less than 14 characters (length: ${password.length})` +
+ " and it's easy to try brute-forcing on public files. For better security we recommend using a longer one, for example: "
+ + generateRandomString(21)
+ + "\nYou can hide this warning by increasing your password length or adding the '--short' flag." +
+ " Do you want to use the short password? [y/N] "
+ )
+ if (!shouldUseShort.match(/^\s*(y|yes)\s*$/i)) {
+ console.log("Aborting.");
+ process.exit(0);
+ }
+ }
+
+ return password;
+}
+exports.getValidatedPassword = getValidatedPassword;
+
+/**
+ * Get the config from the config file.
+ *
+ * @param {string} configArgument
+ * @returns {{}|object}
+ */
+function getConfig(configArgument) {
+ const isUsingconfigFile = configArgument.toLowerCase() !== "false";
+ const configPath = "./" + configArgument;
+
+ if (isUsingconfigFile && fs.existsSync(configPath)) {
+ return JSON.parse(fs.readFileSync(configPath, "utf8"));
+ }
+
+ return {};
+}
+exports.getConfig = getConfig;
+
+/**
+ * Get the password from the command arguments or environment variables.
+ *
+ * @param {string} passwordArgument - password from the command line
+ * @returns {Promise}
+ */
+async function getPassword(passwordArgument) {
+ // try to get the password from the environment variable
+ const envPassword = process.env.STATICRYPT_PASSWORD;
+ const hasEnvPassword = envPassword !== undefined && envPassword !== "";
if (hasEnvPassword) {
- return password;
+ return envPassword;
}
- if (positionalArguments.length < 2) {
- exitWithError("missing password, please provide an argument or set the STATICRYPT_PASSWORD environment variable in the environment or .env file");
+ // try to get the password from the command line arguments
+ if (passwordArgument !== null) {
+ return passwordArgument;
}
- return positionalArguments[1].toString();
+ // prompt the user for their password
+ return prompt('Enter your long, unusual password: ');
}
-exports.getPassword = getPassword;
/**
* @param {string} filepath
@@ -85,6 +147,26 @@ function getFileContent(filepath) {
}
exports.getFileContent = getFileContent;
+/**
+ * @param {object} namedArgs
+ * @param {object} config
+ * @returns {string}
+ */
+function getValidatedSalt(namedArgs, config) {
+ const salt = getSalt(namedArgs, config);
+
+ // validate the salt
+ if (salt.length !== 32 || /[^a-f0-9]/.test(salt)) {
+ exitWithError(
+ "the salt should be a 32 character long hexadecimal string (only [0-9a-f] characters allowed)"
+ + "\nDetected salt: " + salt
+ );
+ }
+
+ return salt;
+}
+exports.getValidatedSalt = getValidatedSalt;
+
/**
* @param {object} namedArgs
* @param {object} config
@@ -103,7 +185,6 @@ function getSalt(namedArgs, config) {
return generateRandomSalt();
}
-exports.getSalt = getSalt;
/**
* A dead-simple alternative to webpack or rollup for inlining simple
@@ -135,16 +216,34 @@ function convertCommonJSToBrowserJS(modulePath) {
}
exports.convertCommonJSToBrowserJS = convertCommonJSToBrowserJS;
+/**
+ * Build the staticrypt script string to inject in our template.
+ *
+ * @returns {string}
+ */
+function buildStaticryptJS() {
+ let staticryptJS = convertCommonJSToBrowserJS("lib/staticryptJs");
+
+ const scriptsToInject = {
+ js_codec: convertCommonJSToBrowserJS("lib/codec"),
+ js_crypto_engine: convertCommonJSToBrowserJS("lib/cryptoEngine"),
+ };
+
+ return renderTemplate(staticryptJS, scriptsToInject);
+}
+exports.buildStaticryptJS = buildStaticryptJS;
+
/**
* @param {string} filePath
* @param {string} errorName
* @returns {string}
*/
-function readFile(filePath, errorName = file) {
+function readFile(filePath, errorName = "file") {
try {
return fs.readFileSync(filePath, "utf8");
} catch (e) {
- exitWithError(`could not read ${errorName}!`);
+ console.error(e);
+ exitWithError(`could not read ${errorName} at path "${filePath}"`);
}
}
@@ -160,47 +259,21 @@ function genFile(data, outputFilePath, templateFilePath) {
const renderedTemplate = renderTemplate(templateContents, data);
+ // create output directory if it does not exist
+ const dirname = path.dirname(outputFilePath);
+ if (!fs.existsSync(dirname)) {
+ fs.mkdirSync(dirname, { recursive: true });
+ }
+
try {
fs.writeFileSync(outputFilePath, renderedTemplate);
} catch (e) {
- exitWithError("could not generate output file!");
+ console.error(e);
+ exitWithError("could not generate output file");
}
}
exports.genFile = genFile;
-/**
- * TODO: remove in next major version
- *
- * This method checks whether the password template support the security fix increasing PBKDF2 iterations. Users using
- * an old custom password_template might have logic that doesn't benefit from the fix.
- *
- * @param {string} templatePathParameter
- * @returns {boolean}
- */
-function isCustomPasswordTemplateLegacy(templatePathParameter) {
- const customTemplateContent = readFile(templatePathParameter, "template");
-
- // if the template injects the crypto engine, it's up to date
- return !customTemplateContent.includes("js_crypto_engine");
-}
-exports.isCustomPasswordTemplateLegacy = isCustomPasswordTemplateLegacy;
-
-/**
- * TODO: remove in next major version
- *
- * This method checks whether the password template support the async logic.
- *
- * @param {string} templatePathParameter
- * @returns {boolean}
- */
-function isPasswordTemplateUsingAsync(templatePathParameter) {
- const customTemplateContent = readFile(templatePathParameter, "template");
-
- // if the template includes this comment, it's up to date
- return customTemplateContent.includes("// STATICRYPT_VERSION: async");
-}
-exports.isPasswordTemplateUsingAsync = isPasswordTemplateUsingAsync;
-
/**
* @param {string} templatePathParameter
* @returns {boolean}
@@ -212,76 +285,33 @@ function isCustomPasswordTemplateDefault(templatePathParameter) {
exports.isCustomPasswordTemplateDefault = isCustomPasswordTemplateDefault;
function parseCommandLineArguments() {
- return Yargs.usage("Usage: staticrypt [] [options]")
+ return Yargs.usage("Usage: staticrypt [options]")
.option("c", {
alias: "config",
type: "string",
describe: 'Path to the config file. Set to "false" to disable.',
default: ".staticrypt.json",
})
- .option("decrypt-button", {
+ .option("d", {
+ alias: "directory",
type: "string",
- describe: 'Label to use for the decrypt button. Default: "DECRYPT".',
- default: "DECRYPT",
- })
- .option("e", {
- alias: "embed",
- type: "boolean",
- describe: "Whether or not to embed crypto-js in the page (or use an external CDN).",
- default: true,
+ describe: "Name of the directory where the encrypted files will be saved.",
+ default: "encrypted/",
})
- .option("engine", {
+ .option("p", {
+ alias: "password",
type: "string",
- describe: "The crypto engine to use. WebCrypto uses 600k iterations and is more secure, CryptoJS 15k.\n" +
- "Possible values: 'cryptojs', 'webcrypto'.",
- default: "cryptojs",
- })
- .option("f", {
- alias: "file-template",
- type: "string",
- describe: "Path to custom HTML template with password prompt.",
- default: PASSWORD_TEMPLATE_DEFAULT_PATH,
- })
- .option("i", {
- alias: "instructions",
- type: "string",
- describe: "Special instructions to display to the user.",
- default: "",
- })
- .option("label-error", {
- type: "string",
- describe: "Error message to display on entering wrong password.",
- default: "Bad password!",
- })
- .option("noremember", {
- type: "boolean",
- describe: 'Set this flag to remove the "Remember me" checkbox.',
- default: false,
- })
- .option("o", {
- alias: "output",
- type: "string",
- describe: "File name/path for the generated encrypted file.",
+ describe: "The password to encrypt your file with. Leave empty to be prompted for it. If STATICRYPT_PASSWORD" +
+ " is set in the env, we'll use that instead.",
default: null,
})
- .option("passphrase-placeholder", {
- type: "string",
- describe: "Placeholder to use for the password input.",
- default: "Password",
- })
- .option("r", {
- alias: "remember",
+ .option("remember", {
type: "number",
describe:
'Expiration in days of the "Remember me" checkbox that will save the (salted + hashed) password ' +
- 'in localStorage when entered by the user. Default: "0", no expiration.',
+ 'in localStorage when entered by the user. Set to "false" to hide the box. Default: "0", no expiration.',
default: 0,
})
- .option("remember-label", {
- type: "string",
- describe: 'Label to use for the "Remember me" checkbox.',
- default: "Remember me",
- })
// do not give a default option to this parameter - we want to see when the flag is included with no
// value and when it's not included at all
.option("s", {
@@ -306,7 +336,37 @@ function parseCommandLineArguments() {
default: false,
})
.option("t", {
- alias: "title",
+ alias: "template",
+ type: "string",
+ describe: "Path to custom HTML template with password prompt.",
+ default: PASSWORD_TEMPLATE_DEFAULT_PATH,
+ })
+ .option("template-button", {
+ type: "string",
+ describe: 'Label to use for the decrypt button. Default: "DECRYPT".',
+ default: "DECRYPT",
+ })
+ .option("template-instructions", {
+ type: "string",
+ describe: "Special instructions to display to the user.",
+ default: "",
+ })
+ .option("template-error", {
+ type: "string",
+ describe: "Error message to display on entering wrong password.",
+ default: "Bad password!",
+ })
+ .option("template-placeholder", {
+ type: "string",
+ describe: "Placeholder to use for the password input.",
+ default: "Password",
+ })
+ .option("template-remember", {
+ type: "string",
+ describe: 'Label to use for the "Remember me" checkbox.',
+ default: "Remember me",
+ })
+ .option("template-title", {
type: "string",
describe: "Title for the output HTML page.",
default: "Protected Page",
diff --git a/cli/index.js b/cli/index.js
index 5f35bd0..68a4de0 100755
--- a/cli/index.js
+++ b/cli/index.js
@@ -3,182 +3,97 @@
"use strict";
const fs = require("fs");
-const path = require("path");
// parse .env file into process.env
require('dotenv').config();
-const cryptojsEngine = require("../lib/cryptoEngine/cryptojsEngine");
-const webcryptoEngine = require("../lib/cryptoEngine/webcryptoEngine");
+const cryptoEngine = require("../lib/cryptoEngine.js");
const codec = require("../lib/codec.js");
-const { convertCommonJSToBrowserJS, exitWithError, isOptionSetByUser, genFile, getPassword, getFileContent, getSalt} = require("./helpers");
-const { isCustomPasswordTemplateLegacy, parseCommandLineArguments, isPasswordTemplateUsingAsync} = require("./helpers.js");
-
-const CRYPTOJS_SCRIPT_TAG =
- '';
+const { generateRandomSalt } = cryptoEngine;
+const { encode } = codec.init(cryptoEngine);
+const { parseCommandLineArguments, buildStaticryptJS, isOptionSetByUser, genFile, getPassword, getFileContent, getSalt,
+ getValidatedSalt,
+ getValidatedPassword, getConfig
+} = require("./helpers.js");
// parse arguments
const yargs = parseCommandLineArguments();
const namedArgs = yargs.argv;
-// set the crypto engine
-const isWebcrypto = namedArgs.engine === "webcrypto";
-const cryptoEngine = isWebcrypto ? webcryptoEngine : cryptojsEngine;
-const { generateRandomSalt, generateRandomString } = cryptoEngine;
-const { encode } = codec.init(cryptoEngine);
-
async function runStatiCrypt() {
- // if the 's' flag is passed without parameter, generate a salt, display & exit
- if (isOptionSetByUser("s", yargs) && !namedArgs.salt) {
- console.log(generateRandomSalt());
- process.exit(0);
- }
+ const hasSaltFlag = isOptionSetByUser("s", yargs);
+ const hasShareFlag = isOptionSetByUser("share", yargs);
- // validate the number of arguments
const positionalArguments = namedArgs._;
- if (positionalArguments.length > 2 || positionalArguments.length === 0) {
- yargs.showHelp();
- process.exit(1);
- }
- // parse input
- const inputFilepath = positionalArguments[0].toString(),
- password = getPassword(positionalArguments);
-
- if (password.length < 16 && !namedArgs.short) {
- console.log(
- `WARNING: Your password is less than 16 characters (length: ${password.length}). Brute-force attacks are easy to `
- + `try on public files, and you are most safe when using a long password.\n\n`
- + `👉️ Here's a strong generated password you could use: `
- + generateRandomString(21)
- + "\n\nThe file was encrypted with your password. You can hide this warning by increasing your password length or"
- + " adding the '--short' flag."
- )
+ // validate the number of arguments
+ if (!hasShareFlag && !hasSaltFlag) {
+ if (positionalArguments.length === 0) {
+ yargs.showHelp();
+ process.exit(1);
+ }
}
- // get config file
- const isUsingconfigFile = namedArgs.config.toLowerCase() !== "false";
- const configPath = "./" + namedArgs.config;
- let config = {};
- if (isUsingconfigFile && fs.existsSync(configPath)) {
- config = JSON.parse(fs.readFileSync(configPath, "utf8"));
+ // if the 's' flag is passed without parameter, generate a salt, display & exit
+ if (hasSaltFlag && !namedArgs.salt) {
+ console.log(generateRandomSalt());
+ process.exit(0);
}
- // get the salt
- const salt = getSalt(namedArgs, config);
+ // get config file
+ const config = getConfig(namedArgs.config);
- // validate the salt
- if (salt.length !== 32 || /[^a-f0-9]/.test(salt)) {
- exitWithError(
- "the salt should be a 32 character long hexadecimal string (only [0-9a-f] characters allowed)"
- + "\nDetected salt: " + salt
- );
- }
-
- // write salt to config file
- if (isUsingconfigFile && config.salt !== salt) {
- config.salt = salt;
- fs.writeFileSync(configPath, JSON.stringify(config, null, 4));
- }
+ // get the salt & password
+ const salt = getValidatedSalt(namedArgs, config);
+ const password = await getValidatedPassword(namedArgs.password, namedArgs.short);
// display the share link with the hashed password if the --share flag is set
- if (isOptionSetByUser("share", yargs)) {
+ if (hasShareFlag) {
const url = namedArgs.share || "";
const hashedPassword = await cryptoEngine.hashPassphrase(password, salt);
console.log(url + "#staticrypt_pwd=" + hashedPassword);
+ process.exit(0);
}
- // TODO: remove in the next major version bump. This is to allow a security update to some versions without breaking
- // older ones. If the password template is custom AND created before 2.2.0 we need to use the old hashing algorithm.
- const isLegacy = isCustomPasswordTemplateLegacy(namedArgs.f);
-
- if (isLegacy) {
- console.log(
- "#################################\n\n" +
- "SECURITY WARNING [StatiCrypt]: You are using an old version of the password template, which has been found to " +
- "be less secure. Please update your custom password_template logic to match the latest version." +
- "\nYou can find instructions here: https://github.com/robinmoisson/staticrypt/issues/161" +
- "\n\n#################################"
- );
- }
-
- if (!isWebcrypto) {
- console.log(
- "WARNING: If you are viewing the file over HTTPS or locally, we recommend " +
- (isPasswordTemplateUsingAsync(namedArgs.f) ? "" : "updating your password template to the latest version and ") +
- "using the '--engine webcrypto' more secure engine. It will become the default in StatiCrypt next major version."
- );
- } else if (!isPasswordTemplateUsingAsync(namedArgs.f) && isWebcrypto) {
- exitWithError(
- "The '--engine webcrypto' engine is only available for password templates that use async/await. Please " +
- "update your password template to the latest version or use the '--engine cryptojs' engine."
- )
- }
-
- // create crypto-js tag (embedded or not)
- let cryptoTag = CRYPTOJS_SCRIPT_TAG;
- if (isWebcrypto) {
- cryptoTag = "";
- } else if (namedArgs.embed) {
- try {
- const embedContents = fs.readFileSync(
- path.join(__dirname, "..", "lib", "kryptojs-3.1.9-1.min.js"),
- "utf8"
- );
-
- cryptoTag = "";
- } catch (e) {
- exitWithError("Embed file does not exist.");
- }
+ // write salt to config file
+ const isUsingconfigFile = namedArgs.config.toLowerCase() !== "false";
+ const configPath = "./" + namedArgs.config;
+ if (isUsingconfigFile && config.salt !== salt) {
+ config.salt = salt;
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 4));
}
- const cryptoEngineString = isWebcrypto
- ? convertCommonJSToBrowserJS("lib/cryptoEngine/webcryptoEngine")
- : convertCommonJSToBrowserJS("lib/cryptoEngine/cryptojsEngine");
-
// get the file content
+ const inputFilepath = positionalArguments[0].toString();
const contents = getFileContent(inputFilepath);
// encrypt input
- encode(contents, password, salt, isLegacy).then((encryptedMessage) => {
- let codecString;
- if (isWebcrypto) {
- codecString = convertCommonJSToBrowserJS("lib/codec");
- } else {
- // TODO: remove on next major version bump. The replace is a hack to pass the salt to the injected js_codec in
- // a backward compatible way (not requiring to update the password_template). Same for using a "sync" version
- // of the codec.
- codecString = convertCommonJSToBrowserJS("lib/codec-sync").replace('##SALT##', salt);
- }
-
- const data = {
- crypto_tag: cryptoTag,
- decrypt_button: namedArgs.decryptButton,
- // TODO: deprecated option here for backward compat, remove on next major version bump
- embed: isWebcrypto ? false : namedArgs.embed,
- encrypted: encryptedMessage,
- instructions: namedArgs.instructions,
- is_remember_enabled: namedArgs.noremember ? "false" : "true",
- js_codec: codecString,
- js_crypto_engine: cryptoEngineString,
- label_error: namedArgs.labelError,
- passphrase_placeholder: namedArgs.passphrasePlaceholder,
- remember_duration_in_days: namedArgs.remember,
- remember_me: namedArgs.rememberLabel,
- salt: salt,
- title: namedArgs.title,
- };
-
- const outputFilepath = namedArgs.output !== null
- ? namedArgs.output
- : inputFilepath.replace(/\.html$/, "") + "_encrypted.html";
-
- genFile(data, outputFilepath, namedArgs.f);
-
- });
+ const encryptedMsg = await encode(contents, password, salt);
+
+ const isRememberEnabled = namedArgs.remember !== "false";
+
+ const data = {
+ is_remember_enabled: JSON.stringify(isRememberEnabled),
+ js_staticrypt: buildStaticryptJS(),
+ staticrypt_config: {
+ encryptedMsg,
+ isRememberEnabled,
+ rememberDurationInDays: namedArgs.remember,
+ salt,
+ },
+ template_button: namedArgs.templateButton,
+ template_error: namedArgs.templateError,
+ template_instructions: namedArgs.templateInstructions,
+ template_placeholder: namedArgs.templatePlaceholder,
+ template_remember: namedArgs.templateRemember,
+ template_title: namedArgs.templateTitle,
+ };
+
+ const outputFilepath = namedArgs.directory.replace(/\/+$/, '') + "/" + inputFilepath;
+
+ genFile(data, outputFilepath, namedArgs.template);
}
runStatiCrypt();
diff --git a/example/example_encrypted.html b/example/encrypted/example.html
similarity index 78%
rename from example/example_encrypted.html
rename to example/encrypted/example.html
index 6033bfb..23428ff 100644
--- a/example/example_encrypted.html
+++ b/example/encrypted/example.html
@@ -183,14 +183,11 @@
-
-
-
diff --git a/index.html b/index.html
index 6fedba7..43970ff 100644
--- a/index.html
+++ b/index.html
@@ -46,7 +46,7 @@
Download your encrypted string in a HTML page with a password prompt you can upload anywhere (see example).
+ target="_blank" href="example/encrypted/example.html">example).
The tool is also available as a CLI on NPM and is
@@ -184,11 +184,6 @@ Encrypted HTML
-
-
-
+
+
diff --git a/lib/staticryptJs.js b/lib/staticryptJs.js
new file mode 100644
index 0000000..55d5d4c
--- /dev/null
+++ b/lib/staticryptJs.js
@@ -0,0 +1,215 @@
+const cryptoEngine = /*[|js_crypto_engine|]*/0
+const codec = /*[|js_codec|]*/0
+const decode = codec.init(cryptoEngine).decode;
+
+
+/**
+ * Initialize the staticrypt module, that exposes functions callbable by the password_template.
+ *
+ * @param {{
+ * encryptedMsg: string,
+ * isRememberEnabled: boolean,
+ * rememberDurationInDays: number,
+ * salt: string,
+ * }} staticryptConfig - object of data that is stored on the password_template at encryption time.
+ *
+ * @param {{
+ * rememberExpirationKey: string,
+ * rememberPassphraseKey: string,
+ * replaceHtmlCallback: function,
+ * clearLocalStorageCallback: function,
+ * }} templateConfig - object of data that can be configured by a custom password_template.
+ */
+function init(staticryptConfig, templateConfig) {
+ const exports = {};
+
+ /**
+ * Decrypt our encrypted page, replace the whole HTML.
+ *
+ * @param {string} hashedPassphrase
+ * @returns {Promise}
+ */
+ async function decryptAndReplaceHtml(hashedPassphrase) {
+ const { encryptedMsg, salt } = staticryptConfig;
+ const { replaceHtmlCallback } = templateConfig;
+
+ const result = await decode(encryptedMsg, hashedPassphrase, salt);
+ if (!result.success) {
+ return false;
+ }
+ const plainHTML = result.decoded;
+
+ // if the user configured a callback call it, otherwise just replace the whole HTML
+ if (typeof replaceHtmlCallback === 'function') {
+ replaceHtmlCallback(plainHTML);
+ } else {
+ document.write(plainHTML);
+ document.close();
+ }
+
+ return true;
+ }
+
+ /**
+ * Attempt to decrypt the page and replace the whole HTML.
+ *
+ * @param {string} password
+ * @param {boolean} isRememberChecked
+ *
+ * @returns {Promise<{isSuccessful: boolean, hashedPassword?: string}>} - we return an object, so that if we want to
+ * expose more information in the future we can do it without breaking the password_template
+ */
+ async function handleDecryptionOfPage(password, isRememberChecked) {
+ const { isRememberEnabled, rememberDurationInDays, salt } = staticryptConfig;
+ const { rememberExpirationKey, rememberPassphraseKey } = templateConfig;
+
+ // decrypt and replace the whole page
+ const hashedPassword = await cryptoEngine.hashPassphrase(password, salt);
+
+ const isDecryptionSuccessful = await decryptAndReplaceHtml(hashedPassword);
+
+ if (!isDecryptionSuccessful) {
+ return {
+ isSuccessful: false,
+ hashedPassword,
+ };
+ }
+
+ // remember the hashedPassword and set its expiration if necessary
+ if (isRememberEnabled && isRememberChecked) {
+ window.localStorage.setItem(rememberPassphraseKey, hashedPassword);
+
+ // set the expiration if the duration isn't 0 (meaning no expiration)
+ if (rememberDurationInDays > 0) {
+ window.localStorage.setItem(
+ rememberExpirationKey,
+ (new Date().getTime() + rememberDurationInDays * 24 * 60 * 60 * 1000).toString()
+ );
+ }
+ }
+
+ return {
+ isSuccessful: true,
+ hashedPassword,
+ };
+ }
+ exports.handleDecryptionOfPage = handleDecryptionOfPage;
+
+ /**
+ * Clear localstorage from staticrypt related values
+ */
+ function clearLocalStorage() {
+ const { clearLocalStorageCallback, rememberExpirationKey, rememberPassphraseKey } = templateConfig;
+
+ if (typeof clearLocalStorageCallback === 'function') {
+ clearLocalStorageCallback();
+ } else {
+ localStorage.removeItem(rememberPassphraseKey);
+ localStorage.removeItem(rememberExpirationKey);
+ }
+ }
+
+ async function handleDecryptOnLoad() {
+ let isSuccessful = await decryptOnLoadFromUrl();
+
+ if (!isSuccessful) {
+ isSuccessful = await decryptOnLoadFromRememberMe();
+ }
+
+ return { isSuccessful };
+ }
+ exports.handleDecryptOnLoad = handleDecryptOnLoad;
+
+ /**
+ * Clear storage if we are logging out
+ *
+ * @returns {boolean} - whether we logged out
+ */
+ function logoutIfNeeded() {
+ const logoutKey = "staticrypt_logout";
+
+ // handle logout through query param
+ const queryParams = new URLSearchParams(window.location.search);
+ if (queryParams.has(logoutKey)) {
+ clearLocalStorage();
+ return true;
+ }
+
+ // handle logout through URL fragment
+ const hash = window.location.hash.substring(1);
+ if (hash.includes(logoutKey)) {
+ clearLocalStorage();
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * To be called on load: check if we want to try to decrypt and replace the HTML with the decrypted content, and
+ * try to do it if needed.
+ *
+ * @returns {Promise} true if we derypted and replaced the whole page, false otherwise
+ */
+ async function decryptOnLoadFromRememberMe() {
+ const { rememberDurationInDays } = staticryptConfig;
+ const { rememberExpirationKey, rememberPassphraseKey } = templateConfig;
+
+ // if we are login out, terminate
+ if (logoutIfNeeded()) {
+ return false;
+ }
+
+ // if there is expiration configured, check if we're not beyond the expiration
+ if (rememberDurationInDays && rememberDurationInDays > 0) {
+ const expiration = localStorage.getItem(rememberExpirationKey),
+ isExpired = expiration && new Date().getTime() > parseInt(expiration);
+
+ if (isExpired) {
+ clearLocalStorage();
+ return false;
+ }
+ }
+
+ const hashedPassphrase = localStorage.getItem(rememberPassphraseKey);
+
+ if (hashedPassphrase) {
+ // try to decrypt
+ const isDecryptionSuccessful = await decryptAndReplaceHtml(hashedPassphrase);
+
+ // if the decryption is unsuccessful the password might be wrong - silently clear the saved data and let
+ // the user fill the password form again
+ if (!isDecryptionSuccessful) {
+ clearLocalStorage();
+ return false;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ function decryptOnLoadFromUrl() {
+ const passwordKey = "staticrypt_pwd";
+
+ // get the password from the query param
+ const queryParams = new URLSearchParams(window.location.search);
+ const hashedPassphraseQuery = queryParams.get(passwordKey);
+
+ // get the password from the url fragment
+ const hashRegexMatch = window.location.hash.substring(1).match(new RegExp(passwordKey + "=(.*)"));
+ const hashedPassphraseFragment = hashRegexMatch ? hashRegexMatch[1] : null;
+
+ const hashedPassphrase = hashedPassphraseFragment || hashedPassphraseQuery;
+
+ if (hashedPassphrase) {
+ return decryptAndReplaceHtml(hashedPassphrase);
+ }
+
+ return false;
+ }
+
+ return exports;
+}
+exports.init = init;
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 9cbc566..b9a49f0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "staticrypt",
- "version": "2.4.0",
+ "version": "3.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "staticrypt",
- "version": "2.4.0",
+ "version": "3.0.0",
"license": "MIT",
"dependencies": {
"crypto-js": "3.1.9-1",
diff --git a/package.json b/package.json
index 913a24b..574a6af 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "staticrypt",
- "version": "2.6.0",
+ "version": "3.0.0",
"description": "Based on the [crypto-js](https://github.com/brix/crypto-js) library, StatiCrypt uses AES-256 to encrypt your input with your passphrase and put it in a HTML file with a password prompt that can decrypted in-browser (client side).",
"main": "index.js",
"files": [
diff --git a/scripts/build.sh b/scripts/build.sh
index 1b27e27..16c7f33 100755
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -1,12 +1,15 @@
# Build the website files
# Should be run with "npm run build" - npm handles the pathing better (so no "#!/usr/bin/env" bash on top)
+# build the index.html file
+node ./scripts/buildIndex.js
+
# encrypt the example file
-node cli/index.js example/example.html test \
- --engine webcrypto \
+cd example
+node ../cli/index.js example.html \
+ -p test \
--short \
--salt b93bbaf35459951c47721d1f3eaeb5b9 \
- --instructions "Enter \"test\" to unlock the page"
+ --config false \
+ --template-instructions "Enter \"test\" to unlock the page"
-# build the index.html file
-node ./scripts/buildIndex.js
diff --git a/scripts/buildIndex.js b/scripts/buildIndex.js
index c0d54e2..3309f02 100644
--- a/scripts/buildIndex.js
+++ b/scripts/buildIndex.js
@@ -1,9 +1,10 @@
-const { convertCommonJSToBrowserJS, genFile } = require("../cli/helpers.js");
+const { convertCommonJSToBrowserJS, genFile, buildStaticryptJS} = require("../cli/helpers.js");
const data = {
js_codec: convertCommonJSToBrowserJS("lib/codec"),
- js_crypto_engine: convertCommonJSToBrowserJS("lib/cryptoEngine/webcryptoEngine"),
+ js_crypto_engine: convertCommonJSToBrowserJS("lib/cryptoEngine"),
js_formater: convertCommonJSToBrowserJS("lib/formater"),
+ js_staticrypt: buildStaticryptJS(),
};
genFile(data, "./index.html", "./scripts/index_template.html");
diff --git a/scripts/index_template.html b/scripts/index_template.html
index 5b2f419..3af59e0 100644
--- a/scripts/index_template.html
+++ b/scripts/index_template.html
@@ -46,7 +46,7 @@
Download your encrypted string in a HTML page with a password prompt you can upload anywhere (see example).
+ target="_blank" href="example/encrypted/example.html">example).
The tool is also available as a CLI on NPM and is
@@ -184,30 +184,29 @@ Encrypted HTML
-
-
-
+
+