secrets upgrade (from hz)

This commit is contained in:
Mike Macgirvin 2024-03-14 14:49:12 +11:00
parent edd71dd365
commit bc4c572951
61 changed files with 37277 additions and 13 deletions

View file

@ -275,10 +275,12 @@ function bb_parse_crypt($match)
$matches = [];
$attributes = $match[1];
$algorithm = "";
$hint = '';
$algorithm = '';
preg_match("/alg='(.*?)'/ism", $attributes, $matches);
$algorithm = $matches[1] ?? '';
if ($matches[1] != "") {
$algorithm = $matches[1];
}
@ -293,13 +295,12 @@ function bb_parse_crypt($match)
$algorithm = $matches[1];
}
$hint = "";
$matches = [];
preg_match("/hint='(.*?)'/ism", $attributes, $matches);
if ($matches[1] != "") {
$hint = $matches[1];
}
$hint = $matches[1] ?? '';
preg_match("/hint=\&quot\;(.*?)\&quot\;/ism", $attributes, $matches);
if ($matches[1] != "") {
$hint = $matches[1];
@ -311,7 +312,13 @@ function bb_parse_crypt($match)
$x = random_string();
$f = 'hz_decrypt';
$f = 'sodium_decrypt';
if (in_array($algorithm, ['AES-128-CCM', 'rot13', 'triple-rot13'])) {
$f = 'hz_decrypt'; // deprecated
}
$onclick = 'onclick="' . $f . '(\'' . $algorithm . '\',\'' . $hint . '\',\'' . $match[2] . '\',\'#' . $x . '\');"';
$label = t('Encrypted content');

View file

@ -0,0 +1,40 @@
name: CI
on: [push, pull_request]
jobs:
build:
strategy:
matrix:
platform: [ubuntu-latest]
node: [ '12', '10' ]
name: Node ${{ matrix.node }} (${{ matrix.platform }})
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- name: install dependencies
run: npm install
- name: test without sodium-native
run: npm test
- name: install sodium-native
run: npm install --save sodium-native
- name: test with sodium-native
run: npm test
build_latest:
name: Node latest
runs-on: ubuntu-latest
container: node:latest
steps:
- uses: actions/checkout@v1
- name: install dependencies
run: npm install
- name: test without sodium-native
run: npm test
- name: install sodium-native
run: npm install --save sodium-native
- name: test with sodium-native
run: npm test

6
library/sodium-plus/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
/.idea
/node_modules
/package-lock.json
*-tmp.js
/nbproject
/.nyc_output

View file

@ -0,0 +1,3 @@
/.idea
/dist
*-tmp.js

View file

@ -0,0 +1,18 @@
/*
* ISC License
*
* Copyright (c) 2019
* Paragon Initiative Enterprises <security at paragonie dot com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

View file

@ -0,0 +1,86 @@
# Sodium-Plus (Na+)
[![Build Status](https://github.com/paragonie/sodium-plus/workflows/CI/badge.svg)](https://github.com/paragonie/sodium-plus/actions?workflow=CI)
[![npm version](https://img.shields.io/npm/v/sodium-plus.svg)](https://npm.im/sodium-plus)
Sodium-Plus delivers a positive cryptography experience for JavaScript developers.
Sodium-Plus brings you all the benefits of using libsodium in your application
without any of the headaches introduced by the incumbent APIs.
Sodium-Plus is permissively licensed (ISC) and free to use.
## Features
* **Cross-platform.**
* Yes, this includes [in the browser](docs/getting-started.md#sodium-plus-in-the-browser).
* Pluggable backend with an [auto-loader](docs/getting-started.md):
* If [sodium-native](https://github.com/sodium-friends/sodium-native)
is installed, it will be preferred.
* Otherwise, the default is [libsodium-wrappers](https://github.com/jedisct1/libsodium.js).
* Fully `async`/`await` ready (aside from object constructors).
* Type-safe API:
* Instead of just passing around `Buffer` objects and hoping you got your
argument order correct, `sodium-plus` will throw an Error if you provide
the wrong key type. This prevents you from accidentally introducing a severe
security risk into your application.
## Installing
### Installing as a Node.js Module
With NPM:
```terminal
npm install sodium-plus
```
You can optionally install `sodium-native` alongside `sodium-plus` if you
want better performance.
The default configuration is a bit slower, but has a wider reach
(e.g. web browsers).
### Installing in a Web Page
See [this section of the documentation](docs/getting-started.md#sodium-plus-in-the-browser)
for getting started with Sodium-Plus in a web browser.
## Using Sodium-Plus in Your Projects
SodiumPlus is meant to be used asynchronously, like so:
```javascript
const { SodiumPlus } = require('sodium-plus');
(async function() {
// Select a backend automatically
let sodium = await SodiumPlus.auto();
let key = await sodium.crypto_secretbox_keygen();
let nonce = await sodium.randombytes_buf(24);
let message = 'This is just a test message';
// Message can be a string, buffer, array, etc.
let ciphertext = await sodium.crypto_secretbox(message, nonce, key);
console.log(ciphertext);
let decrypted = await sodium.crypto_secretbox_open(ciphertext, nonce, key);
console.log(decrypted.toString('utf-8'));
})();
```
This should produce output similar to below (but with different random-looking bytes):
```
<Buffer 00 b7 66 89 3d b4 4d e9 7e 0f 66 91 fd d1 ca fd be bb 7f 00 89 76 5b 48 ec ed 80 cc 87 76 54 1b b5 ea 87 9b e5 19 ee 4c 31 c5 63>
This is just a test message
```
## Documentation
The documentation is [available online on Github](https://github.com/paragonie/sodium-plus/tree/master/docs)!
## Support Contracts
If your company uses this library in their products or services, you may be
interested in [purchasing a support contract from Paragon Initiative Enterprises](https://paragonie.com/enterprise).

View file

@ -0,0 +1,25 @@
const {
CryptographyKey,
Ed25519PublicKey,
Ed25519SecretKey,
SodiumError,
SodiumPlus,
SodiumPolyfill,
SodiumUtil,
X25519PublicKey,
X25519SecretKey
} = require('./index');
// Load dependencies into window
(async function(){
window.CryptographyKey = CryptographyKey;
window.Ed25519PublicKey = Ed25519PublicKey;
window.Ed25519SecretKey = Ed25519SecretKey;
window.SodiumError = SodiumError;
window.SodiumPlus = SodiumPlus;
window.SodiumPolyfill = SodiumPolyfill;
window.SodiumUtil = SodiumUtil;
window.X25519PublicKey = X25519PublicKey;
window.X25519SecretKey = X25519SecretKey;
window.sodium = await SodiumPlus.auto();
})();

View file

@ -0,0 +1,22 @@
#!/usr/bin/env bash
basedir=$(dirname $(readlink -f ${BASH_SOURCE[0]}))
path="${basedir}/build/remove-sodium-native.sh"
bash "${path}"
ret=$?
if [[ $ret -ne 0 ]]; then
echo "Exiting..."
exit "${ret}"
fi
echo "Building..."
browserify browser.js > dist/sodium-plus.js
echo "Minifying..."
browserify browser.js -p tinyify > dist/sodium-plus.min.js
echo "Build complete! Resetting..."
# Once browserify finishes, rollback changes that removed sodium-native.
git checkout -- lib/*
git checkout -- lib/*/*
echo "Done!"

View file

@ -0,0 +1,57 @@
diff --git a/lib/backend/sodiumnative.js b/lib/backend/sodiumnative.js
index a592b10..f268085 100644
--- a/lib/backend/sodiumnative.js
+++ b/lib/backend/sodiumnative.js
@@ -1,12 +1,5 @@
let loaded = false;
-let _sodium;
-/* istanbul ignore next */
-try {
- _sodium = require('sodium-native');
- loaded = true;
-} catch (e) {
- _sodium = {};
-}
+let _sodium = {};
const Backend = require('../backend');
const CryptographyKey = require('../cryptography-key');
const SodiumError = require('../sodium-error');
diff --git a/lib/sodiumplus.js b/lib/sodiumplus.js
index a592b10..f268085 100644
--- a/lib/sodiumplus.js
+++ b/lib/sodiumplus.js
@@ -4,7 +4,6 @@
const Ed25519PublicKey = require('./keytypes/ed25519pk');
const LibsodiumWrappersBackend = require('./backend/libsodium-wrappers');
const SodiumError = require('./sodium-error');
-const SodiumNativeBackend = require('./backend/sodiumnative');
const X25519PublicKey = require('./keytypes/x25519pk');
const X25519SecretKey = require('./keytypes/x25519sk');
const Util = require('./util');
@@ -41,7 +40,7 @@
* @return {boolean}
*/
isSodiumNative() {
- return (this.backend instanceof SodiumNativeBackend);
+ return false;
}
/**
@@ -60,16 +59,7 @@
* @return {Promise<SodiumPlus>}
*/
static async auto() {
- let backend;
- try {
- backend = await SodiumNativeBackend.init();
- } catch (e) {
- backend = await LibsodiumWrappersBackend.init();
- }
- /* istanbul ignore if */
- if (!backend) {
- backend = await LibsodiumWrappersBackend.init();
- }
+ let backend = await LibsodiumWrappersBackend.init();
Util.populateConstants(backend);
return new SodiumPlus(backend);
}

View file

@ -0,0 +1,12 @@
#!/usr/bin/env bash
basedir=$(dirname $(readlink -f ${BASH_SOURCE[0]}))
path="${basedir}/pre-build.patch"
git apply --check "${path}"
ret=$?
if [[ $ret -ne 0 ]]; then
exit "${ret}"
fi
git apply "${path}"
ret=$?
exit $ret

30454
library/sodium-plus/dist/sodium-plus.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,99 @@
# Table of Contents
* [Table of Contents](#table-of-contents) (you are here)
* [Getting Started](getting-started.md#getting-started)
* [CryptographyKey](getting-started.md#cryptographykey)
* [Sodium-Plus in the Browser](getting-started.md#sodium-plus-in-the-browser)
* [SodiumPlus Methods](SodiumPlus#sodiumplus-methods)
* [AEAD (XChaCha20-Poly1305)](SodiumPlus/AEAD.md#aead)
* [crypto_aead_xchacha20poly1305_ietf_decrypt](SodiumPlus/AEAD.md#crypto_aead_xchacha20poly1305_ietf_decrypt)
* [crypto_aead_xchacha20poly1305_ietf_encrypt](SodiumPlus/AEAD.md#crypto_aead_xchacha20poly1305_ietf_encrypt)
* [crypto_aead_xchacha20poly1305_ietf_keygen](SodiumPlus/AEAD.md#crypto_aead_xchacha20poly1305_ietf_keygen)
* [Example for crypto_aead_xchacha20poly1305_ietf_*](SodiumPlus/AEAD.md#example-for-crypto_aead_xchacha20poly1305_ietf_)
* [Shared-key authentication](SodiumPlus/shared-key-authentication.md)
* [crypto_auth](SodiumPlus/shared-key-authentication.md#crypto_auth)
* [crypto_auth_verify](SodiumPlus/shared-key-authentication.md#crypto_auth_verify)
* [crypto_auth_keygen](SodiumPlus/shared-key-authentication.md#crypto_auth_keygen)
* [Example for crypto_auth](SodiumPlus/shared-key-authentication.md#example-for-crypto_auth)
* [Authenticated public-key encryption](SodiumPlus/authenticated-public-key-encryption.md)
* [crypto_box](SodiumPlus/authenticated-public-key-encryption.md#crypto_box)
* [crypto_box_open](SodiumPlus/authenticated-public-key-encryption.md#crypto_box_open)
* [crypto_box_keypair](SodiumPlus/authenticated-public-key-encryption.md#crypto_box_keypair)
* [crypto_box_keypair_from_secretkey_and_publickey](SodiumPlus/authenticated-public-key-encryption.md#crypto_box_keypair_from_secretkey_and_publickey)
* [crypto_box_publickey](SodiumPlus/authenticated-public-key-encryption.md#crypto_box_publickey)
* [crypto_box_secretkey](SodiumPlus/authenticated-public-key-encryption.md#crypto_box_secretkey)
* [crypto_box_publickey_from_secretkey](SodiumPlus/authenticated-public-key-encryption.md#crypto_box_publickey_from_secretkey)
* [Example for crypto_box](SodiumPlus/authenticated-public-key-encryption.md#example-for-crypto_box)
* [Sealed boxes (anonymous public-key encryption)](SodiumPlus/sealed-boxes.md)
* [crypto_box_seal](SodiumPlus/sealed-boxes.md#crypto_box_seal)
* [crypto_box_seal_open](SodiumPlus/sealed-boxes.md#crypto_box_seal_open)
* [Example for crypto_box_seal](SodiumPlus/sealed-boxes.md#example-for-crypto_box_seal)
* [General-purpose cryptographic hash](SodiumPlus/general-purpose-cryptographic-hash.md)
* [crypto_generichash](SodiumPlus/general-purpose-cryptographic-hash.md#crypto_generichash)
* [crypto_generichash_init](SodiumPlus/general-purpose-cryptographic-hash.md#crypto_generichash_init)
* [crypto_generichash_update](SodiumPlus/general-purpose-cryptographic-hash.md#crypto_generichash_update)
* [crypto_generichash_final](SodiumPlus/general-purpose-cryptographic-hash.md#crypto_generichash_final)
* [crypto_generichash_keygen](SodiumPlus/general-purpose-cryptographic-hash.md#crypto_generichash_keygen)
* [Example for crypto_generichash](SodiumPlus/general-purpose-cryptographic-hash.md#example-for-crypto_generichash)
* [Key derivation](SodiumPlus/key-derivation.md)
* [crypto_kdf_derive_from_key](SodiumPlus/key-derivation.md#crypto_kdf_derive_from_key)
* [crypto_kdf_keygen](SodiumPlus/key-derivation.md#crypto_kdf_keygen)
* [Example for crypto_kdf](SodiumPlus/key-derivation.md#example-for-crypto_kdf)
* [Key exchange](SodiumPlus/key-exchange.md)
* [crypto_kx_keypair](SodiumPlus/key-exchange.md#crypto_kx_keypair)
* [crypto_kx_seed_keypair](SodiumPlus/key-exchange.md#crypto_kx_seed_keypair)
* [crypto_kx_client_session_keys](SodiumPlus/key-exchange.md#crypto_kx_client_session_keys)
* [crypto_kx_server_session_keys](SodiumPlus/key-exchange.md#crypto_kx_server_session_keys)
* [Example for crypto_kx](SodiumPlus/key-exchange.md#example-for-crypto_kx)
* [One-time authentication](SodiumPlus/one-time-authentication.md)
* [crypto_onetimeauth](SodiumPlus/one-time-authentication.md#crypto_onetimeauth)
* [crypto_onetimeauth_verify](SodiumPlus/one-time-authentication.md#crypto_onetimeauth_verify)
* [crypto_onetimeauth_keygen](SodiumPlus/one-time-authentication.md#crypto_onetimeauth_keygen)
* [Example for crypto_onetimeauth](SodiumPlus/one-time-authentication.md#example-for-crypto_onetimeauth)
* [Password-based key derivation](SodiumPlus/password-based-key-derivation.md)
* [crypto_pwhash](SodiumPlus/password-based-key-derivation.md#crypto_pwhash)
* [Example for crypto_pwhash](SodiumPlus/password-based-key-derivation.md#example-for-crypto_pwhash)
* [Password hashing and storage](SodiumPlus/password-hashing-and-storage.md)
* [crypto_pwhash_str](SodiumPlus/password-hashing-and-storage.md#crypto_pwhash_str)
* [crypto_pwhash_str_needs_rehash](SodiumPlus/password-hashing-and-storage.md#crypto_pwhash_str_needs_rehash)
* [crypto_pwhash_str_verify](SodiumPlus/password-hashing-and-storage.md#crypto_pwhash_str_verify)
* [Example for crypto_pwhash_str](SodiumPlus/password-hashing-and-storage.md#example-for-crypto_pwhash_str)
* [Scalar multiplication over Curve25519 (advanced)](SodiumPlus/scalar-multiplication.md)
* [crypto_scalarmult](SodiumPlus/scalar-multiplication.md#crypto_scalarmult)
* [crypto_scalarmult_base](SodiumPlus/scalar-multiplication.md#crypto_scalarmult_base)
* [Example for crypto_scalarmult](SodiumPlus/scalar-multiplication.md#example-for-crypto_scalarmult)
* [Shared-key authenticated encryption](SodiumPlus/shared-key-authenticated-encryption.md)
* [crypto_secretbox](SodiumPlus/shared-key-authenticated-encryption.md#crypto_secretbox)
* [crypto_secretbox_open](SodiumPlus/shared-key-authenticated-encryption.md#crypto_secretbox_open)
* [crypto_secretbox_keygen](SodiumPlus/shared-key-authenticated-encryption.md#crypto_secretbox_keygen)
* [Example for crypto_secretbox](SodiumPlus/shared-key-authenticated-encryption.md#example-for-crypto_secretbox)
* [Encrypted streams](SodiumPlus/encrypted-streams.md)
* [crypto_secretstream_xchacha20poly1305_init_push](SodiumPlus/encrypted-streams.md#crypto_secretstream_xchacha20poly1305_init_push)
* [crypto_secretstream_xchacha20poly1305_init_pull](SodiumPlus/encrypted-streams.md#crypto_secretstream_xchacha20poly1305_init_pull)
* [crypto_secretstream_xchacha20poly1305_push](SodiumPlus/encrypted-streams.md#crypto_secretstream_xchacha20poly1305_push)
* [crypto_secretstream_xchacha20poly1305_pull](SodiumPlus/encrypted-streams.md#crypto_secretstream_xchacha20poly1305_pull)
* [crypto_secretstream_xchacha20poly1305_keygen](SodiumPlus/encrypted-streams.md#crypto_secretstream_xchacha20poly1305_keygen)
* [crypto_secretstream_xchacha20poly1305_rekey](SodiumPlus/encrypted-streams.md#crypto_secretstream_xchacha20poly1305_rekey)
* [Example for crypto_secretstream_xchacha20poly1305](SodiumPlus/encrypted-streams.md#example-for-crypto_secretstream_xchacha20poly1305)
* [Short-input hashing](SodiumPlus/short-input-hashing.md)
* [crypto_shorthash](SodiumPlus/short-input-hashing.md#crypto_shorthash)
* [crypto_shorthash_keygen](SodiumPlus/short-input-hashing.md#crypto_shorthash_keygen)
* [Example for crypto_shorthash](SodiumPlus/short-input-hashing.md#example-for-crypto_shorthash)
* [Digital signatures](SodiumPlus/digital-signatures.md)
* [crypto_sign](SodiumPlus/digital-signatures.md#crypto_sign)
* [crypto_sign_open](SodiumPlus/digital-signatures.md#crypto_sign_open)
* [crypto_sign_detached](SodiumPlus/digital-signatures.md#crypto_sign_detached)
* [crypto_sign_verify_detached](SodiumPlus/digital-signatures.md#crypto_sign_verify_detached)
* [crypto_sign_keypair](SodiumPlus/digital-signatures.md#crypto_sign_keypair)
* [crypto_sign_publickey](SodiumPlus/digital-signatures.md#crypto_sign_publickey)
* [crypto_sign_secretkey](SodiumPlus/digital-signatures.md#crypto_sign_secretkey)
* [crypto_sign_ed25519_sk_to_curve25519](SodiumPlus/digital-signatures.md#crypto_sign_ed25519_sk_to_curve25519)
* [crypto_sign_ed25519_pk_to_curve25519](SodiumPlus/digital-signatures.md#crypto_sign_ed25519_pk_to_curve25519)
* [Example for crypto_sign](SodiumPlus/digital-signatures.md#example-for-crypto_sign)
* [Randomness](SodiumPlus/randomness.md)
* [randombytes_buf](SodiumPlus/randomness.md#randombytes_buf)
* [randombytes_uniform](SodiumPlus/randomness.md#randombytes_uniform)
* [Example for randombytes](SodiumPlus/randomness.md#example-for-randombytes)
* [Utilities](SodiumPlus/utilities.md)
* [sodium_bin2hex](SodiumPlus/utilities.md#sodium_bin2hex)
* [sodium_hex2bin](SodiumPlus/utilities.md#sodium_bin2hex)

View file

@ -0,0 +1,64 @@
## AEAD
> **See also:** [Libsodium's documentation on its AEAD features](https://download.libsodium.org/doc/secret-key_cryptography/aead/chacha20-poly1305/xchacha20-poly1305_construction).
### crypto_aead_xchacha20poly1305_ietf_decrypt
Decrypt a message (and optional associated data) with XChaCha20-Poly1305.
**Parameters and their respective types**:
1. `{string|Buffer}` Ciphertext
2. `{string|Buffer}` nonce (must be 24 bytes)
3. `{CryptographyKey}` key
4. `{string|Buffer}` assocData
Returns a `Promise` that resolves to a `Buffer`.
Throws a `SodiumError` on decryption failure.
### crypto_aead_xchacha20poly1305_ietf_encrypt
Encrypt a message (and optional associated data) with XChaCha20-Poly1305.
**Parameters and their respective types**:
1. `{string|Buffer}` Plaintext
2. `{string|Buffer}` nonce (must be 24 bytes)
3. `{CryptographyKey}` key
4. `{string|Buffer}` assocData
Returns a `Promise` that resolves to a `Buffer`.
### crypto_aead_xchacha20poly1305_ietf_keygen
Returns a `CryptographyKey` object containing a key appropriate
for the `crypto_aead_xchacha20poly1305_ietf_` API.
### Example for crypto_aead_xchacha20poly1305_ietf_*
```javascript
const { SodiumPlus } = require('sodium-plus');
let sodium;
(async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let plaintext = 'Your message goes here';
let key = await sodium.crypto_aead_xchacha20poly1305_ietf_keygen();
let nonce = await sodium.randombytes_buf(24);
let ciphertext = await sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
plaintext,
nonce,
key
);
console.log(ciphertext.toString('hex'));
let decrypted = await sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
ciphertext,
nonce,
key
);
console.log(decrypted.toString());
})();
```

View file

@ -0,0 +1,99 @@
# SodiumPlus Methods
This describes the methods in the public API for Sodium-Plus.
If you're not sure which method to use, please refer to the
[Libsodium Quick Reference](https://paragonie.com/blog/2017/06/libsodium-quick-reference-quick-comparison-similar-functions-and-which-one-use)
for guidance.
* [AEAD (XChaCha20-Poly1305)](AEAD.md#aead)
* [crypto_aead_xchacha20poly1305_ietf_decrypt](AEAD.md#crypto_aead_xchacha20poly1305_ietf_decrypt)
* [crypto_aead_xchacha20poly1305_ietf_encrypt](AEAD.md#crypto_aead_xchacha20poly1305_ietf_encrypt)
* [crypto_aead_xchacha20poly1305_ietf_keygen](AEAD.md#crypto_aead_xchacha20poly1305_ietf_keygen)
* [Example for crypto_aead_xchacha20poly1305_ietf_*](AEAD.md#example-for-crypto_aead_xchacha20poly1305_ietf_)
* [Shared-key authentication](shared-key-authentication.md)
* [crypto_auth](shared-key-authentication.md#crypto_auth)
* [crypto_auth_verify](shared-key-authentication.md#crypto_auth_verify)
* [crypto_auth_keygen](shared-key-authentication.md#crypto_auth_keygen)
* [Example for crypto_auth](shared-key-authentication.md#example-for-crypto_auth)
* [Authenticated public-key encryption](authenticated-public-key-encryption.md)
* [crypto_box](authenticated-public-key-encryption.md#crypto_box)
* [crypto_box_open](authenticated-public-key-encryption.md#crypto_box_open)
* [crypto_box_keypair](authenticated-public-key-encryption.md#crypto_box_keypair)
* [crypto_box_keypair_from_secretkey_and_secretkey](authenticated-public-key-encryption.md#crypto_box_keypair_from_secretkey_and_secretkey)
* [crypto_box_publickey](authenticated-public-key-encryption.md#crypto_box_publickey)
* [crypto_box_secretkey](authenticated-public-key-encryption.md#crypto_box_secretkey)
* [crypto_box_publickey_from_secretkey](authenticated-public-key-encryption.md#crypto_box_publickey_from_secretkey)
* [Example for crypto_box](authenticated-public-key-encryption.md#example-for-crypto_box)
* [Sealed boxes (anonymous public-key encryption)](sealed-boxes.md)
* [crypto_box_seal](sealed-boxes.md#crypto_box_seal)
* [crypto_box_seal_open](sealed-boxes.md#crypto_box_seal_open)
* [Example for crypto_box_seal](sealed-boxes.md#example-for-crypto_box_seal)
* [General-purpose cryptographic hash](general-purpose-cryptographic-hash.md)
* [crypto_generichash](general-purpose-cryptographic-hash.md#crypto_generichash)
* [crypto_generichash_init](general-purpose-cryptographic-hash.md#crypto_generichash_init)
* [crypto_generichash_update](general-purpose-cryptographic-hash.md#crypto_generichash_update)
* [crypto_generichash_final](general-purpose-cryptographic-hash.md#crypto_generichash_final)
* [crypto_generichash_keygen](general-purpose-cryptographic-hash.md#crypto_generichash_keygen)
* [Example for crypto_generichash](general-purpose-cryptographic-hash.md#example-for-crypto_generichash)
* [Key derivation](key-derivation.md)
* [crypto_kdf_derive_from_key](key-derivation.md#crypto_kdf_derive_from_key)
* [crypto_kdf_keygen](key-derivation.md#crypto_kdf_keygen)
* [Example for crypto_kdf](key-derivation.md#example-for-crypto_kdf)
* [Key exchange](key-exchange.md)
* [crypto_kx_keypair](key-exchange.md#crypto_kx_keypair)
* [crypto_kx_seed_keypair](key-exchange.md#crypto_kx_seed_keypair)
* [crypto_kx_client_session_keys](key-exchange.md#crypto_kx_client_session_keys)
* [crypto_kx_server_session_keys](key-exchange.md#crypto_kx_server_session_keys)
* [Example for crypto_kx](key-exchange.md#example-for-crypto_kx)
* [One-time authentication](one-time-authentication.md)
* [crypto_onetimeauth](one-time-authentication.md#crypto_onetimeauth)
* [crypto_onetimeauth_verify](one-time-authentication.md#crypto_onetimeauth_verify)
* [crypto_onetimeauth_keygen](one-time-authentication.md#crypto_onetimeauth_keygen)
* [Example for crypto_onetimeauth](one-time-authentication.md#example-for-crypto_onetimeauth)
* [Password-based key derivation](password-based-key-derivation.md)
* [crypto_pwhash](password-based-key-derivation.md#crypto_pwhash)
* [Example for crypto_pwhash](password-based-key-derivation.md#example-for-crypto_pwhash)
* [Password hashing and storage](password-hashing-and-storage.md)
* [crypto_pwhash_str](password-hashing-and-storage.md#crypto_pwhash_str)
* [crypto_pwhash_str_needs_rehash](password-hashing-and-storage.md#crypto_pwhash_str_needs_rehash)
* [crypto_pwhash_str_verify](password-hashing-and-storage.md#crypto_pwhash_str_verify)
* [Example for crypto_pwhash_str](password-hashing-and-storage.md#example-for-crypto_pwhash_str)
* [Scalar multiplication over Curve25519 (advanced)](scalar-multiplication.md)
* [crypto_scalarmult](scalar-multiplication.md#crypto_scalarmult)
* [crypto_scalarmult_base](scalar-multiplication.md#crypto_scalarmult_base)
* [Example for crypto_scalarmult](scalar-multiplication.md#example-for-crypto_scalarmult)
* [Shared-key authenticated encryption](shared-key-authenticated-encryption.md)
* [crypto_secretbox](shared-key-authenticated-encryption.md#crypto_secretbox)
* [crypto_secretbox_open](shared-key-authenticated-encryption.md#crypto_secretbox_open)
* [crypto_secretbox_keygen](shared-key-authenticated-encryption.md#crypto_secretbox_keygen)
* [Example for crypto_secretbox](shared-key-authenticated-encryption.md#example-for-crypto_secretbox)
* [Encrypted streams](encrypted-streams.md)
* [crypto_secretstream_xchacha20poly1305_init_push](encrypted-streams.md#crypto_secretstream_xchacha20poly1305_init_push)
* [crypto_secretstream_xchacha20poly1305_init_pull](encrypted-streams.md#crypto_secretstream_xchacha20poly1305_init_pull)
* [crypto_secretstream_xchacha20poly1305_push](encrypted-streams.md#crypto_secretstream_xchacha20poly1305_push)
* [crypto_secretstream_xchacha20poly1305_pull](encrypted-streams.md#crypto_secretstream_xchacha20poly1305_pull)
* [crypto_secretstream_xchacha20poly1305_keygen](encrypted-streams.md#crypto_secretstream_xchacha20poly1305_keygen)
* [crypto_secretstream_xchacha20poly1305_rekey](encrypted-streams.md#crypto_secretstream_xchacha20poly1305_rekey)
* [Example for crypto_secretstream_xchacha20poly1305](encrypted-streams.md#example-for-crypto_secretstream_xchacha20poly1305)
* [Short-input hashing](short-input-hashing.md)
* [crypto_shorthash](short-input-hashing.md#crypto_shorthash)
* [crypto_shorthash_keygen](short-input-hashing.md#crypto_shorthash_keygen)
* [Example for crypto_shorthash](short-input-hashing.md#example-for-crypto_shorthash)
* [Digital signatures](digital-signatures.md)
* [crypto_sign](digital-signatures.md#crypto_sign)
* [crypto_sign_open](digital-signatures.md#crypto_sign_open)
* [crypto_sign_detached](digital-signatures.md#crypto_sign_detached)
* [crypto_sign_verify_detached](digital-signatures.md#crypto_sign_verify_detached)
* [crypto_sign_keypair](digital-signatures.md#crypto_sign_keypair)
* [crypto_sign_publickey](digital-signatures.md#crypto_sign_publickey)
* [crypto_sign_secretkey](digital-signatures.md#crypto_sign_secretkey)
* [crypto_sign_ed25519_sk_to_curve25519](digital-signatures.md#crypto_sign_ed25519_sk_to_curve25519)
* [crypto_sign_ed25519_pk_to_curve25519](digital-signatures.md#crypto_sign_ed25519_pk_to_curve25519)
* [Example for crypto_sign](digital-signatures.md#example-for-crypto_sign)
* [Randomness](randomness.md)
* [randombytes_buf](randomness.md#randombytes_buf)
* [randombytes_uniform](randomness.md#randombytes_uniform)
* [Example for randombytes](randomness.md#example-for-randombytes)
* [Utilities](utilities.md)
* [sodium_bin2hex](utilities.md#sodium_bin2hex)
* [sodium_hex2bin](utilities.md#sodium_bin2hex)

View file

@ -0,0 +1,99 @@
## Authenticated public-key encryption
> **See also**: [Libsodium's documentation on its public-key authenticated encryption features](https://download.libsodium.org/doc/public-key_cryptography/authenticated_encryption).
### crypto_box
Public-key authenticated encryption.
**Parameters and their respective types**:
1. `{string|Buffer}` plaintext
2. `{Buffer}` nonce (must be 24 bytes)
3. `{X25519SecretKey}` secret key
4. `{X25519PublicKey}` public key
Returns a `Promise` that resolves to a `Buffer`.
### crypto_box_open
Public-key authenticated encryption.
**Parameters and their respective types**:
1. `{Buffer}` ciphertext
2. `{Buffer}` nonce (must be 24 bytes)
3. `{X25519SecretKey}` secret key
4. `{X25519PublicKey}` public key
Returns a `Promise` that resolves to a `Buffer`.
Throws a `SodiumError` on decryption failure.
### crypto_box_keypair
Returns a `Promise` that resolves to a `CryptographyKey` containing a 64-byte
`Buffer`. The first 32 bytes are your X25519 secret key, the latter 32 are your
X25519 public key.
### crypto_box_keypair_from_secretkey_and_publickey
Combine two X25519 keys (secret, public) into a keypair object.
**Parameters and their respective types**:
1. `{X25519SecretKey}` secret key
2. `{X25519PublicKey}` public key
Returns a `Promise` that resolves to a `CryptographyKey`.
### crypto_box_publickey
**Parameters and their respective types**:
1. `{CryptographyKey}` (buffer must be 64 bytes long)
Returns a `Promise` that resolves to a `X25519PublicKey`.
### crypto_box_secretkey
**Parameters and their respective types**:
1. `{CryptographyKey}` (buffer must be 64 bytes long)
Returns a `Promise` that resolves to a `X25519SecretKey`.
### crypto_box_publickey_from_secretkey
Derive the public key from a given X25519 secret key.
**Parameters and their respective types**:
1. `{X25519SecretKey}`
Returns a `Promise` that resolves to a `X25519PublicKey`.
### Example for crypto_box
```javascript
const { SodiumPlus } = require('sodium-plus');
let sodium;
(async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let aliceKeypair = await sodium.crypto_box_keypair();
let aliceSecret = await sodium.crypto_box_secretkey(aliceKeypair);
let alicePublic = await sodium.crypto_box_publickey(aliceKeypair);
let bobKeypair = await sodium.crypto_box_keypair();
let bobSecret = await sodium.crypto_box_secretkey(bobKeypair);
let bobPublic = await sodium.crypto_box_publickey(bobKeypair);
let plaintext = 'Your message goes here';
let nonce = await sodium.randombytes_buf(24);
let ciphertext = await sodium.crypto_box(plaintext, nonce, aliceSecret, bobPublic);
console.log(ciphertext);
let decrypted = await sodium.crypto_box_open(ciphertext, nonce, bobSecret, alicePublic);
console.log(decrypted.toString());
})();
```

View file

@ -0,0 +1,134 @@
## Digital signatures
> **See also**: [Libsodium's documentation on its public-key signature features](https://download.libsodium.org/doc/public-key_cryptography/public-key_signatures).
### crypto_sign
> See also: [the detached API](#crypto_sign_detached) below.
Sign a message with Ed25519, returning a signed message (prefixed with the signature).
**Parameters and their respective types**:
1. `{string|Buffer}` message
2. `{Ed25519SecretKey}` secretKey
Returns a `Promise` that resolves to a `Buffer`.
### crypto_sign_open
Verify a signed message with Ed25519, returning the original message if the signature
is valid.
**Parameters and their respective types**:
1. `{string|Buffer}` signedMessage
2. `{Ed25519SecretKey}` publicKey
Returns a `Promise` that resolves to a `Buffer`.
### crypto_sign_detached
Returns the Ed25519 signature of the message, for the given secret key.
**Parameters and their respective types**:
1. `{string|Buffer}` message
2. `{Ed25519SecretKey}` secretKey
Returns a `Promise` that resolves to a `Buffer`.
### crypto_sign_verify_detached
Returns true if the Ed25519 signature is valid for a given message and public key.
**Parameters and their respective types**:
1. `{string|Buffer}` message
2. `{Ed25519PublicKey}` publicKey
3. `{Buffer}` signature
Returns a `Promise` that resolves to a `boolean`.
### crypto_sign_keypair
Returns a `Promise` that resolves to a `CryptographyKey` containing a 96-byte
`Buffer`. The first 64 bytes are your Ed25519 secret key, the latter 32 are your
Ed25519 public key.
### crypto_sign_seed_keypair
**Parameters and their respective types**:
1. `{Buffer}` 32 byte seed
Returns a `Promise` that resolves to a `CryptographyKey` containing a 96-byte
`Buffer`. The first 64 bytes are your Ed25519 secret key, the latter 32 are your
Ed25519 public key.
### crypto_sign_publickey
**Parameters and their respective types**:
1. `{CryptographyKey}` (buffer must be 96 bytes long)
Returns a `Promise` that resolves to a `Ed25519PublicKey`.
### crypto_sign_secretkey
**Parameters and their respective types**:
1. `{CryptographyKey}` (buffer must be 96 bytes long)
Returns a `Promise` that resolves to a `Ed25519SecretKey`.
### crypto_sign_ed25519_sk_to_curve25519
Obtain a birationally equivalent X25519 secret key, given an Ed25519 secret key.
**Parameters and their respective types**:
1. `{Ed25519SecretKey}`
Returns a `Promise` that resolves to an `X25519SecretKey`.
### crypto_sign_ed25519_pk_to_curve25519
Obtain a birationally equivalent X25519 public key, given an Ed25519 public key.
**Parameters and their respective types**:
1. `{Ed25519PublicKey}`
Returns a `Promise` that resolves to an `X25519PublicKey`.
### Example for crypto_sign
```javascript
const { SodiumPlus } = require('sodium-plus');
let sodium;
(async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let aliceKeypair = await sodium.crypto_sign_keypair();
let aliceSecret = await sodium.crypto_sign_secretkey(aliceKeypair);
let alicePublic = await sodium.crypto_sign_publickey(aliceKeypair);
let message = 'This is something I need to sign publicly.';
// Detached mode:
let signature = await sodium.crypto_sign_detached(message, aliceSecret);
console.log(signature.toString('hex'));
if (await sodium.crypto_sign_verify_detached(message, alicePublic, signature)) {
console.log("Signature is valid.");
} else {
console.error("Invalid signature!");
}
// NaCl (crypto_sign / crypto_sign_open):
let signed = await sodium.crypto_sign(message, aliceSecret);
let opened = await sodium.crypto_sign_open(signed, alicePublic);
console.log(opened.toString());
})();
```

View file

@ -0,0 +1,138 @@
## Encrypted Streams
> **See also:** [Libsodium's documentation on its encrypted streams feature](https://download.libsodium.org/doc/secret-key_cryptography/secretstream)
### crypto_secretstream_xchacha20poly1305_init_push()
Initialize a stream for streaming encryption.
**Parameters and their respective types**:
1. `{CryptographyKey}` key
Returns a `Promise` that resolves to an `array` with 2 elements:
1. A 24-byte header that should be included in the encrypted stream.
2. A backend-specific `state`:
* `LibsodiumWrappers` returns a `number` (a pointer to an internal buffer)
* `SodiumNative` returns a `CryptoSecretstreamXchacha20poly1305StateWrap`
object
The `{state}` type annotation below refers to one of the backend-specific state
types.
You'll typically want to use it with list unpacking syntax, like so:
```
[state, header] = await sodium.crypto_secretstream_xchacha20poly1305_init_push(key);
```
### crypto_secretstream_xchacha20poly1305_init_pull()
Initialize a stream for streaming decryption.
**Parameters and their respective types**:
1. `{CryptographyKey}` key
2. `{Buffer}` header (must be 24 bytes)
Returns a `Promise` that resolves to a backend-specific `state`:
* `LibsodiumWrappers` returns a `number` (a pointer to an internal buffer)
* `SodiumNative` returns a `CryptoSecretstreamXchacha20poly1305StateWrap`
object
The `{state}` type annotation below refers to one of the backend-specific state
types.
### crypto_secretstream_xchacha20poly1305_push()
Encrypt some data in a stream.
**Parameters and their respective types**:
1. `{state}` state
2. `{string|Buffer}` message
3. `{string|Buffer}` (optional) additional associated data
4. `{number}` tag (default = 0, see libsodium docs)
Returns a `Promise` that resolves to a `Buffer` containing the ciphertext.
### crypto_secretstream_xchacha20poly1305_pull()
Decrypt some data in a stream.
**Parameters and their respective types**:
1. `{state}` state
2. `{string|Buffer}` ciphertext
3. `{string|Buffer}` (optional) additional associated data
4. `{number}` tag (default = 0, see libsodium docs)
Returns a `Promise` that resolves to a `Buffer` containing
decrypted plaintext.
### crypto_secretstream_xchacha20poly1305_keygen()
Returns a `CryptographyKey` object containing a key appropriate
for the `crypto_secretstream` API.
### crypto_secretstream_xchacha20poly1305_rekey()
Deterministic re-keying of the internal state.
**Parameters and their respective types**:
1. `{state}` state
Returns a `Promise` that resolves to `undefined`. Instead,
the `state` variable is overwritten in-place.
### Example for crypto_secretstream_xchacha20poly1305
```javascript
const fsp = require('fs').promises;
const path = require('path');
const { SodiumPlus } = require('sodium-plus');
let sodium;
(async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let key = await sodium.crypto_secretstream_xchacha20poly1305_keygen();
let pushState, pullState, header;
[pushState, header] = await sodium.crypto_secretstream_xchacha20poly1305_init_push(key);
// Get a test input from the text file.
let longText = await fsp.readFile(path.join(__dirname, 'encrypted-streams.md'));
let chunk, readUntil;
let ciphertext = Buffer.concat([header]);
// How big are our chunks going to be?
let PUSH_CHUNK_SIZE = await sodium.randombytes_uniform(longText.length - 32) + 32;
let PULL_CHUNK_SIZE = PUSH_CHUNK_SIZE + sodium.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES;
// Encryption...
for (let i = 0; i < longText.length; i += PUSH_CHUNK_SIZE) {
readUntil = (i + PUSH_CHUNK_SIZE) > longText.length ? longText.length : i + PUSH_CHUNK_SIZE;
chunk = await sodium.crypto_secretstream_xchacha20poly1305_push(
pushState,
longText.slice(i, readUntil)
);
ciphertext = Buffer.concat([ciphertext, chunk]);
}
pullState = await sodium.crypto_secretstream_xchacha20poly1305_init_pull(key, header);
// Decrypt, starting at 24 (after the header, which we already have)
let decrypted = Buffer.alloc(0);
for (let i = 24; i < ciphertext.length; i += PULL_CHUNK_SIZE) {
readUntil = (i + PULL_CHUNK_SIZE) > ciphertext.length ? ciphertext.length : i + PULL_CHUNK_SIZE;
chunk = await sodium.crypto_secretstream_xchacha20poly1305_pull(
pullState,
ciphertext.slice(i, readUntil)
);
decrypted = Buffer.concat([decrypted, chunk]);
}
console.log(decrypted.toString());
})();
```

View file

@ -0,0 +1,82 @@
## General-purpose cryptographic hash
> **See also**: [Libsodium's documentation on its generic hashing features](https://download.libsodium.org/doc/hashing/generic_hashing).
### crypto_generichash
General-purpose cryptographic hash (powered by BLAKE2).
**Parameters and their respective types**:
1. `{Buffer}` message
2. `{CryptographyKey|null}` key (optional)
3. `{number}` output length (optional, defaults to 32)
Returns a `Promise` that resolves to a `Buffer`.
### crypto_generichash_keygen
Returns a `CryptographyKey` object containing a key appropriate
for the `crypto_generichash` API.
### crypto_generichash_init
Initialize a BLAKE2 hash context for stream hashing.
**Parameters and their respective types**:
1. `{CryptographyKey|null}` key (optional)
2. `{number}` output length (optional, defaults to 32)
Returns a `Promise` that resolves to... well, that depends on your backend.
* sodium-native returns a `CryptoGenericHashWrap` object.
* libsodium-wrappers returns a number (a buffer's memory address)
### crypto_generichash_update
Update the BLAKE2 hash state with a block of data.
**Parameters and their respective types**:
1. `{*}` hash state (see [crypto_generichash_init()](#crypto_generichash_init))
2. `{string|Buffer}` message chunk
Returns a `Promise` that resolves to `void`. Instead, `state` is updated in-place.
### crypto_generichash_final
Obtain the final BLAKE2 hash output.
**Parameters and their respective types**:
1. `{*}` hash state (see [crypto_generichash_init()](#crypto_generichash_init))
2. `{number}` output length (optional, defaults to 32)
Returns a `Promise` that resolves to a `Buffer`.
### Example for crypto_generichash
```javascript
const { SodiumPlus } = require('sodium-plus');
let sodium;
(async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let message = 'Any message can go here';
let hashed = await sodium.crypto_generichash(message);
console.log(hashed.toString('hex'));
let key = await sodium.crypto_generichash_keygen();
let hash2 = await sodium.crypto_generichash(message, key, 64);
let state = await sodium.crypto_generichash_init(key, 64);
await sodium.crypto_generichash_update(state, 'Any message ');
await sodium.crypto_generichash_update(state, 'can go here');
let hash3 = await sodium.crypto_generichash_final(state, 64);
if (!await sodium.sodium_memcmp(hash2, hash3)) {
throw new Error('Implementation is broken. You should never see this.');
}
console.log(hash2.toString('hex'));
})();
```

View file

@ -0,0 +1,45 @@
## Key derivation
> **See also**: [Libsodium's documentation on its key derivation features](https://download.libsodium.org/doc/key_derivation).
### crypto_kdf_derive_from_key
Derive a subkey from a master key.
**Parameters and their respective types**:
1. `{number}` output length (typically you want `32`)
2. `{number}` subkey ID
3. `{string|Buffer}` context (must be a string/buffer of length 8)
4. `{CryptographyKey}` master key
Returns a `Promise` that resolves to a `CryptographyKey`.
### crypto_kdf_keygen
Returns a `CryptographyKey` object containing a key appropriate
for the `crypto_kdf` API.
### Example for crypto_kdf
```javascript
const { SodiumPlus } = require('sodium-plus');
let sodium;
(async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let masterKey = await sodium.crypto_kdf_keygen();
let context = 'Sodium++';
let subkey1 = await sodium.crypto_kdf_derive_from_key(32, 1, context, masterKey);
let subkey2 = await sodium.crypto_kdf_derive_from_key(32, 2, context, masterKey);
let subkey3 = await sodium.crypto_kdf_derive_from_key(32, 3, context, masterKey);
console.log({
'master-key': masterKey.getBuffer().toString('hex'),
'subkey1': subkey1.getBuffer().toString('hex'),
'subkey2': subkey2.getBuffer().toString('hex'),
'subkey3': subkey3.getBuffer().toString('hex')
});
})();
```

View file

@ -0,0 +1,94 @@
## Key exchange
> **See also**: [Libsodium's documentation on its key exchange features](https://download.libsodium.org/doc/key_exchange).
### crypto_kx_keypair
This is functionally identical to [`crypto_box_keypair()`](#crypto_box_keypair).
Returns a `Promise` that resolves to a `CryptographyKey` with 64 bytes.
### crypto_kx_seed_keypair
Generate an X25519 keypair from a seed. Unlike `crypto_kx_seedpair()`, this is
deterministic from your seed.
**Parameters and their respective types**:
1. `{string|Buffer}` seed
Returns a `Promise` that resolves to a `CryptographyKey` with 64 bytes.
### crypto_kx_client_session_keys
Perform a key exchange from the client's perspective.
Returns an array of two CryptographyKey objects:
* The first is meant for data sent from the server to the client (incoming decryption).
* The second is meant for data sent from the client to the server (outgoing encryption).
**Parameters and their respective types**:
1. `{X25519PublicKey}` client public key (yours)
2. `{X25519SecretKey}` client secret key (yours)
1. `{X25519PublicKey}` server public key (theirs)
Returns a `Promise` that resolves to an array of two `CryptographyKey` objects.
### crypto_kx_server_session_keys
Perform a key exchange from the server's perspective.
Returns an array of two CryptographyKey objects:
* The first is meant for data sent from the client to the server (incoming decryption).
* The second is meant for data sent from the server to the client (outgoing encryption).
**Parameters and their respective types**:
1. `{X25519PublicKey}` server public key (yours)
2. `{X25519SecretKey}` server secret key (yours)
1. `{X25519PublicKey}` client public key (theirs)
Returns a `Promise` that resolves to an array of two `CryptographyKey` objects.
### Example for crypto_kx
```javascript
const { SodiumPlus } = require('sodium-plus');
let sodium;
(async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let clientKeypair = await sodium.crypto_box_keypair();
let clientSecret = await sodium.crypto_box_secretkey(clientKeypair);
let clientPublic = await sodium.crypto_box_publickey(clientKeypair);
let serverKeypair = await sodium.crypto_kx_seed_keypair('Your static input goes here');
let serverSecret = await sodium.crypto_box_secretkey(serverKeypair);
let serverPublic = await sodium.crypto_box_publickey(serverKeypair);
let clientIKey, clientOKey, serverIKey, serverOKey;
[clientIKey, clientOKey] = await sodium.crypto_kx_client_session_keys(
clientPublic,
clientSecret,
serverPublic
);
[serverIKey, serverOKey] = await sodium.crypto_kx_server_session_keys(
serverPublic,
serverSecret,
clientPublic
);
console.log({
'client-sees': {
'incoming': clientIKey.getBuffer().toString('hex'),
'outgoing': clientOKey.getBuffer().toString('hex')
},
'server-sees': {
'incoming': serverIKey.getBuffer().toString('hex'),
'outgoing': serverOKey.getBuffer().toString('hex')
}
});
})();
```

View file

@ -0,0 +1,52 @@
## One-time authentication
> **See also**: [Libsodium's documentation on its one-time authentication features](https://download.libsodium.org/doc/advanced/poly1305).
### crypto_onetimeauth
Get an authenticator for a message for a given key.
**Important:** In order to be secure, keys must be:
1. Secret.
2. Unpredictable.
3. Unique.
**Parameters and their respective types**:
1. `{string|Buffer}` message
2. `{CryptographyKey}` key
Return a `Promise` that resolves to a `Buffer`.
### crypto_onetimeauth_verify
Verify an authenticator for a message for a given key.
**Parameters and their respective types**:
1. `{string|Buffer}` message
2. `{CryptographyKey}` key
2. `{Buffer}` tag
Return a `Promise` that resolves to a `boolean`.
### crypto_onetimeauth_keygen
Returns a `CryptographyKey` object containing a key appropriate
for the `crypto_auth` API.
### Example for crypto_onetimeauth
```javascript
const { SodiumPlus } = require('sodium-plus');
let sodium;
(async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let plaintext = 'Your message goes here';
let key = await sodium.crypto_onetimeauth_keygen();
let tag = await sodium.crypto_onetimeauth(plaintext, key);
console.log(await sodium.crypto_onetimeauth_verify(plaintext, key, tag));
})();
```

View file

@ -0,0 +1,45 @@
## Password-based key derivation
> **See also**: [Libsodium's documentation on Argon2](https://download.libsodium.org/doc/password_hashing/the_argon2i_function).
### crypto_pwhash
Derive a cryptography key from a password and salt.
**Parameters and their respective types**:
1. `{number}` output length
2. `{string|Buffer}` password
3. `{Buffer}` salt (16 bytes)
4. `{number}` opslimit (recommeded minimum: `2`)
5. `{number}` memlimit (recommended mimimum: `67108864` a.k.a. 64MiB)
6. `{number|null}` algorithm (recommended: `this.CRYPTO_PWHASH_ALG_DEFAULT`)
Returns a `Promise` that resolves to a `CryptographyKey`.
### Example for crypto_pwhash
This example is for key derivation. Look [below](#example-for-crypto_pwhash_str)
for information about password storage/verification.
```javascript
const { SodiumPlus } = require('sodium-plus');
let sodium;
(async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let password = 'correct horse battery staple';
let salt = await sodium.randombytes_buf(16);
let key = await sodium.crypto_pwhash(
32,
password,
salt,
sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);
console.log(key.getBuffer().toString('hex'));
})();
```

View file

@ -0,0 +1,74 @@
## Password hashing and storage
> **See also**: [Libsodium's documentation on its password hashing features](https://download.libsodium.org/doc/password_hashing).
### crypto_pwhash_str
Get a password hash (in a safe-for-storage format).
**Parameters and their respective types**:
1. `{string|Buffer}` password
2. `{number}` opslimit (recommeded minimum: `2`)
3. `{number}` memlimit (recommended mimimum: `67108864` a.k.a. 64MiB)
Returns a `Promise` that resolves to a `string`.
### crypto_pwhash_str_needs_rehash
Does this password need to be rehashed? (i.e. have the algorithm parameters
we want changed since the hash was generated?)
**Parameters and their respective types**:
1. `{string}` password hash
2. `{number}` opslimit (recommeded minimum: `2`)
3. `{number}` memlimit (recommended mimimum: `67108864` a.k.a. 64MiB)
Returns a `Promise` that resolves to a `boolean`.
### crypto_pwhash_str_verify
Verify a password against a known password hash.
1. `{string|Buffer}` password
2. `{string}` password hash
Returns a `Promise` that resolves to a `boolean`.
### Example for crypto_pwhash_str
```javascript
const { SodiumPlus } = require('sodium-plus');
let sodium;
(async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let password = 'correct horse battery staple';
// Generating a password hash
let pwhash = await sodium.crypto_pwhash_str(
password,
sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);
console.log(pwhash);
// Check that we don't need to rotate hashes
let stale = await sodium.crypto_pwhash_str_needs_rehash(
pwhash,
sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);
if (stale) {
console.warn('Password needs to be rehashed');
}
// Password validation
if (await sodium.crypto_pwhash_str_verify(password, pwhash)) {
console.log("Password valid");
} else {
console.error("Incorrect password");
}
})();
```

View file

@ -0,0 +1,43 @@
## Randomness
> **See also:** [Libsodium's documentation on its random data features](https://download.libsodium.org/doc/generating_random_data).
### randombytes_buf
Obtain a buffer filled with random bytes.
**Parameters and their respective types**:
1. `{number}` Size of buffer to return
Returns a `Promise` that resolves to a `Buffer`
### randombytes_uniform
Generate an integer between 0 and upperBound (non-inclusive).
For example, randombytes_uniform(10) returns an integer between 0 and 9.
**Parameters and their respective types**:
1. `{number}` Upper bound
Returns a `Promise` that resolves to a `number`.
### Example for randombytes
```javascript
const { SodiumPlus } = require('sodium-plus');
let sodium;
(async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let someBuf = await sodium.randombytes_buf(32);
console.log(someBuf.toString('hex'));
let someInt = await sodium.randombytes_uniform(65536);
console.log(someInt);
})();
```

View file

@ -0,0 +1,49 @@
## Scalar multiplication over Curve25519
> **See also**: [Libsodium's documentation on its scalar multiplication features](https://download.libsodium.org/doc/advanced/scalar_multiplication).
### crypto_scalarmult
Elliptic Curve Diffie-Hellman key exchange over Curve25519.
You probably don't want to ever use this directly.
**Parameters and their respective types**:
1. `{X25519SecretKey}` your secret key
2. `{X25519PublicKey}` their public key
Returns a `Promise` that resolves to a `CryptographyKey`.
### crypto_scalarmult_base
Generate an X25519PublicKey from an X25519SecretKey.
**Parameters and their respective types**:
1. `{X25519SecretKey}` your secret key
Returns a `Promise` that resolves to an `X25519PublicKey`.
### Example for crypto_scalarmult
```javascript
const { SodiumPlus } = require('sodium-plus');
let sodium;
(async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let aliceKeypair = await sodium.crypto_box_keypair();
let aliceSecret = await sodium.crypto_box_secretkey(aliceKeypair);
let alicePublic = await sodium.crypto_box_publickey(aliceKeypair);
let bobKeypair = await sodium.crypto_box_keypair();
let bobSecret = await sodium.crypto_box_secretkey(bobKeypair);
let bobPublic = await sodium.crypto_scalarmult_base(bobSecret);
let aliceToBob = await sodium.crypto_scalarmult(aliceSecret, bobPublic);
let bobToAlice = await sodium.crypto_scalarmult(bobSecret, alicePublic);
console.log({
'alice-to-bob': aliceToBob.getBuffer().toString('hex'),
'bob-to-alice': bobToAlice.getBuffer().toString('hex')
});
})();
```

View file

@ -0,0 +1,48 @@
## Sealed boxes
> **See also**: [Libsodium's documentation on its sealed boxes features](https://download.libsodium.org/doc/public-key_cryptography/sealed_boxes).
### crypto_box_seal
Anonymous public-key encryption. (Message integrity is still assured.)
**Parameters and their respective types**:
1. `{string|Buffer}` plaintext
2. `{X25519PublicKey}` public key
Returns a `Promise` that resolves to a `Buffer`.
### crypto_box_seal_open
Anonymous public-key decryption. (Message integrity is still assured.)
**Parameters and their respective types**:
1. `{Buffer}` ciphertext
2. `{X25519PublicKey}` public key
3. `{X25519SecretKey}` secret key
Returns a `Promise` that resolves to a `Buffer`.
### Example for crypto_box_seal
```javascript
const { SodiumPlus } = require('sodium-plus');
let sodium;
(async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let aliceKeypair = await sodium.crypto_box_keypair();
let aliceSecret = await sodium.crypto_box_secretkey(aliceKeypair);
let alicePublic = await sodium.crypto_box_publickey(aliceKeypair);
let plaintext = 'Your message goes here';
let ciphertext = await sodium.crypto_box_seal(plaintext, alicePublic);
console.log(ciphertext);
let decrypted = await sodium.crypto_box_seal_open(ciphertext, alicePublic, aliceSecret);
console.log(decrypted.toString());
})();
```

View file

@ -0,0 +1,62 @@
## Shared-key authenticated encryption
> **See also**: [Libsodium's documentation on its shared-key authenticated encryption features](https://download.libsodium.org/doc/secret-key_cryptography/secretbox).
### crypto_secretbox
Shared-key authenticated encryption.
**Parameters and their respective types**:
1. `{string|Buffer}` Plaintext
2. `{string|Buffer}` nonce (must be 24 bytes)
3. `{CryptographyKey}` key
Returns a `Promise` that resolves to a `Buffer`.
### crypto_secretbox_open
Shared-key authenticated decryption.
**Parameters and their respective types**:
1. `{string|Buffer}` Ciphertext
2. `{string|Buffer}` nonce (must be 24 bytes)
3. `{CryptographyKey}` key
Returns a `Promise` that resolves to a `Buffer`.
Throws a `SodiumError` on decryption failure.
### crypto_secretbox_keygen
Returns a `CryptographyKey` object containing a key appropriate
for the `crypto_secretbox` API.
### Example for crypto_secretbox
```javascript
const { SodiumPlus } = require('sodium-plus');
let sodium;
(async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let plaintext = 'Your message goes here';
let key = await sodium.crypto_secretbox_keygen();
let nonce = await sodium.randombytes_buf(24);
let ciphertext = await sodium.crypto_secretbox(
plaintext,
nonce,
key
);
console.log(ciphertext.toString('hex'));
let decrypted = await sodium.crypto_secretbox_open(
ciphertext,
nonce,
key
);
console.log(decrypted.toString());
})();
```

View file

@ -0,0 +1,46 @@
## Shared-key authentication
> **See also**: [Libsodium's documentation on its shared-key authentication features](https://download.libsodium.org/doc/secret-key_cryptography/secret-key_authentication).
### crypto_auth
Get an authenticator for a message for a given key.
**Parameters and their respective types**:
1. `{string|Buffer}` message
2. `{CryptographyKey}` key
Return a `Promise` that resolves to a `Buffer`.
### crypto_auth_verify
Verify an authenticator for a message for a given key.
**Parameters and their respective types**:
1. `{string|Buffer}` message
2. `{CryptographyKey}` key
2. `{Buffer}` mac
Return a `Promise` that resolves to a `boolean`.
### crypto_auth_keygen
Returns a `CryptographyKey` object containing a key appropriate
for the `crypto_auth` API.
### Example for crypto_auth
```javascript
const { SodiumPlus } = require('sodium-plus');
let sodium;
(async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let plaintext = 'Your message goes here';
let key = await sodium.crypto_auth_keygen();
let mac = await sodium.crypto_auth(plaintext, key);
console.log(await sodium.crypto_auth_verify(plaintext, key, mac));
})();
```

View file

@ -0,0 +1,39 @@
## Short-input hashing
> **See also**: [Libsodium's documentation on its short-input hashing features](https://download.libsodium.org/doc/hashing/short-input_hashing).
### crypto_shorthash
Calculate a fast hash for short inputs.
**Parameters and their respective types**:
1. `{string|Buffer}` input
3. `{CryptographyKey}` key
Returns a `Promise` that resolves to a `Buffer`.
### crypto_shorthash_keygen
Returns a `CryptographyKey` object containing a key appropriate
for the `crypto_shorthash` API.
### Example for crypto_shorthash
> **Warning:** You probably want [`crypto_generichash()`](general-purpose-cryptographic-hash.md)
> for most use-cases. `crypto_shorthash()` does not offer collision resistance.
```javascript
const { SodiumPlus } = require('sodium-plus');
let sodium;
(async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let key = await sodium.crypto_shorthash_keygen();
let mapped = {};
mapped['foo'] = (await sodium.crypto_shorthash('foo', key)).toString('hex');
mapped['bar'] = (await sodium.crypto_shorthash('bar', key)).toString('hex');
mapped['baz'] = (await sodium.crypto_shorthash('baz', key)).toString('hex');
console.log(mapped);
})();
```

View file

@ -0,0 +1,55 @@
## Stream Ciphers
> **See also**: [Libsodium's documentation on its stream cipher features](https://download.libsodium.org/doc/advanced/stream_ciphers/xsalsa20).
### crypto_stream
Obtain an arbitrary-length stream of pseudorandom bytes from a given
nonce and key.
**Parameters and their respective types**:
1. `{number}` length
2. `{Buffer}` nonce (must be 24 bytes)
3. `{CryptographyKey}` key
Returns a `Promise` that resolves to a `Buffer` containing
the pseudorandom bytes.
### crypto_stream_xor
Encrypt a message with a given nonce and key.
> [**Danger: Unauthenticated encryption!**](https://tonyarcieri.com/all-the-crypto-code-youve-ever-written-is-probably-broken)
> Without a subsequent message authentication strategy, this is vulnerable to
> chosen-ciphertext attacks. Proceed with caution!
**Parameters and their respective types**:
1. `{string|Buffer}` plaintext
2. `{Buffer}` nonce (must be 24 bytes)
3. `{CryptographyKey}` key
Returns a `Promise` that resolves to a `Buffer` containing
the encrypted bytes.
### Example for crypto_stream
```javascript
const { SodiumPlus } = require('sodium-plus');
let sodium;
(async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let key = await sodium.crypto_stream_keygen();
let iv = await sodium.randombytes_buf(24);
let output = await sodium.crypto_stream(64, iv, key);
console.log(output);
iv = await sodium.randombytes_buf(24);
let plaintext = 'This is a secret message';
let ciphertext = await sodium.crypto_stream_xor(plaintext, iv, key);
let decrypted = await sodium.crypto_stream_xor(ciphertext, iv, key);
console.log(decrypted.toString());
})();
```

View file

@ -0,0 +1,45 @@
## Utilities
### sodium_bin2hex
Encode data into a hexadecimal string.
**Parameters and their respective types**:
1. `{string|Buffer}` non-hex-encoded input
Returns a `Promise` that resolves to a `string`.
```javascript
const { SodiumPlus } = require('sodium-plus');
let sodium;
(async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let buf = await sodium.randombytes_buf(32);
console.log(await sodium.sodium_bin2hex(buf));
})();
```
### sodium_hex2bin
Decode data from a hexadecimal string to a `Buffer`.
**Parameters and their respective types**:
1. `{string|Buffer}` hex-encoded input
Returns a `Promise` that resolves to a `Buffer`.
```javascript
const { SodiumPlus } = require('sodium-plus');
let sodium;
(async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let hex = '491d40c4924ba547d6f0bda9da77a539391decdc';
console.log(await sodium.sodium_hex2bin(hex));
})();
```

View file

@ -0,0 +1,100 @@
# Getting Started
You always want to use `SodiumPlus` from within an asynchronous function.
```javascript
const { SodiumPlus } = require('sodium-plus');
let sodium;
async function myFunction() {
if (!sodium) sodium = await SodiumPlus.auto();
// Now you can use sodium.FUNCTION_NAME_HERE()
}
```
When you use `await SodiumPlus.auto()`, this will automatically load in the best
backend available for your platform. This is the recommended way to use SodiumPlus.
If you'd rather use a specific backend, you can do the following:
```javascript
const { SodiumPlus, SodiumUtil, getBackendObject } = require('sodium-plus');
let sodium;
async function myFunction() {
if (!sodium) {
let backend = getBackendObject('LibsodiumWrappers');
SodiumUtil.populateConstants(backend);
sodium = new SodiumPlus(backend);
}
// Now you can use sodium.FUNCTION_NAME_HERE()
}
```
To discover what backend you're using at runtime, invoke the `getBackendName()`
method on the `SodiumPlus` object, like so:
```javascript
const { SodiumPlus } = require('sodium-plus');
let sodium;
async function whichBackend() {
if (!sodium) sodium = await SodiumPlus.auto();
console.log(sodium.getBackendName());
}
```
## CryptographyKey
All cryptographic secrets are contained within a `CryptographyKey` object
(or one of its derived classes). You can create and access them like so:
```javascript
const { CryptographyKey } = require('sodium-plus');
let buf = Buffer.alloc(32);
let key = new CryptographyKey(buf);
// If you do this, the internal buffer will not be visible!
console.log(key);
// CryptographyKey {}
// You'll need to do this instead:
console.log(key.getBuffer());
// <Buffer d9 ff 60 6b ff 96 f6 26 05 53 07 39 ef b5 a5 8b 26 0c 72 9e 1b b7 e4 97 fe 09 de 07 86 8a 0c b6>
```
The following classes inherit from `CryptographyKey`:
* `Ed25519PublicKey` -- Ed25519 public key
* `Ed25519SecretKey` -- Ed25519 secret key
* `X25519PublicKey` -- X25519 public key
* `X25519SecretKey` -- X25519 secret key
## Sodium-Plus in the Browser
First, download [sodium-plus.min.js](../dist) (or find it on a CDN you trust).
Next, include the following script tags in your web page:
```html5
<script src="sodium-plus.min.js"></script>
<script>
(async function() {
if (!window.sodium) window.sodium = await SodiumPlus.auto();
// You can now use the sodium object here.
// Just some example code to ensure it's running:
let random = await sodium.randombytes_buf(32);
let hash = await sodium.crypto_generichash('hello world');
console.log({
'random': random.toString('hex'),
'hash': hash.toString('hex')
});
})();
</script>
```
You can then use the `sodium` API as documented.

433
library/sodium-plus/index.d.ts vendored Normal file
View file

@ -0,0 +1,433 @@
/// <reference types="node" />
// Helper for defining opaque types like crypto_secretstream_xchacha20poly1305_state.
declare const brand: unique symbol;
interface Opaque<T> {
readonly [brand]: T;
}
// Separate the tag constants that crypto_secretstream_xchacha20poly1305_* functions
// take so that we can use them to limit the input values for those functions.
interface CryptoSecretStreamTagConstants {
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH: 0;
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PULL: 1;
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY: 2;
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL: 3;
}
type CryptoSecretStreamTagValues = CryptoSecretStreamTagConstants[keyof CryptoSecretStreamTagConstants];
interface Constants extends CryptoSecretStreamTagConstants {
LIBRARY_VERSION_MAJOR: number;
LIBRARY_VERSION_MINOR: number;
VERSION_STRING: string;
BASE64_VARIANT_ORIGINAL: number;
BASE64_VARIANT_ORIGINAL_NO_PADDING: number;
BASE64_VARIANT_URLSAFE: number;
BASE64_VARIANT_URLSAFE_NO_PADDING: number;
CRYPTO_AEAD_AES256GCM_KEYBYTES: number;
CRYPTO_AEAD_AES256GCM_NSECBYTES: number;
CRYPTO_AEAD_AES256GCM_NPUBBYTES: number;
CRYPTO_AEAD_AES256GCM_ABYTES: number;
CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES: number;
CRYPTO_AEAD_CHACHA20POLY1305_NSECBYTES: number;
CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES: number;
CRYPTO_AEAD_CHACHA20POLY1305_ABYTES: number;
CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES: number;
CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES: number;
CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES: number;
CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES: number;
CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES: number;
CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NSECBYTES: number;
CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES: number;
CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES: number;
CRYPTO_AUTH_BYTES: number;
CRYPTO_AUTH_KEYBYTES: number;
CRYPTO_BOX_SEALBYTES: number;
CRYPTO_BOX_SECRETKEYBYTES: number;
CRYPTO_BOX_PUBLICKEYBYTES: number;
CRYPTO_BOX_KEYPAIRBYTES: number;
CRYPTO_BOX_MACBYTES: number;
CRYPTO_BOX_NONCEBYTES: number;
CRYPTO_BOX_SEEDBYTES: number;
CRYPTO_KDF_BYTES_MIN: number;
CRYPTO_KDF_BYTES_MAX: number;
CRYPTO_KDF_CONTEXTBYTES: number;
CRYPTO_KDF_KEYBYTES: number;
CRYPTO_KX_BYTES: number;
CRYPTO_KX_PRIMITIVE: string;
CRYPTO_KX_SEEDBYTES: number;
CRYPTO_KX_KEYPAIRBYTES: number;
CRYPTO_KX_PUBLICKEYBYTES: number;
CRYPTO_KX_SECRETKEYBYTES: number;
CRYPTO_KX_SESSIONKEYBYTES: number;
CRYPTO_GENERICHASH_BYTES: number;
CRYPTO_GENERICHASH_BYTES_MIN: number;
CRYPTO_GENERICHASH_BYTES_MAX: number;
CRYPTO_GENERICHASH_KEYBYTES: number;
CRYPTO_GENERICHASH_KEYBYTES_MIN: number;
CRYPTO_GENERICHASH_KEYBYTES_MAX: number;
CRYPTO_PWHASH_SALTBYTES: number;
CRYPTO_PWHASH_STRPREFIX: string;
CRYPTO_PWHASH_ALG_ARGON2I13: number;
CRYPTO_PWHASH_ALG_ARGON2ID13: number;
CRYPTO_PWHASH_ALG_DEFAULT: number;
CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE: number;
CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE: number;
CRYPTO_PWHASH_OPSLIMIT_MODERATE: number;
CRYPTO_PWHASH_MEMLIMIT_MODERATE: number;
CRYPTO_PWHASH_OPSLIMIT_SENSITIVE: number;
CRYPTO_PWHASH_MEMLIMIT_SENSITIVE: number;
CRYPTO_PWHASH_SCRYPTSALSA208SHA256_SALTBYTES: number;
CRYPTO_SCALARMULT_BYTES: number;
CRYPTO_SCALARMULT_SCALARBYTES: number;
CRYPTO_SHORTHASH_BYTES: number;
CRYPTO_SHORTHASH_KEYBYTES: number;
CRYPTO_SECRETBOX_KEYBYTES: number;
CRYPTO_SECRETBOX_MACBYTES: number;
CRYPTO_SECRETBOX_NONCEBYTES: number;
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES: number;
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES: number;
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES: number;
CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX: number;
CRYPTO_SIGN_BYTES: number;
CRYPTO_SIGN_SEEDBYTES: number;
CRYPTO_SIGN_PUBLICKEYBYTES: number;
CRYPTO_SIGN_SECRETKEYBYTES: number;
CRYPTO_SIGN_KEYPAIRBYTES: number;
CRYPTO_STREAM_KEYBYTES: number;
CRYPTO_STREAM_NONCEBYTES: number;
}
declare namespace Module {
export type crypto_secretstream_xchacha20poly1305_state = Opaque<
"crypto_secretstream_xchacha20poly1305_state"
>;
export type crypto_generichash_state = Opaque<"crypto_generichash_state">;
export type Backend = Opaque<"Backend">;
export class CryptographyKey {
// Deny types that would otherwise structurally match CryptographyKey.
// See: https://michalzalecki.com/nominal-typing-in-typescript/
private readonly __nominal: void;
constructor(buf: Buffer);
static from(...args: Parameters<typeof Buffer.from>): CryptographyKey;
isEd25519Key(): boolean;
isX25519Key(): boolean;
isPublicKey(): boolean;
getLength(): number;
getBuffer(): Buffer;
toString(encoding?: string): string;
slice(): Buffer;
}
export class Ed25519PublicKey extends CryptographyKey {
readonly keyType: "ed25519";
readonly publicKey: true;
static from(...args: Parameters<typeof Buffer.from>): Ed25519PublicKey;
}
export class Ed25519SecretKey extends CryptographyKey {
readonly keyType: "ed25519";
readonly publicKey: false;
static from(...args: Parameters<typeof Buffer.from>): Ed25519SecretKey;
}
export class X25519PublicKey extends CryptographyKey {
readonly keyType: "x25519";
readonly publicKey: true;
static from(...args: Parameters<typeof Buffer.from>): X25519PublicKey;
}
export class X25519SecretKey extends CryptographyKey {
readonly keyType: "x25519";
readonly publicKey: false;
static from(...args: Parameters<typeof Buffer.from>): X25519SecretKey;
}
export class SodiumError extends Error {}
export function getBackendObject(
type?: "SodiumNative" | "LibsodiumWrappers"
): Backend;
// Mix in Constants.* to SodiumPlus instances.
export interface SodiumPlus extends Constants {}
export class SodiumPlus {
readonly backend: Backend;
constructor(backend: Backend);
getBackendName(): string;
isSodiumNative(): boolean;
isLibsodiumWrappers(): boolean;
static auto(): Promise<SodiumPlus>;
ensureLoaded(): Promise<void>;
crypto_aead_xchacha20poly1305_ietf_decrypt(
ciphertext: string | Buffer,
nonce: string | Buffer,
key: CryptographyKey,
assocData?: string | Buffer
): Promise<Buffer>;
crypto_aead_xchacha20poly1305_ietf_encrypt(
plaintext: string | Buffer,
nonce: string | Buffer,
key: CryptographyKey,
assocData?: string | Buffer
): Promise<Buffer>;
crypto_aead_xchacha20poly1305_ietf_keygen(): Promise<CryptographyKey>;
crypto_auth(
message: string | Buffer,
key: CryptographyKey
): Promise<Buffer>;
crypto_auth_keygen(): Promise<CryptographyKey>;
crypto_auth_verify(
message: string | Buffer,
key: CryptographyKey,
mac: Buffer
): Promise<boolean>;
crypto_box(
plaintext: string | Buffer,
nonce: Buffer,
myPrivateKey: X25519SecretKey,
theirPublicKey: X25519PublicKey
): Promise<Buffer>;
crypto_box_open(
ciphertext: Buffer,
nonce: Buffer,
myPrivateKey: X25519SecretKey,
theirPublicKey: X25519PublicKey
): Promise<Buffer>;
crypto_box_keypair(): Promise<CryptographyKey>;
crypto_box_keypair_from_secretkey_and_secretkey(
sKey: X25519SecretKey,
pKey: X25519PublicKey
): Promise<CryptographyKey>;
crypto_box_secretkey(keypair: CryptographyKey): Promise<X25519SecretKey>;
crypto_box_publickey(keypair: CryptographyKey): Promise<X25519PublicKey>;
crypto_box_publickey_from_secretkey(
secretKey: X25519SecretKey
): Promise<X25519PublicKey>;
crypto_box_seal(
plaintext: string | Buffer,
publicKey: X25519PublicKey
): Promise<Buffer>;
crypto_box_seal_open(
ciphertext: Buffer,
publicKey: X25519PublicKey,
secretKey: X25519SecretKey
): Promise<Buffer>;
crypto_generichash(
message: string | Buffer,
key?: CryptographyKey | null,
outputLength?: number
): Promise<Buffer>;
crypto_generichash_init(
key?: CryptographyKey | null,
outputLength?: number
): Promise<crypto_generichash_state>;
crypto_generichash_update(
state: crypto_generichash_state,
message: string | Buffer
): Promise<crypto_generichash_state>;
crypto_generichash_final(
state: crypto_generichash_state,
outputLength?: number
): Promise<Buffer>;
crypto_generichash_keygen(): Promise<CryptographyKey>;
crypto_kdf_derive_from_key(
length: number,
subKeyId: number,
context: string | Buffer,
key: CryptographyKey
): Promise<CryptographyKey>;
crypto_kdf_keygen(): Promise<CryptographyKey>;
crypto_kx_keypair(): Promise<CryptographyKey>;
crypto_kx_seed_keypair(seed: string | Buffer): Promise<CryptographyKey>;
crypto_kx_client_session_keys(
clientPublicKey: X25519PublicKey,
clientSecretKey: X25519SecretKey,
serverPublicKey: X25519PublicKey
): Promise<CryptographyKey[]>;
crypto_kx_server_session_keys(
serverPublicKey: X25519PublicKey,
serverSecretKey: X25519SecretKey,
clientPublicKey: X25519PublicKey
): Promise<CryptographyKey[]>;
crypto_onetimeauth(
message: string | Buffer,
key: CryptographyKey
): Promise<Buffer>;
crypto_onetimeauth_verify(
message: string | Buffer,
key: CryptographyKey,
tag: Buffer
): Promise<boolean>;
crypto_onetimeauth_keygen(): Promise<CryptographyKey>;
crypto_pwhash(
length: number,
password: string | Buffer,
salt: Buffer,
opslimit: number,
memlimit: number,
algorithm?: number | null
): Promise<CryptographyKey>;
crypto_pwhash_str(
password: string | Buffer,
opslimit: number,
memlimit: number
): Promise<string>;
crypto_pwhash_str_verify(
password: string | Buffer,
hash: string | Buffer
): Promise<boolean>;
crypto_pwhash_str_needs_rehash(
hash: string | Buffer,
opslimit: number,
memlimit: number
): Promise<boolean>;
crypto_scalarmult(
secretKey: X25519SecretKey,
publicKey: X25519PublicKey
): Promise<CryptographyKey>;
crypto_scalarmult_base(
secretKey: X25519SecretKey
): Promise<X25519PublicKey>;
crypto_secretbox(
plaintext: string | Buffer,
nonce: Buffer,
key: CryptographyKey
): Promise<Buffer>;
crypto_secretbox_open(
ciphertext: Buffer,
nonce: Buffer,
key: CryptographyKey
): Promise<Buffer>;
crypto_secretbox_keygen(): Promise<CryptographyKey>;
crypto_secretstream_xchacha20poly1305_init_push(
key: CryptographyKey
): Promise<crypto_secretstream_xchacha20poly1305_state>;
crypto_secretstream_xchacha20poly1305_init_pull(
key: Buffer,
header: CryptographyKey
): Promise<crypto_secretstream_xchacha20poly1305_state>;
crypto_secretstream_xchacha20poly1305_push(
state: crypto_secretstream_xchacha20poly1305_state,
message: string | Buffer,
ad?: string | Buffer,
tag?: CryptoSecretStreamTagValues
): Promise<Buffer>;
crypto_secretstream_xchacha20poly1305_pull(
state: crypto_secretstream_xchacha20poly1305_state,
ciphertext: Buffer,
ad?: string | Buffer,
tag?: CryptoSecretStreamTagValues
): Promise<Buffer>;
crypto_secretstream_xchacha20poly1305_rekey(
state: crypto_secretstream_xchacha20poly1305_state
): Promise<void>;
crypto_secretstream_xchacha20poly1305_keygen(): Promise<CryptographyKey>;
crypto_shorthash(
message: string | Buffer,
key: CryptographyKey
): Promise<Buffer>;
crypto_shorthash_keygen(): Promise<CryptographyKey>;
crypto_sign(
message: string | Buffer,
secretKey: Ed25519SecretKey
): Promise<Buffer>;
crypto_sign_open(
message: string | Buffer,
publicKey: Ed25519PublicKey
): Promise<Buffer>;
crypto_sign_detached(
message: string | Buffer,
secretKey: Ed25519SecretKey
): Promise<Buffer>;
crypto_sign_verify_detached(
message: string | Buffer,
publicKey: Ed25519PublicKey,
signature: Buffer
): Promise<boolean>;
crypto_sign_secretkey(keypair: CryptographyKey): Promise<Ed25519SecretKey>;
crypto_sign_publickey(keypair: CryptographyKey): Promise<Ed25519PublicKey>;
crypto_sign_seed_keypair(seed: Buffer): Promise<CryptographyKey>;
crypto_sign_keypair(): Promise<CryptographyKey>;
crypto_sign_ed25519_sk_to_curve25519(
sk: Ed25519SecretKey
): Promise<X25519SecretKey>;
crypto_sign_ed25519_pk_to_curve25519(
pk: Ed25519PublicKey
): Promise<X25519PublicKey>;
crypto_stream(
length: number,
nonce: Buffer,
key: CryptographyKey
): Promise<Buffer>;
crypto_stream_xor(
plaintext: string | Buffer,
nonce: Buffer,
key: CryptographyKey
): Promise<Buffer>;
crypto_stream_keygen(): Promise<CryptographyKey>;
randombytes_buf(num: number): Promise<Buffer>;
randombytes_uniform(upperBound: number): Promise<number>;
sodium_add(val: Buffer, addv: Buffer): Promise<Buffer>;
sodium_bin2hex(encoded: Buffer): Promise<string>;
sodium_compare(b1: Buffer, b2: Buffer): Promise<number>;
sodium_hex2bin(encoded: Buffer|string): Promise<Buffer>;
sodium_increment(buf: Buffer): Promise<Buffer>;
sodium_is_zero(buf: Buffer, len: number): Promise<Buffer>;
sodium_memcmp(b1: Buffer, b2: Buffer): Promise<boolean>;
sodium_memzero(buf: Buffer): Promise<void>;
sodium_pad(buf: string | Buffer, blockSize: number): Promise<Buffer>;
sodium_unpad(buf: string | Buffer, blockSize: number): Promise<Buffer>;
}
export class SodiumUtil {
static cloneBuffer(buf: Buffer): Promise<Buffer>;
static populateConstants<T>(anyobject: T): T & Constants;
static toBuffer(
stringOrBuffer: string | Buffer | Uint8Array | Promise<Buffer>
): Promise<Buffer>;
}
export class SodiumPolyfill {
static crypto_onetimeauth(
message: string | Buffer,
key: CryptographyKey
): Promise<Buffer>;
static crypto_onetimeauth_verify(
message: string | Buffer,
key: CryptographyKey,
tag: Buffer
): Promise<boolean>;
static crypto_stream_xor(
plaintext: string | Buffer,
nonce: Buffer,
key: CryptographyKey
): Promise<Buffer>;
static crypto_pwhash_str_needs_rehash(
hash: string | Buffer,
opslimit: number,
memlimit: number
): Promise<boolean>;
}
}
export = Module;

View file

@ -0,0 +1,36 @@
module.exports = {
/**
* This is only meant to be used for advanced users.
*
* A backend object can be passed to the SodiumPlus constructor.
*
* @param {string} type
* @return {Backend}
* @throws {SodiumError}
* @throws {Error}
*/
getBackendObject: (type = '') => {
if (type === 'SodiumNative') {
// This one may error out. You should catch it in your code.
// We won't here. Use the `await SodiumPlus.auto()` API instead!
return require('./lib/backend/sodiumnative');
} else if (type === 'LibsodiumWrappers') {
return require('./lib/backend/libsodium-wrappers');
} else if (type.length === 0) {
return require('./lib/backend');
}
// Default: Throw a SodiumError
let SodiumError = require('./lib/sodium-error');
throw new SodiumError(`Unrecognized backend type: ${type}`);
},
CryptographyKey: require('./lib/cryptography-key'),
Ed25519PublicKey: require('./lib/keytypes/ed25519pk'),
Ed25519SecretKey: require('./lib/keytypes/ed25519sk'),
SodiumError: require('./lib/sodium-error'),
SodiumPlus: require('./lib/sodiumplus'),
SodiumPolyfill: require('./lib/polyfill'),
SodiumUtil: require('./lib/util'),
X25519PublicKey: require('./lib/keytypes/x25519pk'),
X25519SecretKey: require('./lib/keytypes/x25519sk')
};

View file

@ -0,0 +1,32 @@
const CryptographyKey = require('./cryptography-key');
/* istanbul ignore if */
if (typeof (Buffer) === 'undefined') {
let Buffer = require('buffer/').Buffer;
}
module.exports = class Backend {
constructor() {
// NOP
this.backendName = 'UndefinedBackend';
}
/**
* @param {CryptographyKey} sKey
* @param {CryptographyKey} pKey
* @return {Promise<CryptographyKey>}
*/
async crypto_box_keypair_from_secretkey_and_publickey(sKey, pKey) {
/* istanbul ignore if */
if (sKey.getLength() !== 32) {
throw new Error('Secret key must be 32 bytes');
}
/* istanbul ignore if */
if (pKey.getLength() !== 32) {
throw new Error('Public key must be 32 bytes');
}
const keypair = Buffer.alloc(64);
sKey.getBuffer().copy(keypair, 0, 0, 32);
pKey.getBuffer().copy(keypair, 32, 0, 32);
return new CryptographyKey(Buffer.from(keypair));
}
};

View file

@ -0,0 +1,782 @@
const _sodium = require('libsodium-wrappers');
const Backend = require('../backend');
const CryptographyKey = require('../cryptography-key');
const Polyfill = require('../polyfill');
const Util = require('../util');
const SodiumError = require('../sodium-error');
const toBuffer = require('typedarray-to-buffer');
/* istanbul ignore if */
if (typeof (Buffer) === 'undefined') {
let Buffer = require('buffer/').Buffer;
}
/* istanbul ignore next */
module.exports = class LibsodiumWrappersBackend extends Backend {
constructor(lib) {
super(lib);
this.sodium = lib;
this.backendName = 'LibsodiumWrappersBackend';
}
static async init() {
await _sodium.ready;
return new LibsodiumWrappersBackend(_sodium);
}
/**
*
* @param {String|Buffer} ciphertext
* @param {String|Buffer} assocData
* @param {String|Buffer} nonce
* @param {CryptographyKey} key
* @return {Promise<Buffer>}
*/
async crypto_aead_xchacha20poly1305_ietf_decrypt(ciphertext, assocData, nonce, key) {
return toBuffer(
this.sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
null,
ciphertext,
assocData,
nonce,
key.getBuffer()
)
);
}
/**
*
* @param {String|Buffer} plaintext
* @param {String|Buffer} assocData
* @param {String|Buffer} nonce
* @param {CryptographyKey} key
* @return {Promise<Buffer>}
*/
async crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, assocData, nonce, key) {
return toBuffer(
this.sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
plaintext,
assocData,
null,
nonce,
key.getBuffer()
)
);
}
/**
* @param {String|Buffer} message
* @param {CryptographyKey} key
* @return {Promise<buffer>}
*/
async crypto_auth(message, key) {
return toBuffer(
this.sodium.crypto_auth(
message,
key.getBuffer()
)
);
}
/**
* @param {Buffer} mac
* @param {String|Buffer} message
* @param {CryptographyKey} key
* @return {Promise<boolean>}
*/
async crypto_auth_verify(mac, message, key) {
return this.sodium.crypto_auth_verify(
mac,
message,
key.getBuffer()
);
}
/**
* @param {string|Buffer} plaintext
* @param {Buffer} nonce
* @param {CryptographyKey} sk
* @param {CryptographyKey} pk
* @return {Promise<Buffer>}
*
*/
async crypto_box(plaintext, nonce, sk, pk) {
return Util.toBuffer(
await this.sodium.crypto_box_easy(
await Util.toBuffer(plaintext),
await Util.toBuffer(nonce),
pk.getBuffer(),
sk.getBuffer()
)
);
}
/**
* @param {Buffer} ciphertext
* @param {Buffer} nonce
* @param {CryptographyKey} sk
* @param {CryptographyKey} pk
* @return {Promise<Buffer>}
*/
async crypto_box_open(ciphertext, nonce, sk, pk) {
return Util.toBuffer(
await this.sodium.crypto_box_open_easy(
await Util.toBuffer(ciphertext),
await Util.toBuffer(nonce),
pk.getBuffer(),
sk.getBuffer()
)
);
}
/**
* @param {string|Buffer} plaintext
* @param {CryptographyKey} pk
* @return {Promise<Buffer>}
*
*/
async crypto_box_seal(plaintext, pk) {
return Util.toBuffer(
await this.sodium.crypto_box_seal(
await Util.toBuffer(plaintext),
pk.getBuffer()
)
);
}
/**
* @param {Buffer} ciphertext
* @param {CryptographyKey} pk
* @param {CryptographyKey} sk
* @return {Promise<Buffer>}
*/
async crypto_box_seal_open(ciphertext, pk, sk) {
return Util.toBuffer(
await this.sodium.crypto_box_seal_open(
await Util.toBuffer(ciphertext),
pk.getBuffer(),
sk.getBuffer()
)
);
}
/**
* @return {Promise<CryptographyKey>}
*/
async crypto_box_keypair() {
const obj = this.sodium.crypto_box_keypair();
return new CryptographyKey(
Buffer.concat([
await Util.toBuffer(obj.privateKey),
await Util.toBuffer(obj.publicKey)
])
);
}
/**
* @param {string|Buffer} message
* @param {CryptographyKey|null} key
* @param {number} outputLength
* @return {Promise<Buffer>}
*/
async crypto_generichash(message, key = null, outputLength = 32) {
if (key) {
return Util.toBuffer(
this.sodium.crypto_generichash(
outputLength,
await Util.toBuffer(message),
key.getBuffer()
)
);
}
return Util.toBuffer(
this.sodium.crypto_generichash(
outputLength,
await Util.toBuffer(message)
)
);
}
/**
* @param {CryptographyKey|null} key
* @param {number} outputLength
* @return {Promise<Buffer>}
*/
async crypto_generichash_init(key = null, outputLength = 32) {
if (key) {
return this.sodium.crypto_generichash_init(key.getBuffer(), outputLength);
}
return this.sodium.crypto_generichash_init(null, outputLength);
}
/**
* @param {*} state
* @param {string|Buffer} message
* @return {Promise<*>}
*/
async crypto_generichash_update(state, message) {
return this.sodium.crypto_generichash_update(state, await Util.toBuffer(message));
}
/**
* @param {*} state
* @param {number} outputLength
* @return {Promise<Buffer>}
*/
async crypto_generichash_final(state, outputLength = 32) {
return Util.toBuffer(
this.sodium.crypto_generichash_final(state, outputLength)
);
}
/**
* @param {X25519PublicKey} clientPublicKey
* @param {X25519SecretKey} clientSecretKey
* @param {X25519PublicKey} serverPublicKey
* @return {Promise<CryptographyKey[]>}
*/
async crypto_kx_client_session_keys(clientPublicKey, clientSecretKey, serverPublicKey) {
const gen = this.sodium.crypto_kx_client_session_keys(
clientPublicKey.getBuffer(),
clientSecretKey.getBuffer(),
serverPublicKey.getBuffer(),
);
return [
new CryptographyKey(await Util.toBuffer(gen.sharedRx)),
new CryptographyKey(await Util.toBuffer(gen.sharedTx))
];
}
/**
* @param {X25519PublicKey} serverPublicKey
* @param {X25519SecretKey} serverSecretKey
* @param {X25519PublicKey} clientPublicKey
* @return {Promise<CryptographyKey[]>}
*/
async crypto_kx_server_session_keys(serverPublicKey, serverSecretKey, clientPublicKey) {
const gen = this.sodium.crypto_kx_server_session_keys(
serverPublicKey.getBuffer(),
serverSecretKey.getBuffer(),
clientPublicKey.getBuffer(),
);
return [
new CryptographyKey(await Util.toBuffer(gen.sharedRx)),
new CryptographyKey(await Util.toBuffer(gen.sharedTx))
];
}
/**
* @param {number} length
* @param {number} subKeyId
* @param {string|Buffer} context
* @param {CryptographyKey} key
* @return {Promise<CryptographyKey>}
*/
async crypto_kdf_derive_from_key(length, subKeyId, context, key) {
return new CryptographyKey(
await Util.toBuffer(
this.sodium.crypto_kdf_derive_from_key(
length,
subKeyId | 0,
context,
key.getBuffer()
)
)
);
}
/**
* @param {string|Buffer} message
* @param {CryptographyKey} key
* @return {Promise<Buffer>}
*/
async crypto_onetimeauth(message, key) {
if (typeof this.sodium.crypto_onetimeauth === 'undefined') {
return Polyfill.crypto_onetimeauth(
await Util.toBuffer(message),
key
);
}
return this.sodium.crypto_onetimeauth(
await Util.toBuffer(message),
key.getBuffer()
);
}
/**
* @param {string|Buffer} message
* @param {CryptographyKey} key
* @param {Buffer} tag
* @return {Promise<boolean>}
*/
async crypto_onetimeauth_verify(message, key, tag) {
if (typeof this.sodium.crypto_onetimeauth_verify === 'undefined') {
return Polyfill.crypto_onetimeauth_verify(
await Util.toBuffer(message),
key,
tag
);
}
return this.sodium.crypto_onetimeauth_verify(
tag,
await Util.toBuffer(message),
key.getBuffer()
);
}
/**
* @param {number} length
* @param {string|Buffer} password
* @param {Buffer} salt
* @param {number} opslimit
* @param {number} memlimit
* @param {number} algorithm
* @return {Promise<Buffer>}
*/
async crypto_pwhash(length, password, salt, opslimit, memlimit, algorithm) {
return Util.toBuffer(
this.sodium.crypto_pwhash(
length,
await Util.toBuffer(password),
await Util.toBuffer(salt),
opslimit,
memlimit,
algorithm
)
);
}
/**
* @param {string|Buffer} password
* @param {number} opslimit
* @param {number} memlimit
* @return {Promise<string>}
*/
async crypto_pwhash_str(password, opslimit, memlimit) {
return (await Util.toBuffer(
this.sodium.crypto_pwhash_str(
await Util.toBuffer(password),
opslimit,
memlimit
))
).toString('utf-8');
}
/**
* @param {string|Buffer} password
* @param {string|Buffer} hash
* @return {Promise<boolean>}
*/
async crypto_pwhash_str_verify(password, hash) {
return this.sodium.crypto_pwhash_str_verify(
hash.toString('utf-8'),
await Util.toBuffer(password)
);
}
/**
* @param {string|Buffer} hash
* @param {number} opslimit
* @param {number} memlimit
* @return {Promise<boolean>}
*/
async crypto_pwhash_str_needs_rehash(hash, opslimit, memlimit) {
if (typeof (this.sodium.crypto_pwhash_str_needs_rehash) !== 'function') {
return await Polyfill.crypto_pwhash_str_needs_rehash(hash, opslimit, memlimit);
}
return this.sodium.crypto_pwhash_str_needs_rehash(hash, opslimit, memlimit);
}
/**
* @param {X25519SecretKey} secretKey
* @param {X25519PublicKey} publicKey
* @return {Promise<CryptographyKey>}
*/
async crypto_scalarmult(secretKey, publicKey) {
return new CryptographyKey(
await Util.toBuffer(
this.sodium.crypto_scalarmult(secretKey.getBuffer(), publicKey.getBuffer())
)
);
}
/**
* @param {string|Buffer} plaintext
* @param {Buffer} nonce
* @param {CryptographyKey} key
* @return {Promise<Buffer>}
*/
async crypto_secretbox(plaintext, nonce, key) {
return Util.toBuffer(
this.sodium.crypto_secretbox_easy(
await Util.toBuffer(plaintext),
nonce,
key.getBuffer()
)
);
}
/**
* @param {Buffer} ciphertext
* @param {Buffer} nonce
* @param {CryptographyKey} key
* @return {Promise<Buffer>}
*/
async crypto_secretbox_open(ciphertext, nonce, key) {
return Util.toBuffer(
this.sodium.crypto_secretbox_open_easy(
await Util.toBuffer(ciphertext),
nonce,
key.getBuffer()
)
);
}
/**
* @param {string|Buffer} message
* @param {CryptographyKey} key
* @return {Promise<Buffer>}
*/
async crypto_shorthash(message, key) {
return Util.toBuffer(
this.sodium.crypto_shorthash(
await Util.toBuffer(message),
key.getBuffer()
)
);
}
/**
* @param {string|Buffer} message,
* @param {Ed25519SecretKey} secretKey
* @return {Promise<Buffer>}
*/
async crypto_sign(message, secretKey) {
return Util.toBuffer(
this.sodium.crypto_sign(
await Util.toBuffer(message),
secretKey.getBuffer()
)
);
}
/**
* @param {string|Buffer} message,
* @param {Ed25519PublicKey} publicKey
* @return {Promise<Buffer>}
*/
async crypto_sign_open(message, publicKey) {
return Util.toBuffer(
this.sodium.crypto_sign_open(
message,
publicKey.getBuffer()
)
);
}
/**
* @param {string|Buffer} message,
* @param {Ed25519SecretKey} secretKey
* @return {Promise<Buffer>}
*/
async crypto_sign_detached(message, secretKey) {
return Util.toBuffer(
this.sodium.crypto_sign_detached(
await Util.toBuffer(message),
secretKey.getBuffer()
)
);
}
/**
* @param {string|Buffer} message,
* @param {Ed25519PublicKey} publicKey
* @param {Buffer} signature
* @return {Promise<Buffer>}
*/
async crypto_sign_verify_detached(message, publicKey, signature) {
return this.sodium.crypto_sign_verify_detached(
signature,
await Util.toBuffer(message),
publicKey.getBuffer()
);
}
/**
* @return {Promise<CryptographyKey>}
*/
async crypto_sign_keypair() {
const obj = this.sodium.crypto_sign_keypair();
return new CryptographyKey(
Buffer.concat([
await Util.toBuffer(obj.privateKey),
await Util.toBuffer(obj.publicKey)
])
);
}
/**
* @param {Buffer} seed
* @return {Promise<CryptographyKey>}
*/
async crypto_sign_seed_keypair(seed) {
const obj = this.sodium.crypto_sign_seed_keypair(seed);
return new CryptographyKey(
Buffer.concat([
await Util.toBuffer(obj.privateKey),
await Util.toBuffer(obj.publicKey)
])
);
}
/**
* @param {Ed25519SecretKey} sk
* @return {Promise<Buffer>}
*/
async crypto_sign_ed25519_sk_to_curve25519(sk) {
return Util.toBuffer(
this.sodium.crypto_sign_ed25519_sk_to_curve25519(sk.getBuffer())
);
}
/**
* @param {Ed25519PublicKey} pk
* @return {Promise<Buffer>}
*/
async crypto_sign_ed25519_pk_to_curve25519(pk) {
return Util.toBuffer(
this.sodium.crypto_sign_ed25519_pk_to_curve25519(pk.getBuffer())
);
}
/**
* @param {number} length
* @param {Buffer} nonce
* @param {CryptographyKey} key
* @return {Promise<Buffer>}
*/
async crypto_stream(length, nonce, key) {
if (typeof (this.sodium.crypto_stream_xor) === 'undefined') {
return Polyfill.crypto_stream_xor(
Buffer.alloc(length, 0),
await Util.toBuffer(nonce),
key
);
}
return this.sodium.crypto_stream(
length,
await Util.toBuffer(nonce),
key.getBuffer()
);
}
/**
* @param {string|Buffer} plaintext
* @param {Buffer} nonce
* @param {CryptographyKey} key
* @return {Promise<Buffer>}
*/
async crypto_stream_xor(plaintext, nonce, key) {
if (typeof (this.sodium.crypto_stream_xor) === 'undefined') {
return Polyfill.crypto_stream_xor(
await Util.toBuffer(plaintext),
await Util.toBuffer(nonce),
key
)
}
return this.sodium.crypto_stream_xor(
await Util.toBuffer(plaintext),
await Util.toBuffer(nonce),
key.getBuffer()
);
}
/**
*
* @param {CryptographyKey} secretKey
* @return {Promise<Buffer>}
*/
async crypto_scalarmult_base(secretKey) {
return Util.toBuffer(
this.sodium.crypto_scalarmult_base(secretKey.getBuffer())
);
}
/**
* @param {CryptographyKey} key
* @return {Promise<array>} [state, header]
*/
async crypto_secretstream_xchacha20poly1305_init_push(key) {
const res = this.sodium.crypto_secretstream_xchacha20poly1305_init_push(key.getBuffer());
return [res.state, await Util.toBuffer(res.header)];
}
/**
* @param {Buffer} header
* @param {CryptographyKey} key
* @return {Promise<*>} Returns the opaque state object
*/
async crypto_secretstream_xchacha20poly1305_init_pull(header, key) {
if (header.length !== this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES) {
throw new SodiumError(`Header must be ${this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES} bytes long`);
}
return this.sodium.crypto_secretstream_xchacha20poly1305_init_pull(header, key.getBuffer());
}
/**
* @param {*} state
* @param {string|Buffer} message
* @param {string|Buffer} ad
* @param {number} tag
* @return {Promise<Buffer>}
*/
async crypto_secretstream_xchacha20poly1305_push(state, message, ad = '', tag = 0) {
return Util.toBuffer(
this.sodium.crypto_secretstream_xchacha20poly1305_push(
state,
await Util.toBuffer(message),
ad.length > 0 ? (await Util.toBuffer(ad)) : null,
tag
)
);
}
/**
* @param {*} state
* @param {Buffer} ciphertext
* @param {string|Buffer} ad
* @param {number} tag
* @return {Promise<Buffer>}
*/
async crypto_secretstream_xchacha20poly1305_pull(state, ciphertext, ad = '', tag = 0) {
if (ciphertext.length < this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES) {
throw new SodiumError('Invalid ciphertext size');
}
const out = this.sodium.crypto_secretstream_xchacha20poly1305_pull(
state,
await Util.toBuffer(ciphertext),
ad.length > 0 ? (await Util.toBuffer(ad)) : null,
tag
);
if (tag !== out.tag) {
throw new SodiumError(`Invalid tag (Given: ${tag}; Expected: ${out.tag})`);
}
return Util.toBuffer(out.message);
}
/**
* @param {*} state
* @return {Promise<void>}
*/
async crypto_secretstream_xchacha20poly1305_rekey(state) {
this.sodium.crypto_secretstream_xchacha20poly1305_rekey(state);
}
/**
* @param {number} number
* @return {Promise<Buffer>}
*/
async randombytes_buf(number) {
return Util.toBuffer(await this.sodium.randombytes_buf(number));
}
/**
* @param {number} upperBound
* @return {Promise<number>}
*/
async randombytes_uniform(upperBound) {
return this.sodium.randombytes_uniform(upperBound);
}
/**
* @param {Uint8Array} val
* @param {Uint8Array} addv
* @return {Promise<Buffer>}
*/
async sodium_add(val, addv) {
const buf = await Util.cloneBuffer(val);
this.sodium.add(buf, addv);
return buf;
}
/**
* @param {Buffer} buf
* @return {Promise<string>}
*/
async sodium_bin2hex(buf) {
return this.sodium.to_hex(buf);
}
/**
* @param {Buffer} b1
* @param {Buffer} b2
* @return {Promise<number>}
*/
async sodium_compare(b1, b2) {
return this.sodium.compare(b1, b2);
}
/**
* @param {Buffer|string} encoded
* @return {Promise<Buffer>}
*/
async sodium_hex2bin(encoded) {
return Buffer.from(this.sodium.from_hex(encoded));
}
/**
* @param {Buffer} buf
* @return {Promise<Buffer>}
*/
async sodium_increment(buf) {
return this.sodium.increment(buf);
}
/**
* @param {Buffer} buf
* @param {number} len
* @return {Promise<Buffer>}
*/
async sodium_is_zero(buf, len) {
return this.sodium.is_zero(buf, len);
}
/**
* @param {Buffer} b1
* @param {Buffer} b2
* @return {Promise<boolean>}
*/
async sodium_memcmp(b1, b2) {
return this.sodium.memcmp(b1, b2);
}
/**
* @param {Buffer} buf
* @return {Promise<void>}
*/
async sodium_memzero(buf) {
this.sodium.memzero(buf);
}
/**
*
* @param {string|Buffer} buf
* @param {number} blockSize
* @return {Promise<Buffer>}
*/
async sodium_pad(buf, blockSize) {
return Util.toBuffer(
this.sodium.pad(await Util.toBuffer(buf), blockSize)
);
}
/**
*
* @param {string|Buffer} buf
* @param {number} blockSize
* @return {Promise<Buffer>}
*/
async sodium_unpad(buf, blockSize) {
return Util.toBuffer(this.sodium.unpad(buf, blockSize));
}
};

View file

@ -0,0 +1,866 @@
let loaded = false;
let _sodium;
/* istanbul ignore next */
try {
_sodium = require('sodium-native');
loaded = true;
} catch (e) {
_sodium = {};
}
const Backend = require('../backend');
const CryptographyKey = require('../cryptography-key');
const SodiumError = require('../sodium-error');
const Util = require('../util');
const toBuffer = require('typedarray-to-buffer');
/* istanbul ignore if */
if (typeof (Buffer) === 'undefined') {
let Buffer = require('buffer/').Buffer;
}
/* istanbul ignore next */
module.exports = class SodiumNativeBackend extends Backend {
constructor(lib) {
super(lib);
this.sodium = lib;
this.backendName = 'SodiumNativeBackend';
}
static async init() {
if (!loaded) {
throw new SodiumError('sodium-native not installed');
}
return new SodiumNativeBackend(_sodium);
}
/**
*
* @param {String|Buffer} ciphertext
* @param {String|Buffer} assocData
* @param {String|Buffer} nonce
* @param {CryptographyKey} key
* @return {Promise<Buffer>}
*/
async crypto_aead_xchacha20poly1305_ietf_decrypt(ciphertext, assocData, nonce, key) {
const plaintext = Buffer.alloc(ciphertext.length - 16, 0);
this.sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
plaintext,
null,
await Util.toBuffer(ciphertext),
await Util.toBuffer(assocData),
await Util.toBuffer(nonce),
key.getBuffer()
);
return plaintext;
}
/**
*
* @param {String|Buffer} plaintext
* @param {String|Buffer} assocData
* @param {String|Buffer} nonce
* @param {CryptographyKey} key
* @return {Promise<Buffer>}
*/
async crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, assocData, nonce, key) {
const ciphertext = Buffer.alloc(plaintext.length + 16, 0);
this.sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
ciphertext,
await Util.toBuffer(plaintext),
await Util.toBuffer(assocData),
null,
await Util.toBuffer(nonce),
key.getBuffer()
);
return ciphertext;
}
/**
* @param {String|Buffer} message
* @param {CryptographyKey} key
* @return {Promise<buffer>}
*/
async crypto_auth(message, key) {
const output = Buffer.alloc(32);
this.sodium.crypto_auth(
output,
await Util.toBuffer(message),
key.getBuffer()
);
return toBuffer(output);
}
/**
* @param {Buffer} mac
* @param {String|Buffer} message
* @param {CryptographyKey} key
* @return {Promise<boolean>}
*/
async crypto_auth_verify(mac, message, key) {
return this.sodium.crypto_auth_verify(
mac,
await Util.toBuffer(message),
key.getBuffer()
);
}
/**
* @param {string|Buffer} plaintext
* @param {Buffer} nonce
* @param {CryptographyKey} sk
* @param {CryptographyKey} pk
* @return {Promise<Buffer>}
*
*/
async crypto_box(plaintext, nonce, sk, pk) {
const ciphertext = Buffer.alloc(plaintext.length + 16);
this.sodium.crypto_box_easy(
ciphertext,
await Util.toBuffer(plaintext),
nonce,
pk.getBuffer(),
sk.getBuffer()
);
return Util.toBuffer(ciphertext);
}
/**
* @param {Buffer} ciphertext
* @param {Buffer} nonce
* @param {CryptographyKey} sk
* @param {CryptographyKey} pk
* @return {Promise<Buffer>}
*/
async crypto_box_open(ciphertext, nonce, sk, pk) {
const plaintext = Buffer.alloc(ciphertext.length - 16);
const success = this.sodium.crypto_box_open_easy(
plaintext,
ciphertext,
nonce,
pk.getBuffer(),
sk.getBuffer()
);
if (!success) {
throw new SodiumError('Decryption failed');
}
return Util.toBuffer(plaintext);
}
/**
* @param {string|Buffer} plaintext
* @param {CryptographyKey} pk
* @return {Promise<Buffer>}
*
*/
async crypto_box_seal(plaintext, pk) {
const ciphertext = Buffer.alloc(plaintext.length + 48);
this.sodium.crypto_box_seal(
ciphertext,
await Util.toBuffer(plaintext),
pk.getBuffer()
);
return Util.toBuffer(ciphertext);
}
/**
* @param {Buffer} ciphertext
* @param {CryptographyKey} pk
* @param {CryptographyKey} sk
* @return {Promise<Buffer>}
*/
async crypto_box_seal_open(ciphertext, pk, sk) {
const plaintext = Buffer.alloc(ciphertext.length - 48);
const success = this.sodium.crypto_box_seal_open(
plaintext,
await Util.toBuffer(ciphertext),
pk.getBuffer(),
sk.getBuffer()
);
if (!success) {
throw new SodiumError('Decryption failed');
}
return Util.toBuffer(plaintext);
}
/**
* @return {Promise<CryptographyKey>}
*/
async crypto_box_keypair() {
const sK = Buffer.alloc(32, 0);
const pK = Buffer.alloc(32, 0);
this.sodium.crypto_box_keypair(sK, pK);
return new CryptographyKey(
Buffer.concat([pK, sK])
);
}
/**
* @param {string|Buffer} message
* @param {CryptographyKey|null} key
* @param {number} outputLength
* @return {Promise<Buffer>}
*/
async crypto_generichash(message, key = null, outputLength = 32) {
const hash = Buffer.alloc(outputLength);
if (key) {
this.sodium.crypto_generichash(hash, await Util.toBuffer(message), key.getBuffer());
} else {
this.sodium.crypto_generichash(hash, await Util.toBuffer(message));
}
return hash;
}
/**
* @param {CryptographyKey|null} key
* @param {number} outputLength
* @return {Promise<Buffer>}
*/
async crypto_generichash_init(key = null, outputLength = 32) {
const state = Buffer.alloc(this.CRYPTO_GENERICHASH_STATEBYTES);
if (key) {
this.sodium.crypto_generichash_init(state, key.getBuffer(), outputLength);
} else {
this.sodium.crypto_generichash_init(state, null, outputLength);
}
return state;
}
/**
* @param {*} state
* @param {string|Buffer} message
* @return {Promise<*>}
*/
async crypto_generichash_update(state, message) {
this.sodium.crypto_generichash_update(state, await Util.toBuffer(message));
return state;
}
/**
* @param {*} state
* @param {number} outputLength
* @return {Promise<Buffer>}
*/
async crypto_generichash_final(state, outputLength = 32) {
const output = Buffer.alloc(outputLength);
this.sodium.crypto_generichash_final(state, output);
return output;
}
/**
* @param {number} length
* @param {number} subKeyId
* @param {string|Buffer} context
* @param {CryptographyKey} key
* @return {Promise<CryptographyKey>}
*/
async crypto_kdf_derive_from_key(length, subKeyId, context, key) {
const subkey = Buffer.alloc(length, 0);
this.sodium.crypto_kdf_derive_from_key(
subkey,
subKeyId | 0,
await Util.toBuffer(context),
key.getBuffer()
);
return new CryptographyKey(subkey);
}
/**
* @param {X25519PublicKey} clientPublicKey
* @param {X25519SecretKey} clientSecretKey
* @param {X25519PublicKey} serverPublicKey
* @return {Promise<CryptographyKey[]>}
*/
async crypto_kx_client_session_keys(clientPublicKey, clientSecretKey, serverPublicKey) {
const rx = Buffer.alloc(this.CRYPTO_KX_SESSIONKEYBYTES);
const tx = Buffer.alloc(this.CRYPTO_KX_SESSIONKEYBYTES);
this.sodium.crypto_kx_client_session_keys(
rx,
tx,
clientPublicKey.getBuffer(),
clientSecretKey.getBuffer(),
serverPublicKey.getBuffer(),
);
return [
new CryptographyKey(rx),
new CryptographyKey(tx)
];
}
/**
* @param {X25519PublicKey} serverPublicKey
* @param {X25519SecretKey} serverSecretKey
* @param {X25519PublicKey} clientPublicKey
* @return {Promise<CryptographyKey[]>}
*/
async crypto_kx_server_session_keys(serverPublicKey, serverSecretKey, clientPublicKey) {
const rx = Buffer.alloc(this.CRYPTO_KX_SESSIONKEYBYTES);
const tx = Buffer.alloc(this.CRYPTO_KX_SESSIONKEYBYTES);
this.sodium.crypto_kx_server_session_keys(
rx,
tx,
serverPublicKey.getBuffer(),
serverSecretKey.getBuffer(),
clientPublicKey.getBuffer(),
);
return [
new CryptographyKey(rx),
new CryptographyKey(tx)
];
}
/**
* @param {string|Buffer} message
* @param {CryptographyKey} key
* @return {Promise<Buffer>}
*/
async crypto_onetimeauth(message, key) {
const output = Buffer.alloc(16);
this.sodium.crypto_onetimeauth(
output,
await Util.toBuffer(message),
key.getBuffer()
);
return output;
}
/**
* @param {string|Buffer} message
* @param {CryptographyKey} key
* @param {Buffer} tag
* @return {Promise<boolean>}
*/
async crypto_onetimeauth_verify(message, key, tag) {
return this.sodium.crypto_onetimeauth_verify(
tag,
await Util.toBuffer(message),
key.getBuffer()
);
}
/**
* @param {number} length
* @param {string|Buffer} password
* @param {Buffer} salt
* @param {number} opslimit
* @param {number} memlimit
* @param {number} algorithm
* @return {Promise<Buffer>}
*/
async crypto_pwhash(length, password, salt, opslimit, memlimit, algorithm) {
const hashed = Buffer.alloc(length, 0);
const bufPass = await Util.toBuffer(password);
const bufSalt = await Util.toBuffer(salt);
await new Promise((resolve, reject) => {
this.sodium.crypto_pwhash_async(
hashed,
bufPass,
bufSalt,
opslimit,
memlimit,
algorithm,
(e, res) => {
if (e) return reject(e);
return resolve(res);
}
);
});
return hashed;
}
/**
* @param {string|Buffer} password
* @param {number} opslimit
* @param {number} memlimit
* @return {Promise<string>}
*/
async crypto_pwhash_str(password, opslimit, memlimit) {
const hashed = Buffer.alloc(128, 0);
const bufPass = await Util.toBuffer(password);
await new Promise((resolve, reject) => {
this.sodium.crypto_pwhash_str_async(
hashed,
bufPass,
opslimit,
memlimit,
(e, res) => {
if (e) return reject(e);
return resolve(res);
}
);
});
return hashed.toString();
}
/**
* @param {string|Buffer} password
* @param {string|Buffer} hash
* @return {Promise<boolean>}
*/
async crypto_pwhash_str_verify(password, hash) {
const allocated = Buffer.alloc(128, 0);
(await Util.toBuffer(hash)).copy(allocated, 0, 0);
const bufPass = await Util.toBuffer(password);
return new Promise((resolve, reject) => {
this.sodium.crypto_pwhash_str_verify_async(
allocated,
bufPass,
(e, res) => {
if (e) return reject(e);
return resolve(res);
}
);
});
}
/**
* @param {string|Buffer} hash
* @param {number} opslimit
* @param {number} memlimit
* @return {Promise<boolean>}
*/
async crypto_pwhash_str_needs_rehash(hash, opslimit, memlimit) {
const allocated = Buffer.alloc(128, 0);
(await Util.toBuffer(hash)).copy(allocated, 0, 0);
return this.sodium.crypto_pwhash_str_needs_rehash(
allocated,
opslimit,
memlimit
);
}
/**
* @param {X25519SecretKey} secretKey
* @param {X25519PublicKey} publicKey
* @return {Promise<CryptographyKey>}
*/
async crypto_scalarmult(secretKey, publicKey) {
const shared = Buffer.alloc(32);
this.sodium.crypto_scalarmult(shared, secretKey.getBuffer(), publicKey.getBuffer());
return new CryptographyKey(
await Util.toBuffer(shared)
);
}
/**
*
* @param {CryptographyKey} secretKey
* @return {Promise<Buffer>}
*/
async crypto_scalarmult_base(secretKey) {
const buf = Buffer.alloc(32);
this.sodium.crypto_scalarmult_base(buf, secretKey.getBuffer());
return buf;
}
/**
* @param {string|Buffer} plaintext
* @param {Buffer} nonce
* @param {CryptographyKey} key
* @return {Promise<Buffer>}
*/
async crypto_secretbox(plaintext, nonce, key) {
const encrypted = Buffer.alloc(plaintext.length + 16);
this.sodium.crypto_secretbox_easy(
encrypted,
await Util.toBuffer(plaintext),
nonce,
key.getBuffer()
);
return encrypted;
}
/**
* @param {string|Buffer} message
* @param {CryptographyKey} key
* @return {Promise<Buffer>}
*/
async crypto_shorthash(message, key) {
const output = Buffer.alloc(8);
this.sodium.crypto_shorthash(
output,
await Util.toBuffer(message),
key.getBuffer()
);
return output;
}
/**
* @param {Buffer} ciphertext
* @param {Buffer} nonce
* @param {CryptographyKey} key
* @return {Promise<Buffer>}
*/
async crypto_secretbox_open(ciphertext, nonce, key) {
const decrypted = Buffer.alloc(ciphertext.length - 16);
if (!this.sodium.crypto_secretbox_open_easy(
decrypted,
ciphertext,
nonce,
key.getBuffer()
)) {
throw new SodiumError('Decryption failure');
}
return decrypted;
}
/**
* @param {CryptographyKey} key
* @return {Promise<array>} [state, header]
*/
async crypto_secretstream_xchacha20poly1305_init_push(key) {
const state = Buffer.alloc(this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_STATEBYTES);
const header = Buffer.alloc(this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES);
this.sodium.randombytes_buf(header);
this.sodium.crypto_secretstream_xchacha20poly1305_init_push(state, header, key.getBuffer());
return [state, header];
}
/**
* @param {Buffer} header
* @param {CryptographyKey} key
* @return {Promise<*>} Returns the opaque state object
*/
async crypto_secretstream_xchacha20poly1305_init_pull(header, key) {
if (header.length !== this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES) {
throw new SodiumError(`Header must be ${this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES} bytes long`);
}
const state = Buffer.alloc(this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_STATEBYTES);
this.sodium.crypto_secretstream_xchacha20poly1305_init_pull(state, header, key.getBuffer());
return state;
}
/**
* @param {*} state
* @param {string|Buffer} message
* @param {string|Buffer} ad
* @param {number} tag
* @return {Promise<Buffer>}
*/
async crypto_secretstream_xchacha20poly1305_push(state, message, ad = '', tag = 0) {
const ciphertext = Buffer.alloc(message.length + this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES);
this.sodium.crypto_secretstream_xchacha20poly1305_push(
state,
ciphertext,
await Util.toBuffer(message),
ad.length > 0 ? (await Util.toBuffer(ad)) : null,
Buffer.from([tag])
);
return ciphertext;
}
/**
* @param {*} state
* @param {Buffer} ciphertext
* @param {string|Buffer} ad
* @param {number} tag
* @return {Promise<Buffer>}
*/
async crypto_secretstream_xchacha20poly1305_pull(state, ciphertext, ad = '', tag = 0) {
if (ciphertext.length < this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES) {
throw new SodiumError('Invalid ciphertext size');
}
const plaintext = Buffer.alloc(ciphertext.length - this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES);
this.sodium.crypto_secretstream_xchacha20poly1305_pull(
state,
plaintext,
Buffer.from([tag]),
ciphertext,
ad.length > 0 ? (await Util.toBuffer(ad)) : null
);
return plaintext;
}
/**
* @param {*} state
* @return {Promise<void>}
*/
async crypto_secretstream_xchacha20poly1305_rekey(state) {
this.sodium.crypto_secretstream_xchacha20poly1305_rekey(state);
}
/**
* @param {string|Buffer} message,
* @param {Ed25519SecretKey} secretKey
* @return {Promise<Buffer>}
*/
async crypto_sign(message, secretKey) {
const signed = Buffer.alloc(message.length + 64);
this.sodium.crypto_sign(signed, await Util.toBuffer(message), secretKey.getBuffer());
return signed;
}
/**
* @param {Buffer} signedMessage,
* @param {Ed25519PublicKey} publicKey
* @return {Promise<Buffer>}
*/
async crypto_sign_open(signedMessage, publicKey) {
const original = Buffer.alloc(signedMessage.length - 64);
this.sodium.crypto_sign_open(original, await Util.toBuffer(signedMessage), publicKey.getBuffer());
return original;
}
/**
* @param {string|Buffer} message,
* @param {Ed25519SecretKey} secretKey
* @return {Promise<Buffer>}
*/
async crypto_sign_detached(message, secretKey) {
const signature = Buffer.alloc(64);
this.sodium.crypto_sign_detached(signature, await Util.toBuffer(message), secretKey.getBuffer());
return signature;
}
/**
* @param {string|Buffer} message,
* @param {Ed25519PublicKey} publicKey
* @param {Buffer} signature
* @return {Promise<Buffer>}
*/
async crypto_sign_verify_detached(message, publicKey, signature) {
return this.sodium.crypto_sign_verify_detached(
signature,
await Util.toBuffer(message),
publicKey.getBuffer()
);
}
/**
* @return {Promise<CryptographyKey>}
*/
async crypto_sign_keypair() {
const sK = Buffer.alloc(64, 0);
const pK = Buffer.alloc(32, 0);
this.sodium.crypto_sign_keypair(pK, sK);
return new CryptographyKey(
Buffer.concat([sK, pK])
);
}
/**
* @param {Buffer} seed
* @return {Promise<CryptographyKey>}
*/
async crypto_sign_seed_keypair(seed) {
const sK = Buffer.alloc(64, 0);
const pK = Buffer.alloc(32, 0);
this.sodium.crypto_sign_seed_keypair(pK, sK, seed);
return new CryptographyKey(
Buffer.concat([sK, pK])
);
}
/**
* @param {Ed25519SecretKey} sk
* @return {Promise<Buffer>}
*/
async crypto_sign_ed25519_sk_to_curve25519(sk) {
const xsk = Buffer.alloc(32);
this.sodium.crypto_sign_ed25519_sk_to_curve25519(xsk, sk.getBuffer());
return xsk;
}
/**
* @param {Ed25519PublicKey} pk
* @return {Promise<Buffer>}
*/
async crypto_sign_ed25519_pk_to_curve25519(pk) {
const xpk = Buffer.alloc(32);
this.sodium.crypto_sign_ed25519_pk_to_curve25519(xpk, pk.getBuffer());
return xpk;
}
/**
* @param {number} length
* @param {Buffer} nonce
* @param {CryptographyKey} key
* @return {Promise<Buffer>}
*/
async crypto_stream(length, nonce, key) {
const output = Buffer.alloc(length);
this.sodium.crypto_stream(
output,
await Util.toBuffer(nonce),
key.getBuffer()
);
return output;
}
/**
* @param {string|Buffer} plaintext
* @param {Buffer} nonce
* @param {CryptographyKey} key
* @return {Promise<Buffer>}
*/
async crypto_stream_xor(plaintext, nonce, key) {
const output = Buffer.alloc(plaintext.length);
this.sodium.crypto_stream_xor(
output,
await Util.toBuffer(plaintext),
await Util.toBuffer(nonce),
key.getBuffer()
);
return output;
}
/**
* @param {number} number
* @return {Promise<Buffer>}
*/
async randombytes_buf(number) {
let buf = Buffer.alloc(number);
this.sodium.randombytes_buf(buf);
return buf;
}
/**
* @param {number} upperBound
* @return {Promise<number>}
*/
async randombytes_uniform(upperBound) {
return this.sodium.randombytes_uniform(upperBound);
}
/**
* @param {Uint8Array} val
* @param {Uint8Array} addv
* @return {Promise<Buffer>}
*/
async sodium_add(val, addv) {
const buf = await Util.cloneBuffer(val);
this.sodium.sodium_add(buf, addv);
return buf;
}
/**
* @param {Buffer} input
* @return {Promise<string>}
*/
async sodium_bin2hex(input) {
let str = "", b, c, x;
for (let i = 0; i < input.length; i++) {
c = input[i] & 0xf;
b = input[i] >>> 4;
x =
((87 + c + (((c - 10) >> 8) & ~38)) << 8) |
(87 + b + (((b - 10) >> 8) & ~38));
str += String.fromCharCode(x & 0xff) + String.fromCharCode(x >>> 8);
}
return str;
}
/**
* @param {Buffer} b1
* @param {Buffer} b2
* @return {Promise<number>}
*/
async sodium_compare(b1, b2) {
return this.sodium.sodium_compare(b1, b2);
}
/**
* @param {Buffer|string} hex
* @param {string|null} ignore
* @return {Promise<Buffer>}
*/
async sodium_hex2bin(hex, ignore = null) {
let bin_pos = 0,
hex_pos = 0,
c = 0,
c_acc = 0,
c_alpha0 = 0,
c_alpha = 0,
c_num0 = 0,
c_num = 0,
c_val = 0,
state = 0;
const bin = Buffer.alloc(hex.length >> 1, 0);
while (hex_pos < hex.length) {
c = hex.charCodeAt(hex_pos);
c_num = c ^ 48;
c_num0 = (c_num - 10) >> 8;
c_alpha = (c & ~32) - 55;
c_alpha0 = ((c_alpha - 10) ^ (c_alpha - 16)) >> 8;
if ((c_num0 | c_alpha0) === 0) {
if (ignore && state === 0 && ignore.indexOf(c) >= 0) {
hex_pos++;
continue;
}
break;
}
c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha);
if (state === 0) {
c_acc = c_val * 16;
} else {
bin[bin_pos++] = c_acc | c_val;
}
state = ~state;
hex_pos++;
}
return bin;
}
/**
* @param {Buffer} buf
* @return {Promise<Buffer>}
*/
async sodium_increment(buf) {
return this.sodium.sodium_increment(buf);
}
/**
* @param {Buffer} buf
* @param {number} len
* @return {Promise<Buffer>}
*/
async sodium_is_zero(buf, len) {
return this.sodium.sodium_is_zero(buf, len);
}
/**
* @param {Buffer} b1
* @param {Buffer} b2
* @return {Promise<boolean>}
*/
async sodium_memcmp(b1, b2) {
return this.sodium.sodium_memcmp(b1, b2);
}
/**
* @param {Buffer} buf
* @return {Promise<void>}
*/
async sodium_memzero(buf) {
this.sodium.sodium_memzero(buf);
}
/**
* @param {string|Buffer} buf
* @param {number} blockSize
* @return {Promise<Buffer>}
*/
async sodium_pad(buf, blockSize) {
buf = await Util.toBuffer(buf);
let length = buf.length + (buf.length % blockSize);
if (length < blockSize) {
length += blockSize;
}
const padded = Buffer.alloc(length + 100);
buf.copy(padded, 0, 0);
const sliceto = this.sodium.sodium_pad(padded, buf.length, blockSize);
return padded.slice(0, sliceto);
}
/**
*
* @param {string|Buffer} buf
* @param {number} blockSize
* @return {Promise<Buffer>}
*/
async sodium_unpad(buf, blockSize) {
const outlen = this.sodium.sodium_unpad(buf, buf.length, blockSize);
return buf.slice(0, outlen);
}
};

View file

@ -0,0 +1,80 @@
"use strict";
/* istanbul ignore if */
if (typeof (Buffer) === 'undefined') {
let Buffer = require('buffer/').Buffer;
}
module.exports = class CryptographyKey {
/**
* Note: We use Object.defineProperty() to hide the buffer inside of the
* CryptographyKey object to prevent accidental leaks.
*
* @param {Buffer} buf
*/
constructor(buf) {
if (!Buffer.isBuffer(buf)) {
throw new TypeError('Argument 1 must be an instance of Buffer.');
}
Object.defineProperty(this, 'buffer', {
enumerable: false,
value: buf.slice()
});
}
/**
* @return {CryptographyKey}
*/
static from() {
return new CryptographyKey(Buffer.from(...arguments));
}
/**
* @return {boolean}
*/
isEd25519Key() {
return false;
}
/**
* @return {boolean}
*/
isX25519Key() {
return false;
}
/**
* @return {boolean}
*/
isPublicKey() {
return false;
}
/**
* @return {Number}
*/
getLength() {
return this.buffer.length;
}
/**
* @return {Buffer}
*/
getBuffer() {
return this.buffer;
}
/**
* @param {string} encoding
*/
toString(encoding = 'utf-8') {
/* istanbul ignore if */
return this.getBuffer().toString(encoding);
}
/**
* @return {Buffer}
*/
slice() {
return this.buffer.slice(...arguments);
}
};

View file

@ -0,0 +1,28 @@
const CryptographyKey = require('../cryptography-key');
class Ed25519PublicKey extends CryptographyKey {
constructor(buf) {
if (buf.length !== 32) {
throw new Error('Ed25519 public keys must be 32 bytes long');
}
super(buf);
this.keyType = 'ed25519';
this.publicKey = true;
}
/**
* @return {Ed25519PublicKey}
*/
static from() {
return new Ed25519PublicKey(Buffer.from(...arguments));
}
isEd25519Key() {
return true;
}
isPublicKey() {
return true;
}
}
module.exports = Ed25519PublicKey;

View file

@ -0,0 +1,29 @@
const CryptographyKey = require('../cryptography-key');
class Ed25519SecretKey extends CryptographyKey {
constructor(buf) {
if (buf.length !== 64) {
throw new Error('Ed25519 secret keys must be 64 bytes long');
}
super(buf);
this.keyType = 'ed25519';
this.publicKey = false;
}
/**
* @return {Ed25519SecretKey}
*/
static from() {
return new Ed25519SecretKey(Buffer.from(...arguments));
}
isEd25519Key() {
return true;
}
isPublicKey() {
return false;
}
}
module.exports = Ed25519SecretKey;

View file

@ -0,0 +1,29 @@
const CryptographyKey = require('../cryptography-key');
class X25519PublicKey extends CryptographyKey {
constructor(buf) {
if (buf.length !== 32) {
throw new Error('X25519 public keys must be 32 bytes long');
}
super(buf);
this.keyType = 'x25519';
this.publicKey = true;
}
/**
* @return {X25519PublicKey}
*/
static from() {
return new X25519PublicKey(Buffer.from(...arguments));
}
isX25519Key() {
return true;
}
isPublicKey() {
return true;
}
}
module.exports = X25519PublicKey;

View file

@ -0,0 +1,29 @@
const CryptographyKey = require('../cryptography-key');
class X25519SecretKey extends CryptographyKey {
constructor(buf) {
if (buf.length !== 32) {
throw new Error('X25519 secret keys must be 32 bytes long');
}
super(buf);
this.keyType = 'x25519';
this.publicKey = false;
}
/**
* @return {X25519SecretKey}
*/
static from() {
return new X25519SecretKey(Buffer.from(...arguments));
}
isX25519Key() {
return true;
}
isPublicKey() {
return false;
}
}
module.exports = X25519SecretKey;

View file

@ -0,0 +1,74 @@
"use strict";
const crypto = require('crypto');
const Poly1305 = require('poly1305-js');
const Util = require('./util');
const XSalsa20 = require('xsalsa20');
/* istanbul ignore if */
if (typeof (Buffer) === 'undefined') {
let Buffer = require('buffer/').Buffer;
}
module.exports = class SodiumPolyfill {
/**
* @param {string|Buffer} message
* @param {CryptographyKey} key
* @return {Promise<Buffer>}
*/
static async crypto_onetimeauth(message, key) {
return Poly1305.onetimeauth(
await Util.toBuffer(message),
key.getBuffer()
);
}
/**
* @param {string|Buffer} message
* @param {CryptographyKey} key
* @param {Buffer} tag
* @return {Promise<boolean>}
*/
static async crypto_onetimeauth_verify(message, key, tag) {
return Poly1305.onetimeauth_verify(
await Util.toBuffer(message),
key.getBuffer(),
await Util.toBuffer(tag)
);
}
/**
* @param {string|Buffer} plaintext
* @param {Buffer} nonce
* @param {CryptographyKey} key
* @return {Promise<Buffer>}
*/
static async crypto_stream_xor(plaintext, nonce, key) {
const stream = XSalsa20(nonce, key.getBuffer());
const output = stream.update(plaintext);
stream.finalize();
return Util.toBuffer(output);
}
/**
* Polyfill crypto_pwhash_str_needs_rehash() for bindings that don't
* include this (somewhat new) helper function.
*
* @param {string|Buffer} hash
* @param {number} opslimit
* @param {number} memlimit
* @return {Promise<boolean>}
*/
static async crypto_pwhash_str_needs_rehash(hash, opslimit, memlimit) {
const pwhash = (await Util.toBuffer(hash)).toString('utf-8');
const pieces = pwhash.split('$');
const expect = 'm=' + (memlimit >> 10) + ',t=' + opslimit + ',p=1';
if (expect.length !== pieces[3].length) {
return true;
}
return !crypto.timingSafeEqual(
await Util.toBuffer(expect),
await Util.toBuffer(pieces[3])
);
}
};

View file

@ -0,0 +1 @@
module.exports = class SodiumError extends Error {};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,133 @@
"use strict";
/* istanbul ignore if */
if (typeof (Buffer) === 'undefined') {
let Buffer = require('buffer/').Buffer;
}
const arrayToBuffer = require('typedarray-to-buffer');
module.exports = class Util
{
static async cloneBuffer(buf) {
return Buffer.from(buf);
}
/**
* Define the sodium constants
*
* @param {object} anyobject
* @return {object}
*/
static populateConstants(anyobject) {
anyobject.LIBRARY_VERSION_MAJOR = 10;
anyobject.LIBRARY_VERSION_MINOR = 2;
anyobject.VERSION_STRING = '1.0.17';
anyobject.BASE64_VARIANT_ORIGINAL = 1;
anyobject.BASE64_VARIANT_ORIGINAL_NO_PADDING = 3;
anyobject.BASE64_VARIANT_URLSAFE = 5;
anyobject.BASE64_VARIANT_URLSAFE_NO_PADDING = 7;
anyobject.CRYPTO_AEAD_AES256GCM_KEYBYTES = 32;
anyobject.CRYPTO_AEAD_AES256GCM_NSECBYTES = 0;
anyobject.CRYPTO_AEAD_AES256GCM_NPUBBYTES = 12;
anyobject.CRYPTO_AEAD_AES256GCM_ABYTES = 16;
anyobject.CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES = 32;
anyobject.CRYPTO_AEAD_CHACHA20POLY1305_NSECBYTES = 0;
anyobject.CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES = 8;
anyobject.CRYPTO_AEAD_CHACHA20POLY1305_ABYTES = 16;
anyobject.CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES = 32;
anyobject.CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES = 0;
anyobject.CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES = 12;
anyobject.CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES = 16;
anyobject.CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES = 32;
anyobject.CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NSECBYTES = 0;
anyobject.CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES = 24;
anyobject.CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES = 16;
anyobject.CRYPTO_AUTH_BYTES = 32;
anyobject.CRYPTO_AUTH_KEYBYTES = 32;
anyobject.CRYPTO_BOX_SEALBYTES = 16;
anyobject.CRYPTO_BOX_SECRETKEYBYTES = 32;
anyobject.CRYPTO_BOX_PUBLICKEYBYTES = 32;
anyobject.CRYPTO_BOX_KEYPAIRBYTES = 64;
anyobject.CRYPTO_BOX_MACBYTES = 16;
anyobject.CRYPTO_BOX_NONCEBYTES = 24;
anyobject.CRYPTO_BOX_SEEDBYTES = 32;
anyobject.CRYPTO_KDF_BYTES_MIN = 16;
anyobject.CRYPTO_KDF_BYTES_MAX = 64;
anyobject.CRYPTO_KDF_CONTEXTBYTES = 8;
anyobject.CRYPTO_KDF_KEYBYTES = 32;
anyobject.CRYPTO_KX_BYTES = 32;
anyobject.CRYPTO_KX_PRIMITIVE = 'x25519blake2b';
anyobject.CRYPTO_KX_SEEDBYTES = 32;
anyobject.CRYPTO_KX_KEYPAIRBYTES = 64;
anyobject.CRYPTO_KX_PUBLICKEYBYTES = 32;
anyobject.CRYPTO_KX_SECRETKEYBYTES = 32;
anyobject.CRYPTO_KX_SESSIONKEYBYTES = 32;
anyobject.CRYPTO_GENERICHASH_BYTES = 32;
anyobject.CRYPTO_GENERICHASH_BYTES_MIN = 16;
anyobject.CRYPTO_GENERICHASH_BYTES_MAX = 64;
anyobject.CRYPTO_GENERICHASH_KEYBYTES = 32;
anyobject.CRYPTO_GENERICHASH_KEYBYTES_MIN = 16;
anyobject.CRYPTO_GENERICHASH_KEYBYTES_MAX = 64;
anyobject.CRYPTO_GENERICHASH_STATEBYTES = 384;
anyobject.CRYPTO_PWHASH_SALTBYTES = 16;
anyobject.CRYPTO_PWHASH_STRPREFIX = '$argon2id$';
anyobject.CRYPTO_PWHASH_ALG_ARGON2I13 = 1;
anyobject.CRYPTO_PWHASH_ALG_ARGON2ID13 = 2;
anyobject.CRYPTO_PWHASH_ALG_DEFAULT = anyobject.CRYPTO_PWHASH_ALG_ARGON2ID13;
anyobject.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE = 2;
anyobject.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE = 67108864;
anyobject.CRYPTO_PWHASH_OPSLIMIT_MODERATE = 3;
anyobject.CRYPTO_PWHASH_MEMLIMIT_MODERATE = 268435456;
anyobject.CRYPTO_PWHASH_OPSLIMIT_SENSITIVE = 4;
anyobject.CRYPTO_PWHASH_MEMLIMIT_SENSITIVE = 1073741824;
anyobject.CRYPTO_PWHASH_SCRYPTSALSA208SHA256_SALTBYTES = 32;
anyobject.CRYPTO_SCALARMULT_BYTES = 32;
anyobject.CRYPTO_SCALARMULT_SCALARBYTES = 32;
anyobject.CRYPTO_SHORTHASH_BYTES = 8;
anyobject.CRYPTO_SHORTHASH_KEYBYTES = 16;
anyobject.CRYPTO_SECRETBOX_KEYBYTES = 32;
anyobject.CRYPTO_SECRETBOX_MACBYTES = 16;
anyobject.CRYPTO_SECRETBOX_NONCEBYTES = 24;
anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_STATEBYTES = 52;
anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES = 17;
anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES = 24;
anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES = 32;
anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH = 0;
anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PULL = 1;
anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY = 2;
anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL = 3;
anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX = 0x3fffffff80;
anyobject.CRYPTO_SIGN_BYTES = 64;
anyobject.CRYPTO_SIGN_SEEDBYTES = 32;
anyobject.CRYPTO_SIGN_PUBLICKEYBYTES = 32;
anyobject.CRYPTO_SIGN_SECRETKEYBYTES = 64;
anyobject.CRYPTO_SIGN_KEYPAIRBYTES = 96;
anyobject.CRYPTO_STREAM_KEYBYTES = 32;
anyobject.CRYPTO_STREAM_NONCEBYTES = 24;
return anyobject;
}
/**
* Coerce input to a Buffer, throwing a TypeError if it cannot be coerced.
*
* @param {string|Buffer|Uint8Array|Promise<Buffer>} stringOrBuffer
* @returns {Buffer}
*/
static async toBuffer(stringOrBuffer)
{
if (Buffer.isBuffer(stringOrBuffer)) {
return stringOrBuffer;
} else if (stringOrBuffer === null) {
return null;
} else if (typeof(stringOrBuffer) === 'string') {
return Buffer.from(stringOrBuffer, 'binary');
} else if (stringOrBuffer instanceof Uint8Array) {
return arrayToBuffer(stringOrBuffer);
} else if (stringOrBuffer instanceof Promise) {
return await stringOrBuffer;
} else {
throw new TypeError('Invalid type; string or buffer expected');
}
}
};

View file

@ -0,0 +1,64 @@
{
"name": "sodium-plus",
"version": "0.9.0",
"description": "The Sodium Cryptography Library",
"author": "Paragon Initiative Enterprises, LLC",
"license": "ISC",
"keywords": [
"easy-to-use",
"libsodium",
"sodium",
"cryptography",
"cryptography library",
"elliptic curve cryptography",
"ecdh",
"eddsa",
"diffie-hellman",
"authentication",
"encryption",
"digital signatures",
"signatures",
"curve25519",
"ed25519",
"x25519",
"xsalsa20poly1305",
"xchacha20",
"poly1305"
],
"peerDependencies": {
"sodium-native": "^3.2.0"
},
"dependencies": {
"buffer": "^5.6.0",
"libsodium-wrappers": "^0.7.6",
"poly1305-js": "^0.4.2",
"typedarray-to-buffer": "^3.1.5",
"xsalsa20": "^1.1.0"
},
"repository": "https://github.com/paragonie/sodium-plus",
"main": "index.js",
"types": "index.d.ts",
"files": [
"build",
"build.sh",
"dist/sodium-plus.js",
"dist/sodium-plus.min.js",
"index.d.ts",
"index.js",
"lib/"
],
"scripts": {
"test": "mocha",
"parallel-test": "mocha-parallel-tests"
},
"devDependencies": {
"@types/node": "^14.0.24",
"assert": "^2.0.0",
"browserify": "^16.5.1",
"chai": "^4.2.0",
"mkdirp": "^1.0.4",
"mocha-parallel-tests": "^2.3.0",
"mocha": "^8.0.1",
"tinyify": "^2.5.2"
}
}

View file

@ -0,0 +1,13 @@
const { expect } = require('chai');
module.exports = async function expectError(promised, message) {
let thrown = false;
try {
await promised;
} catch (e) {
thrown = true;
expect(message).to.be.equal(e.message);
}
if (!thrown) {
throw new Error('Function did not throw');
}
};

View file

@ -0,0 +1,37 @@
const { describe, it } = require('mocha');
const { expect } = require('chai');
const { SodiumPlus, X25519SecretKey, X25519PublicKey } = require('../index');
let sodium;
describe('Backend', () => {
it('crypto_box_keypair_from_secretkey_and_publickey', async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let a = Buffer.alloc(32);
let b = Buffer.alloc(32);
let c = Buffer.alloc(31);
let d = await sodium.crypto_box_keypair_from_secretkey_and_publickey(
new X25519SecretKey(a),
new X25519PublicKey(b)
);
expect(64).to.be.equal(d.buffer.length);
expect(() => {
sodium.crypto_box_keypair_from_secretkey_and_publickey(
new X25519SecretKey(c),
new X25519PublicKey(b)
)
.then(() => {})
.catch((e) => { throw e });
}).to.throw('X25519 secret keys must be 32 bytes long');
expect(() => {
sodium.crypto_box_keypair_from_secretkey_and_publickey(
new X25519SecretKey(a),
new X25519PublicKey(c)
)
.then(() => {})
.catch((e) => { throw e });
}).to.throw('X25519 public keys must be 32 bytes long');
});
});

View file

@ -0,0 +1,83 @@
const assert = require('assert');
const crypto = require('crypto');
const { describe, it } = require('mocha');
const { expect } = require('chai');
const {
CryptographyKey,
Ed25519PublicKey,
Ed25519SecretKey,
X25519PublicKey,
X25519SecretKey
} = require('../index');
let sodium;
describe('CryptographyKey', () => {
it('Internal buffer is hidden from stack traces and iterators', async () => {
let bytes = await crypto.randomBytes(32);
let key = new CryptographyKey(bytes);
assert(Object.keys(key).length === 0, 'There should be no keys when you dump an object!');
expect(bytes.toString('hex')).to.be.equals(key.getBuffer().toString('hex'));
expect(bytes.toString('hex')).to.be.equals(key.toString('hex'));
});
it('constructor rejects invalid types', async () => {
let bytes = await crypto.randomBytes(32);
let key = new CryptographyKey(bytes);
// For test coverage
expect(bytes.toString('hex')).to.be.equal(key.slice().toString('hex'));
expect(false).to.be.equal(key.isPublicKey());
});
it('constructor rejects invalid types', () => {
expect(() => {
new CryptographyKey(new Uint8Array(32))
}).to.throw('Argument 1 must be an instance of Buffer.');
});
it('from()', async () => {
let key = CryptographyKey.from('808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f', 'hex');
expect(key.getBuffer().toString('hex')).to.be.equals('808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f');
let ed25519sk = Ed25519SecretKey.from(
'88c6102ed8b3278ae7e95ebcd4ed3f1a513d2fd3c1a88f5ecbda5f95209ce709' +
'324095af3d25e0f205d1a1297d01e810940063d05fc247d2042f6fc2f98a55c2',
'hex'
);
expect(ed25519sk instanceof Ed25519SecretKey).to.be.equals(true);
let ed25519pk = Ed25519PublicKey.from(
'324095af3d25e0f205d1a1297d01e810940063d05fc247d2042f6fc2f98a55c2',
'hex'
);
expect(ed25519pk instanceof Ed25519PublicKey).to.be.equals(true);
let x25519sk = X25519SecretKey.from(
'fcb38e648f61e145c96be1a89776754b0a2e28ba57d3024ecae892dc5d93ec26',
'hex'
);
expect(x25519sk instanceof X25519SecretKey).to.be.equals(true);
let x25519pk = X25519PublicKey.from(
'81149890dc709032327ab8d2628df8c0c8163f59bbb92a6fc3a83cb34864d503',
'hex'
);
expect(x25519pk instanceof X25519PublicKey).to.be.equals(true);
expect(ed25519sk.isPublicKey()).to.be.equals(false);
expect(ed25519pk.isPublicKey()).to.be.equals(true);
expect(ed25519sk.isEd25519Key()).to.be.equals(true);
expect(ed25519pk.isEd25519Key()).to.be.equals(true);
expect(ed25519sk.isX25519Key()).to.be.equals(false);
expect(ed25519pk.isX25519Key()).to.be.equals(false);
expect(x25519sk.isPublicKey()).to.be.equals(false);
expect(x25519pk.isPublicKey()).to.be.equals(true);
expect(x25519sk.isEd25519Key()).to.be.equals(false);
expect(x25519pk.isEd25519Key()).to.be.equals(false);
expect(x25519sk.isX25519Key()).to.be.equals(true);
expect(x25519pk.isX25519Key()).to.be.equals(true);
expect(() => {
new Ed25519SecretKey(Buffer.alloc(32))
}).to.throw('Ed25519 secret keys must be 64 bytes long');
expect(() => {
new Ed25519PublicKey(Buffer.alloc(64))
}).to.throw('Ed25519 public keys must be 32 bytes long');
});
});

View file

@ -0,0 +1,135 @@
<!-- From a blog post -- https://paragonie.com/blog/2019/10/improving-cryptography-javascript-ecosystem -->
It's been more than eight years since [Javascript Cryptography Considered Harmful](https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/august/javascript-cryptography-considered-harmful) was published.
It's just as true today as it was eight years ago that JavaScript cryptography in a web browser is dangerous. **But the *ecosystem* itself has changed immensely in this time.**
## JavaScript Cryptography, Considered
Between the continued rise in popularity of JavaScript frameworks (e.g. React) and the prevalence of cross-platform development tools (Cordova, Electron), it's now possible to write JavaScript code once and deploy it on a web server, in a webpage, in a browser extension, in a native mobile app, and in desktop software... with no changes to your JavaScript code.
Despite eight years of transformative change to the programming landscape, the JavaScript ecosystem has been severely neglected by the security industry, especially with regards to usable cryptography.
<!--
### Putting Skin in the Game
Anyone who's already familiar with our work knows this already, but we find the security industry's neglect of programming language ecosystems simply *unacceptable*.
Instead of unhelpful remarks like "Want your code to be secure? Stop writing it in [languages the security industry dislike]", the security industry's responsibility should be to make the languages and tools developers want to use secure.
This is as true for how Node.js developers are treated by many security experts today as it was for how PHP developers were treated five years ago.
Unlike most security companies, we're actually putting in the time and work to improve matters. We encourage our colleagues, and challenge our competitors, to step up and do the same (or, at the very least, cease with the gatekeeping remarks).
-->
## What PIE Has Already Done for JavaScript Cryptography
### Sodium-Plus - A Positive Experience for JS Cryptography
This month we released [Sodium-Plus](https://github.com/paragonie/sodium-plus), a pluggable, cross-platform, type-safe interface for libsodium to make it easier to write safe and secure JavaScript cryptography code. Our initial announcement was [posted on dev.to](https://dev.to/paragonie/sodium-plus-a-positive-cryptography-experience-for-javascript-developers-2p08).
To be clear: This isn't a new libsodium binding. What sodium-plus does is wrap one of the existing bindings (e.g. [sodium-native](https://github.com/paragonie/sodium-plus)) and—regardless of how unpleasant the low-level binding's API is to work with—lets you interact with it using a sane and simple asynchronous API.
Instead of writing code like this:
<pre><code class="javascript">const sodium = require('sodium-native');
// Key generation
let aliceSecret = Buffer.alloc(32);
let alicePublic = Buffer.alloc(32);
let bobSecret = Buffer.alloc(32);
let bobPublic = Buffer.alloc(32);
sodium.crypto_box_keypair(alicePublic, aliceSecret);
sodium.crypto_box_keypair(bobPublic, bobSecret);
// Nonce
let nonce = Buffer.alloc(24);
sodium.randombytes_buf(nonce);
// Plaintext
let message = 'A string I want to encrypt.';
let plaintext = Buffer.from(message);
// Encrypt
let ciphertext = Buffer.alloc(plaintext.length + 16);
sodium.crypto_box_easy(ciphertext, plaintext, nonce, bobPublic, aliceSecret);
console.log(ciphertext.toString('hex'));
// Decrypt
let decrypted = Buffer.alloc(ciphertext.length - 16);
sodium.crypto_box_open_easy(decrypted, ciphertext, nonce, alicePublic, bobSecret);
console.log(decrypted.toString());
</code></pre>
...you can just write this:
<pre><code class="javascript">const { SodiumPlus } = require('sodium-plus');
let sodium;
(async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let aliceKeypair = await sodium.crypto_box_keypair();
let aliceSecret = await sodium.crypto_box_secretkey(aliceKeypair);
let alicePublic = await sodium.crypto_box_publickey(aliceKeypair);
let bobKeypair = await sodium.crypto_box_keypair();
let bobSecret = await sodium.crypto_box_secretkey(bobKeypair);
let bobPublic = await sodium.crypto_box_publickey(bobKeypair);
let nonce = await sodium.randombytes_buf(24);
let plaintext = 'Your message goes here';
let ciphertext = await sodium.crypto_box(plaintext, nonce, aliceSecret, bobPublic);
console.log(ciphertext.toString('hex'));
let decrypted = await sodium.crypto_box_open(ciphertext, nonce, bobSecret, alicePublic);
console.log(decrypted.toString());
})();
</code></pre>
The second snippet works in browsers, browser extensions, mobile apps, desktop apps, and webservers, without requiring a C compiler be integrated into your JavaScript toolkit.
By default, Sodium-Plus uses [libsodium-wrappers](https://github.com/jedisct1/libsodium.js). However, if sodium-native is installed, it will opportunistically use that first, since sodium-native offers much better performance.
One of the many features included in Sodium-Plus is type-safety with cryptography keys. An `Ed25519SecretKey` cannot be used by `crypto_box()`, only by `crypto_sign()`. This prevents a whole host of usage mistakes that passing around bare `Buffer` objects cannot prevent.
Check out [the Sodium-Plus documentation](https://github.com/paragonie/sodium-plus/tree/master/docs) for more information.
### Certainty.js: CACert Management for JavaScript Projects
We originally created [Certainty](https://paragonie.com/blog/2017/10/certainty-automated-cacert-pem-management-for-php-software) to solve the problem of "developers disabling SSL/TLS verification", which was in many cases actually a symptom of the "unreliable/outdated CACert bundle" problem.
Until recently, there was no congruent means for auto-updating your CACert bundles for Node.js developers. So we decided to write [certainty.js](https://github.com/paragonie/certainty-js).
<pre><code class="javascript">const {Certainty} = require('certainty-js');
const http = require('request-promise-native');
(async function () {
let options = {
'ca': await Certainty.getLatestCABundle('/path/to/directory'),
'uri': 'https://php-chronicle.pie-hosted.com/chronicle/lasthash',
'minVersion': 'TLSv1.2',
'strictSSL': true,
'timeout': 30000
};
// Send request...
console.log(await http.get(options));
})();</code></pre>
The next releases of Certainty.js will include the LocalCACertBuilder features from the PHP version, as well as a refactor to use `Sodium-Plus`.
### CipherSweet.js
Scenario: You need to encrypt some of your database fields, but you still need to use those fields in SELECT queries somehow. Is there a secure way to achieve this result without having to invoke a lot of new and experimental cryptography primitives?
It turns out: Yes, you can. Our proposed implementation is called [CipherSweet](https://paragonie.com/blog/2019/01/ciphersweet-searchable-encryption-doesn-t-have-be-bitter).
CipherSweet has already been ported from PHP to Node.js, with other languages coming soon.
You can find [CipherSweet-js](https://github.com/paragonie/ciphersweet-js) on Github. The documentation is available [on our website](https://ciphersweet.paragonie.com/node.js).
## Our Work Continues
Like many other programming languages, JavaScript has its own needs and unique challenges. We
remain committed to improving the security and usability of the languages, frameworks, and tools developers want to use, and strive towards a more private and secure Internet for everyone.
If your company relies on PHP or JavaScript code and needs expert assistance with solving cryptography problems in your application, [reach out to us](https://paragonie.com/contact). We [write code](https://paragonie.com/service/app-dev), [audit code](https://paragonie.com/service/code-review), and [offer consultation for security designs](https://paragonie.com/service/technology-consulting).

View file

@ -0,0 +1,33 @@
const { describe, it } = require('mocha');
const { expect } = require('chai');
const { SodiumPlus } = require('../index');
const VERBOSE = false;
let sodium;
(async () => {
if (!sodium) sodium = await SodiumPlus.auto();
if (VERBOSE) {
console.log({
'libsodium-wrappers': sodium.isLibsodiumWrappers(),
'sodium-native': sodium.isSodiumNative()
});
}
})();
describe('SodiumPlus', () => {
it('crypto_pwhash', async function() {
this.timeout(0);
if (!sodium) sodium = await SodiumPlus.auto();
let password = 'correct horse battery staple';
let salt = Buffer.from('808182838485868788898a8b8c8d8e8f', 'hex');
let hashed = await sodium.crypto_pwhash(
16,
password,
salt,
sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);
expect(hashed.toString('hex')).to.be.equals('720f95400220748a811bca9b8cff5d6e');
});
});

View file

@ -0,0 +1,53 @@
const assert = require('assert');
const { describe, it } = require('mocha');
const { expect } = require('chai');
const { SodiumPlus } = require('../index');
const VERBOSE = false;
let sodium;
(async () => {
if (!sodium) sodium = await SodiumPlus.auto();
if (VERBOSE) {
console.log({
'libsodium-wrappers': sodium.isLibsodiumWrappers(),
'sodium-native': sodium.isSodiumNative()
});
}
})();
describe('SodiumPlus', () => {
it('SodiumPlus.crypto_pwhash_str', async function () {
this.timeout(0);
if (!sodium) sodium = await SodiumPlus.auto();
let password = 'correct horse battery staple';
let hashed = await sodium.crypto_pwhash_str(
password,
sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);
assert(hashed);
assert(await sodium.crypto_pwhash_str_verify(password, hashed));
assert(await sodium.crypto_pwhash_str_verify('incorrect password', hashed) === false);
let needs;
needs = await sodium.crypto_pwhash_str_needs_rehash(
hashed,
sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);
expect(needs).to.be.equals(false);
needs = await sodium.crypto_pwhash_str_needs_rehash(
hashed,
sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE + 1,
sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);
expect(needs).to.be.equals(true);
needs = await sodium.crypto_pwhash_str_needs_rehash(
hashed,
sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE << 1
);
expect(needs).to.be.equals(true);
});
});

View file

@ -0,0 +1,732 @@
const assert = require('assert');
const expectError = require('./async-helper');
const fsp = require('fs').promises;
const path = require('path');
const { describe, it } = require('mocha');
const { expect } = require('chai');
const { CryptographyKey, SodiumPlus, X25519PublicKey, X25519SecretKey } = require('../index');
const Util = require('../lib/util');
const VERBOSE = false;
let sodium;
(async () => {
if (!sodium) sodium = await SodiumPlus.auto();
if (VERBOSE) {
console.log({
'libsodium-wrappers': sodium.isLibsodiumWrappers(),
'sodium-native': sodium.isSodiumNative()
});
}
})();
describe('SodiumPlus', () => {
it('ensureLoaded', async () => {
if (!sodium) sodium = await SodiumPlus.auto();
await sodium.ensureLoaded();
expect('string').to.be.equal(typeof sodium.getBackendName());
expect('boolean').to.be.equal(typeof sodium.isSodiumNative());
expect('boolean').to.be.equal(typeof sodium.isLibsodiumWrappers());
});
it('index.js', async () => {
const indexFile = require('../index');
expect(typeof indexFile.getBackendObject()).to.be.equal('function');
expect(typeof indexFile.getBackendObject('SodiumNative')).to.be.equal('function');
expect(typeof indexFile.getBackendObject('LibsodiumWrappers')).to.be.equal('function');
expect(() => {
indexFile.getBackendObject('Sodium')
}).to.throw('Unrecognized backend type: Sodium');
});
it('SodiumPlus.CONSTANTS', async () => {
if (!sodium) sodium = await SodiumPlus.auto();
let dummy = Util.populateConstants({});
for (let val in dummy) {
expect(sodium.backend[val]).to.be.equals(dummy[val]);
expect(sodium[val]).to.be.equals(dummy[val]);
}
});
it('SodiumPlus.crypto_aead_xchacha20poly1305_ietf_*', async() => {
if (!sodium) sodium = await SodiumPlus.auto();
let plaintext = Buffer.from(
'4c616469657320616e642047656e746c656d656e206f662074686520636c6173' +
'73206f66202739393a204966204920636f756c64206f6666657220796f75206f' +
'6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73' +
'637265656e20776f756c642062652069742e',
'hex'
);
let assocData = Buffer.from('50515253c0c1c2c3c4c5c6c7', 'hex');
let nonce = Buffer.from('404142434445464748494a4b4c4d4e4f5051525354555657', 'hex');
let key = CryptographyKey.from('808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f', 'hex');
let ciphertext = await sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, nonce, key, assocData);
let expected = 'bd6d179d3e83d43b9576579493c0e939572a1700252bfaccbed2902c21396cbb' +
'731c7f1b0b4aa6440bf3a82f4eda7e39ae64c6708c54c216cb96b72e1213b452' +
'2f8c9ba40db5d945b11b69b982c1bb9e3f3fac2bc369488f76b2383565d3fff9' +
'21f9664c97637da9768812f615c68b13b52e' +
'c0875924c1c7987947deafd8780acf49';
expect(ciphertext.toString('hex')).to.be.equals(expected);
let decrypted = await sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(ciphertext, nonce, key, assocData);
expect(decrypted.toString('hex')).to.be.equals(plaintext.toString('hex'));
let randomKey = await sodium.crypto_aead_xchacha20poly1305_ietf_keygen();
assert(randomKey instanceof CryptographyKey);
let ciphertext2 = await sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, nonce, randomKey);
decrypted = await sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(ciphertext2, nonce, randomKey);
expect(decrypted.toString('hex')).to.be.equals(plaintext.toString('hex'));
expect(ciphertext.toString('hex')).to.not.equals(ciphertext2.toString('hex'));
await expectError(
sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(plaintext, nonce.slice(1), randomKey),
'Argument 2 must be 24 bytes'
);
await expectError(
sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(plaintext, nonce, Buffer.alloc(32)),
'Argument 3 must be an instance of CryptographyKey'
);
await expectError(
sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, nonce.slice(1), randomKey),
'Argument 2 must be 24 bytes'
);
await expectError(
sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, nonce, Buffer.alloc(32)),
'Argument 3 must be an instance of CryptographyKey'
);
});
it('SodiumPlus.crypto_auth', async() => {
if (!sodium) sodium = await SodiumPlus.auto();
let key = await sodium.crypto_auth_keygen();
let message = 'Science, math, technology, engineering, and compassion for others.';
let mac = await sodium.crypto_auth(message, key);
assert(await sodium.crypto_auth_verify(message, key, mac) === true);
await expectError(
sodium.crypto_auth(message, Buffer.alloc(32)),
'Argument 2 must be an instance of CryptographyKey'
);
await expectError(
sodium.crypto_auth_verify(message, Buffer.alloc(32), mac),
'Argument 2 must be an instance of CryptographyKey'
);
});
it('SodiumPlus.crypto_box', async() => {
if (!sodium) sodium = await SodiumPlus.auto();
let plaintext = 'Science, math, technology, engineering, and compassion for others.';
let aliceKeypair = await sodium.crypto_box_keypair();
let aliceSecret = await sodium.crypto_box_secretkey(aliceKeypair);
let alicePublic = await sodium.crypto_box_publickey(aliceKeypair);
let bobKeypair = await sodium.crypto_box_keypair();
let bobSecret = await sodium.crypto_box_secretkey(bobKeypair);
let bobPublic = await sodium.crypto_box_publickey(bobKeypair);
let nonce = await sodium.randombytes_buf(24);
let ciphertext = await sodium.crypto_box(plaintext, nonce, aliceSecret, bobPublic);
let decrypted = await sodium.crypto_box_open(ciphertext, nonce, bobSecret, alicePublic);
expect(decrypted.toString('hex')).to.be.equals(Buffer.from(plaintext).toString('hex'));
let derived = await sodium.crypto_box_publickey_from_secretkey(aliceSecret);
expect(alicePublic.getBuffer().toString('hex'))
.to.be.equal(derived.getBuffer().toString('hex'));
/* Unhappy path: */
await expectError(
sodium.crypto_box(plaintext, nonce, alicePublic, bobPublic),
'Argument 3 must be an instance of X25519SecretKey'
);
await expectError(
sodium.crypto_box(plaintext, nonce, bobSecret, aliceSecret),
'Argument 4 must be an instance of X25519PublicKey'
);
await expectError(
sodium.crypto_box(plaintext, nonce.slice(1), bobSecret, alicePublic),
'Nonce must be a buffer of exactly 24 bytes'
);
await expectError(
sodium.crypto_box_open(ciphertext, nonce, alicePublic, bobPublic),
'Argument 3 must be an instance of X25519SecretKey'
);
await expectError(
sodium.crypto_box_open(ciphertext, nonce, bobSecret, aliceSecret),
'Argument 4 must be an instance of X25519PublicKey'
);
await expectError(
sodium.crypto_box_open(ciphertext.slice(0, 14), nonce, bobSecret, alicePublic),
'Ciphertext must be a buffer of at least 16 bytes'
);
await expectError(
sodium.crypto_box_open(ciphertext, nonce.slice(1), bobSecret, alicePublic),
'Nonce must be a buffer of exactly 24 bytes'
);
await expectError(
sodium.crypto_box_keypair_from_secretkey_and_publickey(alicePublic, alicePublic),
'Argument 1 must be an instance of X25519SecretKey'
);
await expectError(
sodium.crypto_box_keypair_from_secretkey_and_publickey(aliceSecret, aliceSecret),
'Argument 2 must be an instance of X25519PublicKey'
);
await expectError(
sodium.crypto_box_secretkey(derived),
'Keypair must be 64 bytes'
);
await expectError(
sodium.crypto_box_publickey(derived),
'Keypair must be 64 bytes'
);
await expectError(
sodium.crypto_box_publickey_from_secretkey(derived),
'Argument 1 must be an instance of X25519SecretKey'
);
});
it('SodiumPlus.crypto_box_seal', async() => {
if (!sodium) sodium = await SodiumPlus.auto();
let plaintext = 'Science, math, technology, engineering, and compassion for others.';
let aliceKeypair = await sodium.crypto_box_keypair();
let aliceSecret = await sodium.crypto_box_secretkey(aliceKeypair);
let alicePublic = await sodium.crypto_box_publickey(aliceKeypair);
assert(aliceSecret instanceof X25519SecretKey);
assert(alicePublic instanceof X25519PublicKey);
let ciphertext = await sodium.crypto_box_seal(plaintext, alicePublic);
let decrypted = await sodium.crypto_box_seal_open(ciphertext, alicePublic, aliceSecret);
expect(decrypted.toString('hex')).to.be.equals(Buffer.from(plaintext).toString('hex'));
await expectError(
sodium.crypto_box_seal(plaintext, aliceSecret),
'Argument 2 must be an instance of X25519PublicKey'
);
await expectError(
sodium.crypto_box_seal_open(plaintext, aliceSecret, aliceSecret),
'Argument 2 must be an instance of X25519PublicKey'
);
await expectError(
sodium.crypto_box_seal_open(plaintext, alicePublic, alicePublic),
'Argument 3 must be an instance of X25519SecretKey'
);
});
it('SodiumPlus.crypto_generichash', async() => {
let message = 'Science, math, technology, engineering, and compassion for others.';
let piece1 = message.slice(0, 16);
let piece2 = message.slice(16);
let hash1 = await sodium.crypto_generichash(message);
expect(hash1.toString('hex')).to.be.equals('47c1fdbde32b30b9c54dd47cf88ba92d2d05df1265e342c9563ed56aee84ab02');
let state = await sodium.crypto_generichash_init();
await sodium.crypto_generichash_update(state, piece1);
await sodium.crypto_generichash_update(state, piece2);
let hash2 = await sodium.crypto_generichash_final(state);
expect(hash1.toString('hex')).to.be.equals(hash2.toString('hex'));
let key = await sodium.crypto_generichash_keygen();
hash1 = await sodium.crypto_generichash(message, key);
state = await sodium.crypto_generichash_init(key);
await sodium.crypto_generichash_update(state, piece1);
await sodium.crypto_generichash_update(state, piece2);
hash2 = await sodium.crypto_generichash_final(state);
expect(hash1.toString('hex')).to.be.equals(hash2.toString('hex'));
});
it('SodiumPlus.crypto_kdf', async function() {
if (!sodium) sodium = await SodiumPlus.auto();
let subkey, expected;
let key = CryptographyKey.from('808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f', 'hex');
let context = 'NaClTest';
subkey = await sodium.crypto_kdf_derive_from_key(32, 1, context, key);
expected = 'bce6fcf118cac2691bb23975a63dfac02282c1cd5de6ab9febcbb0ec4348181b';
expect(subkey.toString('hex')).to.be.equals(expected);
subkey = await sodium.crypto_kdf_derive_from_key(32, 2, context, key);
expected = '877cf1c1a2da9b900c79464acebc3731ed4ebe326a7951911639821d09dc6dda';
expect(subkey.toString('hex')).to.be.equals(expected);
let key2 = await sodium.crypto_kdf_keygen();
let subkey2 = await sodium.crypto_kdf_derive_from_key(32, 1, context, key2);
expect(subkey2.toString('hex')).to.not.equals(key2.toString('hex'));
expect(subkey2.toString('hex')).to.not.equals(subkey.toString('hex'));
await expectError(
sodium.crypto_kdf_derive_from_key(-32, 1, context, key2),
'Length must be a positive integer.'
);
await expectError(
sodium.crypto_kdf_derive_from_key(32, -1, context, key2),
'Key ID must be an unsigned integer'
);
});
it('SodiumPlus.crypto_kx', async function() {
if (!sodium) sodium = await SodiumPlus.auto();
let clientKeys = await sodium.crypto_kx_keypair();
let clientSecret = await sodium.crypto_box_secretkey(clientKeys);
let clientPublic = await sodium.crypto_box_publickey(clientKeys);
let seed = 'Unit test static key seed goes here. Nothing too complicated. No randomness needed, really.';
let serverKeys = await sodium.crypto_kx_seed_keypair(seed);
let serverSecret = await sodium.crypto_box_secretkey(serverKeys);
let serverPublic = await sodium.crypto_box_publickey(serverKeys);
let clientRx, clientTx, serverRx, serverTx;
[clientRx, clientTx] = await sodium.crypto_kx_client_session_keys(clientPublic, clientSecret, serverPublic);
[serverRx, serverTx] = await sodium.crypto_kx_server_session_keys(serverPublic, serverSecret, clientPublic);
expect(clientRx.toString('hex')).to.be.equals(serverTx.toString('hex'));
expect(clientTx.toString('hex')).to.be.equals(serverRx.toString('hex'));
await expectError(
sodium.crypto_kx_client_session_keys(clientSecret, clientSecret, serverPublic),
'Argument 1 must be an instance of X25519PublicKey'
);
await expectError(
sodium.crypto_kx_client_session_keys(clientPublic, clientPublic, serverPublic),
'Argument 2 must be an instance of X25519SecretKey'
);
await expectError(
sodium.crypto_kx_client_session_keys(clientPublic, clientSecret, serverSecret),
'Argument 3 must be an instance of X25519PublicKey'
);
await expectError(
sodium.crypto_kx_server_session_keys(serverSecret, serverSecret, clientPublic),
'Argument 1 must be an instance of X25519PublicKey'
);
await expectError(
sodium.crypto_kx_server_session_keys(serverPublic, serverPublic, clientPublic),
'Argument 2 must be an instance of X25519SecretKey'
);
await expectError(
sodium.crypto_kx_server_session_keys(serverPublic, serverSecret, clientSecret),
'Argument 3 must be an instance of X25519PublicKey'
);
});
it('SodiumPlus.crypto_onetimeauth', async() => {
if (!sodium) sodium = await SodiumPlus.auto();
let key = await sodium.crypto_onetimeauth_keygen();
let plaintext = 'Science, math, technology, engineering, and compassion for others.';
let tag = await sodium.crypto_onetimeauth(plaintext, key);
assert(await sodium.crypto_onetimeauth_verify(plaintext, key, tag));
assert((await sodium.crypto_onetimeauth_verify(plaintext + ' extra', key, tag)) === false);
let msg = Buffer.alloc(32, 0);
key = CryptographyKey.from('746869732069732033322d62797465206b657920666f7220506f6c7931333035', 'hex');
tag = await sodium.crypto_onetimeauth(msg, key);
expect(tag.toString('hex')).to.be.equals('49ec78090e481ec6c26b33b91ccc0307');
assert(await sodium.crypto_onetimeauth_verify(msg, key, tag));
await expectError(
sodium.crypto_onetimeauth(msg, Buffer.alloc(32)),
'Argument 2 must be an instance of CryptographyKey'
);
await expectError(
sodium.crypto_onetimeauth_verify(msg, Buffer.alloc(32), tag),
'Argument 2 must be an instance of CryptographyKey'
);
});
it('SodiumPlus.crypto_scalarmult', async() => {
let aliceKeypair = await sodium.crypto_box_keypair();
let aliceSecret = await sodium.crypto_box_secretkey(aliceKeypair);
let alicePublic = await sodium.crypto_box_publickey(aliceKeypair);
assert(aliceSecret instanceof X25519SecretKey);
assert(alicePublic instanceof X25519PublicKey);
// crypto_scalarmult_base test:
let testPublic = await sodium.crypto_scalarmult_base(aliceSecret);
expect(testPublic.getBuffer().toString('hex')).to.be.equals(alicePublic.getBuffer().toString('hex'));
// crypto_scalarmult test:
let bobKeypair = await sodium.crypto_box_keypair();
let bobSecret = await sodium.crypto_box_secretkey(bobKeypair);
let bobPublic = await sodium.crypto_box_publickey(bobKeypair);
expect(alicePublic.getBuffer().toString('hex')).to.be.equals(alicePublic.getBuffer().toString('hex'));
let ab = await sodium.crypto_scalarmult(aliceSecret, bobPublic);
expect(ab.toString('hex')).to.not.equals('0000000000000000000000000000000000000000000000000000000000000000');
let ba = await sodium.crypto_scalarmult(bobSecret, alicePublic);
expect(ba.toString('hex')).to.not.equals('0000000000000000000000000000000000000000000000000000000000000000');
expect(ab.toString('hex')).to.be.equals(ba.toString('hex'));
await expectError(
sodium.crypto_scalarmult(alicePublic, bobPublic),
'Argument 1 must be an instance of X25519SecretKey'
);
await expectError(
sodium.crypto_scalarmult(aliceSecret, bobSecret),
'Argument 2 must be an instance of X25519PublicKey'
);
await expectError(
sodium.crypto_scalarmult_base(alicePublic),
'Argument 1 must be an instance of X25519SecretKey'
);
});
it('SodiumPlus.crypto_secretbox', async() => {
if (!sodium) sodium = await SodiumPlus.auto();
let plaintext = 'Science, math, technology, engineering, and compassion for others.';
let key = await sodium.crypto_secretbox_keygen();
let nonce = await sodium.randombytes_buf(24);
let ciphertext = await sodium.crypto_secretbox(plaintext, nonce, key);
let decrypted = await sodium.crypto_secretbox_open(ciphertext, nonce, key);
expect(decrypted.toString('hex')).to.be.equals(Buffer.from(plaintext).toString('hex'));
// Unhappy path:
let ed25519key = await sodium.crypto_sign_secretkey(await sodium.crypto_sign_keypair());
await expectError(
sodium.crypto_secretbox(ciphertext.slice(0, 14), nonce, ed25519key),
'Argument 3 must not be an asymmetric key'
);
await expectError(
sodium.crypto_secretbox(ciphertext, nonce.slice(1), key),
'Nonce must be a buffer of exactly 24 bytes'
);
await expectError(
sodium.crypto_secretbox_open(ciphertext.slice(0, 14), nonce, ed25519key),
'Argument 3 must not be an asymmetric key'
);
await expectError(
sodium.crypto_secretbox_open(ciphertext.slice(0, 14), nonce, key),
'Ciphertext must be a buffer of at least 16 bytes'
);
await expectError(
sodium.crypto_secretbox_open(ciphertext, nonce.slice(1), key),
'Nonce must be a buffer of exactly 24 bytes'
);
});
it('SodiumPlus.crypto_secretstream_xchacha20poly1305', async() => {
if (!sodium) sodium = await SodiumPlus.auto();
let key = await sodium.crypto_secretstream_xchacha20poly1305_keygen();
let encryptor, decryptor;
encryptor = await sodium.crypto_secretstream_xchacha20poly1305_init_push(key);
decryptor = await sodium.crypto_secretstream_xchacha20poly1305_init_pull(key, encryptor.header);
await expectError(
sodium.crypto_secretstream_xchacha20poly1305_init_push(Buffer.alloc(31)),
'Key must be an instance of CryptographyKey'
);
await expectError(
sodium.crypto_secretstream_xchacha20poly1305_init_pull(Buffer.alloc(31), encryptor.header),
'Key must be an instance of CryptographyKey'
);
let invalidKey = new CryptographyKey(Buffer.alloc(31));
await expectError(
sodium.crypto_secretstream_xchacha20poly1305_init_push(invalidKey),
'crypto_secretstream keys must be 32 bytes long'
);
await expectError(
sodium.crypto_secretstream_xchacha20poly1305_init_pull(invalidKey, encryptor.header),
'crypto_secretstream keys must be 32 bytes long'
);
await expectError(
sodium.crypto_secretstream_xchacha20poly1305_init_pull(key, encryptor.header.slice(1)),
'crypto_secretstream headers must be 24 bytes long'
);
// Get a test input from the text file.
let longText = await fsp.readFile(path.join(__dirname, 'longtext.md'));
let chunk, readUntil;
let ciphertext = Buffer.concat([encryptor.header]);
// How big are our chunks going to be?
let PUSH_CHUNK_SIZE = await sodium.randombytes_uniform(longText.length - 32) + 32;
let PULL_CHUNK_SIZE = PUSH_CHUNK_SIZE + sodium.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES;
// Encrypt
for (let i = 0; i < longText.length; i += PUSH_CHUNK_SIZE) {
readUntil = (i + PUSH_CHUNK_SIZE) > longText.length ? longText.length : i + PUSH_CHUNK_SIZE;
chunk = await encryptor.push(
longText.slice(i, readUntil)
);
ciphertext = Buffer.concat([ciphertext, chunk]);
}
expect(ciphertext.slice(0, 24).toString('hex')).to.be
.equals(encryptor.header.toString('hex'));
// Decrypt, starting at 24 (after the header, which we already have)
let decrypted = Buffer.alloc(0);
for (let i = 24; i < ciphertext.length; i += PULL_CHUNK_SIZE) {
readUntil = (i + PULL_CHUNK_SIZE) > ciphertext.length ? ciphertext.length : i + PULL_CHUNK_SIZE;
chunk = await decryptor.pull(
ciphertext.slice(i, readUntil)
);
decrypted = Buffer.concat([decrypted, chunk]);
}
expect(decrypted.toString('hex')).to.be.equals(longText.toString('hex'));
await encryptor.rekey();
});
it('SodiumPlus.crypto_shorthash', async() => {
if (!sodium) sodium = await SodiumPlus.auto();
let key = CryptographyKey.from('808182838485868788898a8b8c8d8e8f', 'hex');
let message;
let hash;
message = 'This is short input0';
hash = await sodium.crypto_shorthash(message, key);
expect(hash.toString('hex')).to.be.equals('ef589fb9ef4196b3');
message = 'This is short input1';
hash = await sodium.crypto_shorthash(message, key);
expect(hash.toString('hex')).to.be.equals('5e8f01039bc53eb7');
let random = await sodium.crypto_shorthash_keygen();
expect(sodium.CRYPTO_SHORTHASH_KEYBYTES).to.be.equal(random.getLength());
});
it('SodiumPlus.crypto_sign_seed_keypair', async() => {
if (!sodium) sodium = await SodiumPlus.auto();
let aliceKeypair = await sodium.crypto_sign_seed_keypair(
await sodium.crypto_generichash('sodium-plus')
);
let alicePublic = await sodium.crypto_sign_publickey(aliceKeypair);
expect(alicePublic.getBuffer().toString('hex')).to.be.equals(
'292288efba3a33275d216f2e4d9014d330f3b2852d6b767de15e43839096d6e8'
);
await expectError(
sodium.crypto_sign_seed_keypair(Buffer.alloc(31)),
'Seed must be 32 bytes long; got 31'
);
// Should not throw:
await sodium.crypto_sign_seed_keypair(
new CryptographyKey(await sodium.crypto_generichash('sodium-plus'))
);
});
it('SodiumPlus.crypto_sign_{secret,public}key', async() => {
await expectError(
sodium.crypto_sign_secretkey(new CryptographyKey(Buffer.alloc(16))),
'Keypair must be 96 bytes'
);
await expectError(
sodium.crypto_sign_publickey(new CryptographyKey(Buffer.alloc(16))),
'Keypair must be 96 bytes'
);
});
it('SodiumPlus.crypto_sign', async() => {
if (!sodium) sodium = await SodiumPlus.auto();
let aliceKeypair = await sodium.crypto_sign_keypair();
let aliceSecret = await sodium.crypto_sign_secretkey(aliceKeypair);
let alicePublic = await sodium.crypto_sign_publickey(aliceKeypair);
let plaintext = 'Science, math, technology, engineering, and compassion for others.';
let signed = await sodium.crypto_sign(plaintext, aliceSecret);
let opened = await sodium.crypto_sign_open(signed, alicePublic);
expect(signed.slice(64).toString('hex')).to.be.equals(opened.toString('hex'));
expect(opened.toString()).to.be.equals(plaintext);
let signature = await sodium.crypto_sign_detached(plaintext, aliceSecret);
let valid = await sodium.crypto_sign_verify_detached(plaintext, alicePublic, signature);
expect(valid).to.be.equals(true);
let invalid = await sodium.crypto_sign_verify_detached(plaintext + ' extra', alicePublic, signature);
expect(invalid).to.be.equals(false);
await expectError(
sodium.crypto_sign(plaintext, alicePublic),
'Argument 2 must be an instance of Ed25519SecretKey'
);
await expectError(
sodium.crypto_sign_open(signed, aliceSecret),
'Argument 2 must be an instance of Ed25519PublicKey'
);
await expectError(
sodium.crypto_sign_detached(plaintext, alicePublic),
'Argument 2 must be an instance of Ed25519SecretKey'
);
await expectError(
sodium.crypto_sign_verify_detached(plaintext, aliceSecret, signature),
'Argument 2 must be an instance of Ed25519PublicKey'
);
});
it('SodiumPlus.crypto_sign_ed25519_to_curve25519', async function () {
this.timeout(0);
if (!sodium) sodium = await SodiumPlus.auto();
let aliceKeypair = CryptographyKey.from(
'411a2c2227d2a799ebae0ed94417d8e8ed1ca9b0a9d5f4cd743cc52d961e94e2' +
'da49154c9e700b754199df7974e9fa4ee4b6ebbc71f89d8d8938335ea4a1409d' +
'da49154c9e700b754199df7974e9fa4ee4b6ebbc71f89d8d8938335ea4a1409d', 'hex');
let aliceSecret = await sodium.crypto_sign_secretkey(aliceKeypair);
let alicePublic = await sodium.crypto_sign_publickey(aliceKeypair);
let ecdhSecret = await sodium.crypto_sign_ed25519_sk_to_curve25519(aliceSecret);
expect(ecdhSecret.toString('hex')).to.be
.equals('60c783b8d1674b7081b72a105b55872502825d4ec638028152e085b54705ad7e');
let ecdhPublic = await sodium.crypto_sign_ed25519_pk_to_curve25519(alicePublic);
expect(ecdhPublic.toString('hex')).to.be
.equals('5a791d07cfb39060c8e9b641b6a915a3126cd14ddc243a9928c490c8e1f59e7c');
});
it('SodiumPlus.crypto_stream', async function () {
if (!sodium) sodium = await SodiumPlus.auto();
let key = CryptographyKey.from('8000000000000000000000000000000000000000000000000000000000000000', 'hex');
let iv = Buffer.alloc(24, 0);
let output = await sodium.crypto_stream(256, iv, key);
let testVector = '93D88C085B8433B1FBAD2221FAD718078D96119F727D27F0547F9F3D29DE1358' +
'F3FE3D9EEACF59E894FA76E6507F567B4A0796DD00D8BFC736344A9906CB1F5D';
expect(output.slice(0, 64).toString('hex').toUpperCase()).to.be.equals(testVector);
testVector = '17FD2BD86D095016D8367E0DD47D3E4A18DAE7BB24F8B5E3E9F52C4A493BE982' +
'ECA8E89A4DEC78467E31087A1ACDA83754BEFB273AB27EB396EB4957F7166C25';
expect(output.slice(192, 256).toString('hex').toUpperCase()).to.be.equals(testVector);
key = CryptographyKey.from('8080808080808080808080808080808080808080808080808080808080808080', 'hex');
output = await sodium.crypto_stream_xor('Test message', iv, key);
expect(output.toString('hex')).to.be.equals('1071d0355cb22c4c4e00303f');
key = await sodium.crypto_stream_keygen();
iv = await sodium.randombytes_buf(24);
let plaintext = 'This is a secret message';
let ciphertext = await sodium.crypto_stream_xor(plaintext, iv, key);
let decrypted = await sodium.crypto_stream_xor(ciphertext, iv, key);
expect(decrypted.toString()).to.be.equals(plaintext);
});
it('SodiumPlus.randombytes_buf', async() => {
if (!sodium) sodium = await SodiumPlus.auto();
let a, b;
for (let i = 0; i < 100; i++) {
a = await sodium.randombytes_buf(64);
b = await sodium.randombytes_buf(64);
expect(a.toString('hex')).to.not.equals(b.toString('hex'));
}
});
it('SodiumPlus.randombytes_uniform', async() => {
if (!sodium) sodium = await SodiumPlus.auto();
let a, b;
for (let i = 0; i < 100; i++) {
a = await sodium.randombytes_uniform(0x3fffffff);
b = await sodium.randombytes_uniform(0x3fffffff);
expect(a).to.not.equals(b);
}
});
it('SodiumPlus.sodium_bin2hex', async () => {
if (!sodium) sodium = await SodiumPlus.auto();
let buf = await sodium.randombytes_buf(32);
expect(await sodium.sodium_bin2hex(buf)).to.be.equals(buf.toString('hex'));
});
it('SodiumPlus.sodium_add', async () => {
if (!sodium) sodium = await SodiumPlus.auto();
let foo = Buffer.from('ed000000', 'hex');
let bar = Buffer.from('01000000', 'hex');
let baz = await sodium.sodium_add(foo, bar);
expect(baz.toString('hex')).to.be.equals('ee000000');
bar = Buffer.from('ff000000', 'hex');
baz = await sodium.sodium_add(baz, bar);
expect(baz.toString('hex')).to.be.equals('ed010000');
foo = Buffer.from('ffffffff', 'hex');
bar = Buffer.from('01000000', 'hex');
baz = await sodium.sodium_add(foo, bar);
expect(baz.toString('hex')).to.be.equals('00000000');
bar = Buffer.from('02000000', 'hex');
baz = await sodium.sodium_add(foo, bar);
expect(baz.toString('hex')).to.be.equals('01000000');
});
it('SodiumPlus.sodium_compare', async() => {
if (!sodium) sodium = await SodiumPlus.auto();
let a = Buffer.from('80808080', 'hex');
let b = Buffer.from('81808080', 'hex');
let c = Buffer.from('80808081', 'hex');
expect(await sodium.sodium_compare(a, a)).to.be.equals(0);
expect(await sodium.sodium_compare(b, b)).to.be.equals(0);
expect(await sodium.sodium_compare(c, c)).to.be.equals(0);
expect(await sodium.sodium_compare(a, b)).to.be.below(0);
expect(await sodium.sodium_compare(b, a)).to.be.above(0);
expect(await sodium.sodium_compare(a, c)).to.be.below(0);
expect(await sodium.sodium_compare(c, a)).to.be.above(0);
expect(await sodium.sodium_compare(b, c)).to.be.below(0);
expect(await sodium.sodium_compare(c, b)).to.be.above(0);
});
it('SodiumPlus.sodium_hex2bin', async () => {
if (!sodium) sodium = await SodiumPlus.auto();
let buf = await sodium.randombytes_buf(32);
let hex = buf.toString('hex');
let bin = await sodium.sodium_hex2bin(hex);
expect(Buffer.isBuffer(bin)).to.be.equals(true);
expect(bin.toString('base64')).to.be.equals(buf.toString('base64'));
});
it('SodiumPlus.sodium_increment', async() => {
if (!sodium) sodium = await SodiumPlus.auto();
let a = Buffer.from('80808080', 'hex');
let b = Buffer.from('81808080', 'hex');
await sodium.sodium_increment(a);
expect(await sodium.sodium_compare(b, a)).to.be.equals(0);
a = Buffer.from('ffffffff', 'hex');
b = Buffer.from('00000000', 'hex');
await sodium.sodium_increment(a);
expect(await sodium.sodium_compare(b, a)).to.be.equals(0);
});
it('SodiumPlus.sodium_is_zero', async() => {
if (!sodium) sodium = await SodiumPlus.auto();
let buf;
buf = Buffer.from('00', 'hex');
expect(await sodium.sodium_is_zero(buf, 1)).to.be.equals(true);
buf = Buffer.from('01', 'hex');
expect(await sodium.sodium_is_zero(buf, 1)).to.be.equals(false);
});
it('SodiumPlus.sodium_memcmp', async() => {
if (!sodium) sodium = await SodiumPlus.auto();
let a, b, c;
a = await sodium.randombytes_buf(32);
b = await sodium.randombytes_buf(32);
c = await Util.cloneBuffer(b);
expect(await sodium.sodium_memcmp(a, b)).to.be.equals(false);
expect(await sodium.sodium_memcmp(a, c)).to.be.equals(false);
expect(await sodium.sodium_memcmp(b, c)).to.be.equals(true);
expect(await sodium.sodium_memcmp(c, b)).to.be.equals(true);
});
it('SodiumPlus.sodium_memzero', async() => {
if (!sodium) sodium = await SodiumPlus.auto();
let buf = await sodium.randombytes_buf(16);
expect(buf.toString('hex')).to.not.equals('00000000000000000000000000000000');
await sodium.sodium_memzero(buf);
expect(buf.toString('hex')).to.be.equals('00000000000000000000000000000000');
});
it('SodiumPlus.sodium_pad', async() => {
if (!sodium) sodium = await SodiumPlus.auto();
let buf, size, padded, unpadded;
for (let i = 0; i < 100; i++) {
buf = await sodium.randombytes_buf(
await sodium.randombytes_uniform(96) + 16
);
size = await sodium.randombytes_uniform(96) + 5;
padded = await sodium.sodium_pad(buf, size);
unpadded = await sodium.sodium_unpad(padded, size);
expect(unpadded.toString('hex')).to.be.equals(buf.toString('hex'));
}
});
});

View file

@ -0,0 +1,30 @@
const { describe, it } = require('mocha');
const { expect } = require('chai');
const { SodiumPlus } = require('../index');
const Util = require('../lib/util');
const VERBOSE = false;
const expectError = require('./async-helper');
let sodium;
(async () => {
if (!sodium) sodium = await SodiumPlus.auto();
if (VERBOSE) {
console.log({
'libsodium-wrappers': sodium.isLibsodiumWrappers(),
'sodium-native': sodium.isSodiumNative()
});
}
})();
describe('Util', async () => {
it('toBuffer()', async () => {
if (!sodium) sodium = await SodiumPlus.auto();
expect(null).to.be.equal(await Util.toBuffer(null));
let promised = await Util.toBuffer(sodium.crypto_secretbox_keygen());
expect(32).to.be.equal(promised.getBuffer().length);
await expectError(Util.toBuffer(12), 'Invalid type; string or buffer expected');
});
});

View file

@ -17,8 +17,84 @@ function str_rot13 (str) {
// Arrays for pluggable encryptors/decryptors
var red_encryptors = new Array();
var red_decryptors = new Array();
let red_encryptors = new Array();
let red_decryptors = new Array();
async function sodium_encrypt(element) {
if (!window.sodium) {
window.sodium = await SodiumPlus.auto();
}
if (typeof tinyMCE !== typeof undefined) {
tinyMCE.triggerSave(false,true);
}
let message = $(element).val();
let password = prompt(aStr['passphrase']);
if (!password) {
return false;
}
let hint = bin2hex(prompt(aStr['passhint']));
let salt = await sodium.randombytes_buf(16);
let nonce = await sodium.randombytes_buf(24);
let key = await sodium.crypto_pwhash(
32,
password,
salt,
sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);
// Message can be a string, buffer, array, etc.
let ciphertext = await sodium.crypto_secretbox(message, nonce, key);
delete message, password, key;
let s = await sodium.sodium_bin2hex(salt);
let n = await sodium.sodium_bin2hex(nonce);
let c = await sodium.sodium_bin2hex(ciphertext);
let encrypted = window.btoa(s + '.' + n + '.' + c);
let val = "[crypt alg='XSalsa20' hint='" + hint + "']" + encrypted + '[/crypt]';
$(element).val(val);
}
async function sodium_decrypt(alg, hint, encrypted, element) {
if (alg !== 'XSalsa20') {
alert('Unsupported algorithm');
return false;
}
let arr = window.atob(encrypted).split('.');
let salt = await sodium.sodium_hex2bin(arr[0]);
let nonce = await sodium.sodium_hex2bin(arr[1]);
let ciphertext = await sodium.sodium_hex2bin(arr[2]);
let password = prompt((hint.length) ? hex2bin(hint) : aStr['passphrase']);
if (!password) {
return false;
}
let key = await sodium.crypto_pwhash(
32,
password,
salt,
sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);
let decrypted = await sodium.crypto_secretbox_open(ciphertext, nonce, key);
delete password, key;
if ($(element).css('display') === 'none' && typeof tinyMCE !== typeof undefined) {
tinyMCE.activeEditor.setContent(newdiv);
}
else {
$(element).html(decrypted.toString('utf-8'));
}
}
function hz_encrypt(alg, elem) {

View file

@ -24,6 +24,7 @@ Head::add_js('/library/jgrowl/jquery.jgrowl.min.js');
Head::add_js('/library/jquery-resizable/dist/jquery-resizable.min.js');
Head::add_js('/library/sjcl/sjcl.js');
Head::add_js('/library/sodium-plus/dist/sodium-plus.min.js');
Head::add_js('acl.js');
Head::add_js('webtoolkit.base64.js');

View file

@ -70,7 +70,7 @@
</div>
{{if $feature_encrypt}}
<div class="btn-group mr-2 d-none d-md-flex">
<button id="chat-encrypt-wrapper" class="btn btn-outline-secondary btn-sm" onclick="red_encrypt('{{$cipher}}', '#chatText', $('#chatText').val()); return false;">
<button id="chat-encrypt-wrapper" class="btn btn-outline-secondary btn-sm" onclick="sodium_encrypt('#chatText'); return false;">
<i id="chat-encrypt" class="fa fa-key jot-icons" title="{{$encrypt}}" ></i>
</button>
</div>
@ -83,7 +83,7 @@
<a class="dropdown-item" href="#" onclick="chatJotGetLink(); return false;" ><i class="fa fa-link"></i>&nbsp;{{$insert}}</a>
{{if $feature_encrypt}}
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" onclick="red_encrypt('{{$cipher}}', '#chatText' ,$('#chatText').val()); return false;"><i class="fa fa-key"></i>&nbsp;{{$encrypt}}</a>
<a class="dropdown-item" href="#" onclick="sodium_encrypt('#chatText'); return false;"><i class="fa fa-key"></i>&nbsp;{{$encrypt}}</a>
{{/if}}
</div>
</div>

View file

@ -58,7 +58,7 @@
</div>
{{if $feature_encrypt}}
<div class="btn-group mr-2">
<button class="btn btn-outline-secondary btn-sm" title="{{$encrypt}}" onclick="hz_encrypt('{{$cipher}}','#comment-edit-text-' + '{{$id}}'); return false;">
<button class="btn btn-outline-secondary btn-sm" title="{{$encrypt}}" onclick="sodium_encrypt('#comment-edit-text-' + '{{$id}}'); return false;">
<i class="fa fa-key comment-icon"></i>
</button>
</div>

View file

@ -195,7 +195,7 @@
</button>
{{/if}}
{{if $feature_encrypt}}
<button id="profile-encrypt-wrapper" class="btn btn-outline-secondary btn-sm" title="{{$encrypt}}" onclick="hz_encrypt('{{$cipher}}','#profile-jot-text');return false;">
<button id="profile-encrypt-wrapper" class="btn btn-outline-secondary btn-sm" title="{{$encrypt}}" onclick="sodium_encrypt('#profile-jot-text');return false;">
<i id="profile-encrypt" class="fa fa-key jot-icons"></i>
</button>
{{/if}}