├── .npmignore ├── lib ├── sodium-error.js ├── keytypes │ ├── ed25519pk.js │ ├── x25519pk.js │ ├── x25519sk.js │ └── ed25519sk.js ├── backend.js ├── cryptography-key.js ├── polyfill.js ├── util.js └── backend │ ├── libsodium-wrappers.js │ └── sodiumnative.js ├── .gitignore ├── test ├── async-helper.js ├── util-test.js ├── sodiumplus-pwhash-test.js ├── backend-test.js ├── sodiumplus-pwhashstr-test.js ├── cryptography-key-test.js ├── longtext.md └── sodiumplus-test.js ├── browser.js ├── LICENSE ├── docs ├── SodiumPlus │ ├── utilities.md │ ├── randomness.md │ ├── short-input-hashing.md │ ├── shared-key-authentication.md │ ├── password-based-key-derivation.md │ ├── one-time-authentication.md │ ├── key-derivation.md │ ├── sealed-boxes.md │ ├── shared-key-authenticated-encryption.md │ ├── scalar-multiplication.md │ ├── stream-ciphers.md │ ├── AEAD.md │ ├── password-hashing-and-storage.md │ ├── general-purpose-cryptographic-hash.md │ ├── authenticated-public-key-encryption.md │ ├── key-exchange.md │ ├── digital-signatures.md │ ├── encrypted-streams.md │ └── README.md ├── getting-started.md └── README.md ├── .github └── workflows │ └── main.yml ├── index.js ├── package.json ├── README.md └── index.d.ts /.npmignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /dist 3 | *-tmp.js 4 | -------------------------------------------------------------------------------- /lib/sodium-error.js: -------------------------------------------------------------------------------- 1 | module.exports = class SodiumError extends Error {}; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /node_modules 3 | /package-lock.json 4 | *-tmp.js 5 | /nbproject 6 | /.nyc_output 7 | -------------------------------------------------------------------------------- /test/async-helper.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | module.exports = async function expectError(promised, message) { 3 | let thrown = false; 4 | try { 5 | await promised; 6 | } catch (e) { 7 | thrown = true; 8 | expect(message).to.be.equal(e.message); 9 | } 10 | if (!thrown) { 11 | throw new Error('Function did not throw'); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /lib/keytypes/ed25519pk.js: -------------------------------------------------------------------------------- 1 | const CryptographyKey = require('../cryptography-key'); 2 | 3 | class Ed25519PublicKey extends CryptographyKey { 4 | constructor(buf) { 5 | if (buf.length !== 32) { 6 | throw new Error('Ed25519 public keys must be 32 bytes long'); 7 | } 8 | super(buf); 9 | this.keyType = 'ed25519'; 10 | this.publicKey = true; 11 | } 12 | /** 13 | * @return {Ed25519PublicKey} 14 | */ 15 | static from() { 16 | return new Ed25519PublicKey(Buffer.from(...arguments)); 17 | } 18 | 19 | isEd25519Key() { 20 | return true; 21 | } 22 | 23 | isPublicKey() { 24 | return true; 25 | } 26 | } 27 | 28 | module.exports = Ed25519PublicKey; 29 | -------------------------------------------------------------------------------- /lib/keytypes/x25519pk.js: -------------------------------------------------------------------------------- 1 | const CryptographyKey = require('../cryptography-key'); 2 | 3 | class X25519PublicKey extends CryptographyKey { 4 | constructor(buf) { 5 | if (buf.length !== 32) { 6 | throw new Error('X25519 public keys must be 32 bytes long'); 7 | } 8 | super(buf); 9 | this.keyType = 'x25519'; 10 | this.publicKey = true; 11 | } 12 | 13 | /** 14 | * @return {X25519PublicKey} 15 | */ 16 | static from() { 17 | return new X25519PublicKey(Buffer.from(...arguments)); 18 | } 19 | 20 | isX25519Key() { 21 | return true; 22 | } 23 | 24 | isPublicKey() { 25 | return true; 26 | } 27 | } 28 | 29 | module.exports = X25519PublicKey; 30 | -------------------------------------------------------------------------------- /lib/keytypes/x25519sk.js: -------------------------------------------------------------------------------- 1 | const CryptographyKey = require('../cryptography-key'); 2 | 3 | class X25519SecretKey extends CryptographyKey { 4 | constructor(buf) { 5 | if (buf.length !== 32) { 6 | throw new Error('X25519 secret keys must be 32 bytes long'); 7 | } 8 | super(buf); 9 | this.keyType = 'x25519'; 10 | this.publicKey = false; 11 | } 12 | 13 | /** 14 | * @return {X25519SecretKey} 15 | */ 16 | static from() { 17 | return new X25519SecretKey(Buffer.from(...arguments)); 18 | } 19 | 20 | isX25519Key() { 21 | return true; 22 | } 23 | 24 | isPublicKey() { 25 | return false; 26 | } 27 | } 28 | 29 | module.exports = X25519SecretKey; 30 | -------------------------------------------------------------------------------- /lib/keytypes/ed25519sk.js: -------------------------------------------------------------------------------- 1 | const CryptographyKey = require('../cryptography-key'); 2 | 3 | class Ed25519SecretKey extends CryptographyKey { 4 | constructor(buf) { 5 | if (buf.length !== 64) { 6 | throw new Error('Ed25519 secret keys must be 64 bytes long'); 7 | } 8 | super(buf); 9 | this.keyType = 'ed25519'; 10 | this.publicKey = false; 11 | } 12 | 13 | /** 14 | * @return {Ed25519SecretKey} 15 | */ 16 | static from() { 17 | return new Ed25519SecretKey(Buffer.from(...arguments)); 18 | } 19 | 20 | isEd25519Key() { 21 | return true; 22 | } 23 | 24 | isPublicKey() { 25 | return false; 26 | } 27 | } 28 | 29 | module.exports = Ed25519SecretKey; -------------------------------------------------------------------------------- /browser.js: -------------------------------------------------------------------------------- 1 | const { 2 | CryptographyKey, 3 | Ed25519PublicKey, 4 | Ed25519SecretKey, 5 | SodiumError, 6 | SodiumPlus, 7 | SodiumPolyfill, 8 | SodiumUtil, 9 | X25519PublicKey, 10 | X25519SecretKey 11 | } = require('./index'); 12 | 13 | // Load dependencies into window 14 | (async function(){ 15 | window.CryptographyKey = CryptographyKey; 16 | window.Ed25519PublicKey = Ed25519PublicKey; 17 | window.Ed25519SecretKey = Ed25519SecretKey; 18 | window.SodiumError = SodiumError; 19 | window.SodiumPlus = SodiumPlus; 20 | window.SodiumPolyfill = SodiumPolyfill; 21 | window.SodiumUtil = SodiumUtil; 22 | window.X25519PublicKey = X25519PublicKey; 23 | window.X25519SecretKey = X25519SecretKey; 24 | window.sodium = await SodiumPlus.auto(); 25 | })(); 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | * ISC License 3 | * 4 | * Copyright (c) 2019 5 | * Paragon Initiative Enterprises 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | */ -------------------------------------------------------------------------------- /test/util-test.js: -------------------------------------------------------------------------------- 1 | const { describe, it } = require('mocha'); 2 | const { expect } = require('chai'); 3 | const { SodiumPlus } = require('../index'); 4 | const Util = require('../lib/util'); 5 | const VERBOSE = false; 6 | const expectError = require('./async-helper'); 7 | 8 | let sodium; 9 | 10 | (async () => { 11 | if (!sodium) sodium = await SodiumPlus.auto(); 12 | if (VERBOSE) { 13 | console.log({ 14 | 'libsodium-wrappers': sodium.isLibsodiumWrappers(), 15 | 'sodium-native': sodium.isSodiumNative() 16 | }); 17 | } 18 | })(); 19 | 20 | describe('Util', async () => { 21 | it('toBuffer()', async () => { 22 | if (!sodium) sodium = await SodiumPlus.auto(); 23 | 24 | expect(null).to.be.equal(await Util.toBuffer(null)); 25 | 26 | let promised = await Util.toBuffer(sodium.crypto_secretbox_keygen()); 27 | expect(32).to.be.equal(promised.getBuffer().length); 28 | await expectError(Util.toBuffer(12), 'Invalid type; string or buffer expected'); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /lib/backend.js: -------------------------------------------------------------------------------- 1 | const CryptographyKey = require('./cryptography-key'); 2 | /* istanbul ignore if */ 3 | if (typeof (Buffer) === 'undefined') { 4 | let Buffer = require('buffer/').Buffer; 5 | } 6 | 7 | module.exports = class Backend { 8 | constructor() { 9 | // NOP 10 | this.backendName = 'UndefinedBackend'; 11 | } 12 | 13 | /** 14 | * @param {CryptographyKey} sKey 15 | * @param {CryptographyKey} pKey 16 | * @return {Promise} 17 | */ 18 | async crypto_box_keypair_from_secretkey_and_publickey(sKey, pKey) { 19 | /* istanbul ignore if */ 20 | if (sKey.getLength() !== 32) { 21 | throw new Error('Secret key must be 32 bytes'); 22 | } 23 | /* istanbul ignore if */ 24 | if (pKey.getLength() !== 32) { 25 | throw new Error('Public key must be 32 bytes'); 26 | } 27 | const keypair = Buffer.alloc(64); 28 | sKey.getBuffer().copy(keypair, 0, 0, 32); 29 | pKey.getBuffer().copy(keypair, 32, 0, 32); 30 | return new CryptographyKey(Buffer.from(keypair)); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /docs/SodiumPlus/utilities.md: -------------------------------------------------------------------------------- 1 | ## Utilities 2 | 3 | ### sodium_bin2hex 4 | 5 | Encode data into a hexadecimal string. 6 | 7 | **Parameters and their respective types**: 8 | 9 | 1. `{string|Buffer}` non-hex-encoded input 10 | 11 | Returns a `Promise` that resolves to a `string`. 12 | 13 | ```javascript 14 | const { SodiumPlus } = require('sodium-plus'); 15 | let sodium; 16 | 17 | (async function () { 18 | if (!sodium) sodium = await SodiumPlus.auto(); 19 | let buf = await sodium.randombytes_buf(32); 20 | 21 | console.log(await sodium.sodium_bin2hex(buf)); 22 | })(); 23 | ``` 24 | 25 | ### sodium_hex2bin 26 | 27 | Decode data from a hexadecimal string to a `Buffer`. 28 | 29 | **Parameters and their respective types**: 30 | 31 | 1. `{string|Buffer}` hex-encoded input 32 | 33 | Returns a `Promise` that resolves to a `Buffer`. 34 | 35 | ```javascript 36 | const { SodiumPlus } = require('sodium-plus'); 37 | let sodium; 38 | 39 | (async function () { 40 | if (!sodium) sodium = await SodiumPlus.auto(); 41 | let hex = '491d40c4924ba547d6f0bda9da77a539391decdc'; 42 | 43 | console.log(await sodium.sodium_hex2bin(hex)); 44 | })(); 45 | ``` 46 | -------------------------------------------------------------------------------- /test/sodiumplus-pwhash-test.js: -------------------------------------------------------------------------------- 1 | const { describe, it } = require('mocha'); 2 | const { expect } = require('chai'); 3 | const { SodiumPlus } = require('../index'); 4 | const VERBOSE = false; 5 | 6 | let sodium; 7 | 8 | (async () => { 9 | if (!sodium) sodium = await SodiumPlus.auto(); 10 | if (VERBOSE) { 11 | console.log({ 12 | 'libsodium-wrappers': sodium.isLibsodiumWrappers(), 13 | 'sodium-native': sodium.isSodiumNative() 14 | }); 15 | } 16 | })(); 17 | 18 | describe('SodiumPlus', () => { 19 | it('crypto_pwhash', async function() { 20 | this.timeout(0); 21 | if (!sodium) sodium = await SodiumPlus.auto(); 22 | let password = 'correct horse battery staple'; 23 | let salt = Buffer.from('808182838485868788898a8b8c8d8e8f', 'hex'); 24 | let hashed = await sodium.crypto_pwhash( 25 | 16, 26 | password, 27 | salt, 28 | sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, 29 | sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE 30 | ); 31 | expect(hashed.toString('hex')).to.be.equals('720f95400220748a811bca9b8cff5d6e'); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /docs/SodiumPlus/randomness.md: -------------------------------------------------------------------------------- 1 | 2 | ## Randomness 3 | 4 | > **See also:** [Libsodium's documentation on its random data features](https://download.libsodium.org/doc/generating_random_data). 5 | 6 | ### randombytes_buf 7 | 8 | Obtain a buffer filled with random bytes. 9 | 10 | **Parameters and their respective types**: 11 | 12 | 1. `{number}` Size of buffer to return 13 | 14 | Returns a `Promise` that resolves to a `Buffer` 15 | 16 | ### randombytes_uniform 17 | 18 | Generate an integer between 0 and upperBound (non-inclusive). 19 | 20 | For example, randombytes_uniform(10) returns an integer between 0 and 9. 21 | 22 | **Parameters and their respective types**: 23 | 24 | 1. `{number}` Upper bound 25 | 26 | Returns a `Promise` that resolves to a `number`. 27 | 28 | ### Example for randombytes 29 | 30 | ```javascript 31 | const { SodiumPlus } = require('sodium-plus'); 32 | let sodium; 33 | 34 | (async function () { 35 | if (!sodium) sodium = await SodiumPlus.auto(); 36 | 37 | let someBuf = await sodium.randombytes_buf(32); 38 | console.log(someBuf.toString('hex')); 39 | 40 | let someInt = await sodium.randombytes_uniform(65536); 41 | console.log(someInt); 42 | })(); 43 | ``` 44 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | matrix: 9 | platform: [ubuntu-latest] 10 | node: [ '12', '10' ] 11 | name: Node ${{ matrix.node }} (${{ matrix.platform }}) 12 | runs-on: ${{ matrix.platform }} 13 | steps: 14 | - uses: actions/checkout@v1 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: ${{ matrix.node }} 18 | - name: install dependencies 19 | run: npm install 20 | - name: test without sodium-native 21 | run: npm test 22 | - name: install sodium-native 23 | run: npm install --save sodium-native 24 | - name: test with sodium-native 25 | run: npm test 26 | 27 | build_latest: 28 | name: Node latest 29 | runs-on: ubuntu-latest 30 | container: node:latest 31 | steps: 32 | - uses: actions/checkout@v1 33 | - name: install dependencies 34 | run: npm install 35 | - name: test without sodium-native 36 | run: npm test 37 | - name: install sodium-native 38 | run: npm install --save sodium-native 39 | - name: test with sodium-native 40 | run: npm test 41 | -------------------------------------------------------------------------------- /docs/SodiumPlus/short-input-hashing.md: -------------------------------------------------------------------------------- 1 | ## Short-input hashing 2 | 3 | > **See also**: [Libsodium's documentation on its short-input hashing features](https://download.libsodium.org/doc/hashing/short-input_hashing). 4 | 5 | ### crypto_shorthash 6 | 7 | Calculate a fast hash for short inputs. 8 | 9 | **Parameters and their respective types**: 10 | 11 | 1. `{string|Buffer}` input 12 | 3. `{CryptographyKey}` key 13 | 14 | Returns a `Promise` that resolves to a `Buffer`. 15 | 16 | ### crypto_shorthash_keygen 17 | 18 | Returns a `CryptographyKey` object containing a key appropriate 19 | for the `crypto_shorthash` API. 20 | 21 | ### Example for crypto_shorthash 22 | 23 | > **Warning:** You probably want [`crypto_generichash()`](general-purpose-cryptographic-hash.md) 24 | > for most use-cases. `crypto_shorthash()` does not offer collision resistance. 25 | 26 | ```javascript 27 | const { SodiumPlus } = require('sodium-plus'); 28 | let sodium; 29 | 30 | (async function () { 31 | if (!sodium) sodium = await SodiumPlus.auto(); 32 | let key = await sodium.crypto_shorthash_keygen(); 33 | let mapped = {}; 34 | mapped['foo'] = (await sodium.crypto_shorthash('foo', key)).toString('hex'); 35 | mapped['bar'] = (await sodium.crypto_shorthash('bar', key)).toString('hex'); 36 | mapped['baz'] = (await sodium.crypto_shorthash('baz', key)).toString('hex'); 37 | console.log(mapped); 38 | })(); 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/SodiumPlus/shared-key-authentication.md: -------------------------------------------------------------------------------- 1 | ## Shared-key authentication 2 | 3 | > **See also**: [Libsodium's documentation on its shared-key authentication features](https://download.libsodium.org/doc/secret-key_cryptography/secret-key_authentication). 4 | 5 | ### crypto_auth 6 | 7 | Get an authenticator for a message for a given key. 8 | 9 | **Parameters and their respective types**: 10 | 11 | 1. `{string|Buffer}` message 12 | 2. `{CryptographyKey}` key 13 | 14 | Return a `Promise` that resolves to a `Buffer`. 15 | 16 | ### crypto_auth_verify 17 | 18 | Verify an authenticator for a message for a given key. 19 | 20 | **Parameters and their respective types**: 21 | 22 | 1. `{string|Buffer}` message 23 | 2. `{CryptographyKey}` key 24 | 2. `{Buffer}` mac 25 | 26 | Return a `Promise` that resolves to a `boolean`. 27 | 28 | ### crypto_auth_keygen 29 | 30 | Returns a `CryptographyKey` object containing a key appropriate 31 | for the `crypto_auth` API. 32 | 33 | ### Example for crypto_auth 34 | 35 | ```javascript 36 | const { SodiumPlus } = require('sodium-plus'); 37 | let sodium; 38 | 39 | (async function () { 40 | if (!sodium) sodium = await SodiumPlus.auto(); 41 | let plaintext = 'Your message goes here'; 42 | let key = await sodium.crypto_auth_keygen(); 43 | let mac = await sodium.crypto_auth(plaintext, key); 44 | console.log(await sodium.crypto_auth_verify(plaintext, key, mac)); 45 | })(); 46 | ``` 47 | -------------------------------------------------------------------------------- /test/backend-test.js: -------------------------------------------------------------------------------- 1 | const { describe, it } = require('mocha'); 2 | const { expect } = require('chai'); 3 | const { SodiumPlus, X25519SecretKey, X25519PublicKey } = require('../index'); 4 | 5 | let sodium; 6 | describe('Backend', () => { 7 | it('crypto_box_keypair_from_secretkey_and_publickey', async function () { 8 | if (!sodium) sodium = await SodiumPlus.auto(); 9 | let a = Buffer.alloc(32); 10 | let b = Buffer.alloc(32); 11 | let c = Buffer.alloc(31); 12 | 13 | let d = await sodium.crypto_box_keypair_from_secretkey_and_publickey( 14 | new X25519SecretKey(a), 15 | new X25519PublicKey(b) 16 | ); 17 | expect(64).to.be.equal(d.buffer.length); 18 | 19 | expect(() => { 20 | sodium.crypto_box_keypair_from_secretkey_and_publickey( 21 | new X25519SecretKey(c), 22 | new X25519PublicKey(b) 23 | ) 24 | .then(() => {}) 25 | .catch((e) => { throw e }); 26 | }).to.throw('X25519 secret keys must be 32 bytes long'); 27 | 28 | expect(() => { 29 | sodium.crypto_box_keypair_from_secretkey_and_publickey( 30 | new X25519SecretKey(a), 31 | new X25519PublicKey(c) 32 | ) 33 | .then(() => {}) 34 | .catch((e) => { throw e }); 35 | }).to.throw('X25519 public keys must be 32 bytes long'); 36 | }); 37 | }); -------------------------------------------------------------------------------- /docs/SodiumPlus/password-based-key-derivation.md: -------------------------------------------------------------------------------- 1 | ## Password-based key derivation 2 | 3 | > **See also**: [Libsodium's documentation on Argon2](https://download.libsodium.org/doc/password_hashing/the_argon2i_function). 4 | 5 | ### crypto_pwhash 6 | 7 | Derive a cryptography key from a password and salt. 8 | 9 | **Parameters and their respective types**: 10 | 11 | 1. `{number}` output length 12 | 2. `{string|Buffer}` password 13 | 3. `{Buffer}` salt (16 bytes) 14 | 4. `{number}` opslimit (recommeded minimum: `2`) 15 | 5. `{number}` memlimit (recommended mimimum: `67108864` a.k.a. 64MiB) 16 | 6. `{number|null}` algorithm (recommended: `this.CRYPTO_PWHASH_ALG_DEFAULT`) 17 | 18 | Returns a `Promise` that resolves to a `CryptographyKey`. 19 | 20 | ### Example for crypto_pwhash 21 | 22 | This example is for key derivation. Look [below](#example-for-crypto_pwhash_str) 23 | for information about password storage/verification. 24 | 25 | ```javascript 26 | 27 | const { SodiumPlus } = require('sodium-plus'); 28 | let sodium; 29 | 30 | (async function () { 31 | if (!sodium) sodium = await SodiumPlus.auto(); 32 | 33 | let password = 'correct horse battery staple'; 34 | let salt = await sodium.randombytes_buf(16); 35 | 36 | let key = await sodium.crypto_pwhash( 37 | 32, 38 | password, 39 | salt, 40 | sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, 41 | sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE 42 | ); 43 | console.log(key.getBuffer().toString('hex')); 44 | })(); 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/SodiumPlus/one-time-authentication.md: -------------------------------------------------------------------------------- 1 | ## One-time authentication 2 | 3 | > **See also**: [Libsodium's documentation on its one-time authentication features](https://download.libsodium.org/doc/advanced/poly1305). 4 | 5 | ### crypto_onetimeauth 6 | 7 | Get an authenticator for a message for a given key. 8 | 9 | **Important:** In order to be secure, keys must be: 10 | 11 | 1. Secret. 12 | 2. Unpredictable. 13 | 3. Unique. 14 | 15 | **Parameters and their respective types**: 16 | 17 | 1. `{string|Buffer}` message 18 | 2. `{CryptographyKey}` key 19 | 20 | Return a `Promise` that resolves to a `Buffer`. 21 | 22 | ### crypto_onetimeauth_verify 23 | 24 | Verify an authenticator for a message for a given key. 25 | 26 | **Parameters and their respective types**: 27 | 28 | 1. `{string|Buffer}` message 29 | 2. `{CryptographyKey}` key 30 | 2. `{Buffer}` tag 31 | 32 | Return a `Promise` that resolves to a `boolean`. 33 | 34 | ### crypto_onetimeauth_keygen 35 | 36 | Returns a `CryptographyKey` object containing a key appropriate 37 | for the `crypto_auth` API. 38 | 39 | ### Example for crypto_onetimeauth 40 | 41 | ```javascript 42 | const { SodiumPlus } = require('sodium-plus'); 43 | let sodium; 44 | 45 | (async function () { 46 | if (!sodium) sodium = await SodiumPlus.auto(); 47 | let plaintext = 'Your message goes here'; 48 | let key = await sodium.crypto_onetimeauth_keygen(); 49 | let tag = await sodium.crypto_onetimeauth(plaintext, key); 50 | console.log(await sodium.crypto_onetimeauth_verify(plaintext, key, tag)); 51 | })(); 52 | ``` 53 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /** 3 | * This is only meant to be used for advanced users. 4 | * 5 | * A backend object can be passed to the SodiumPlus constructor. 6 | * 7 | * @param {string} type 8 | * @return {Backend} 9 | * @throws {SodiumError} 10 | * @throws {Error} 11 | */ 12 | getBackendObject: (type = '') => { 13 | if (type === 'SodiumNative') { 14 | // This one may error out. You should catch it in your code. 15 | // We won't here. Use the `await SodiumPlus.auto()` API instead! 16 | return require('./lib/backend/sodiumnative'); 17 | } else if (type === 'LibsodiumWrappers') { 18 | return require('./lib/backend/libsodium-wrappers'); 19 | } else if (type.length === 0) { 20 | return require('./lib/backend'); 21 | } 22 | 23 | // Default: Throw a SodiumError 24 | let SodiumError = require('./lib/sodium-error'); 25 | throw new SodiumError(`Unrecognized backend type: ${type}`); 26 | }, 27 | CryptographyKey: require('./lib/cryptography-key'), 28 | Ed25519PublicKey: require('./lib/keytypes/ed25519pk'), 29 | Ed25519SecretKey: require('./lib/keytypes/ed25519sk'), 30 | SodiumError: require('./lib/sodium-error'), 31 | SodiumPlus: require('./lib/sodiumplus'), 32 | SodiumPolyfill: require('./lib/polyfill'), 33 | SodiumUtil: require('./lib/util'), 34 | X25519PublicKey: require('./lib/keytypes/x25519pk'), 35 | X25519SecretKey: require('./lib/keytypes/x25519sk') 36 | }; 37 | -------------------------------------------------------------------------------- /docs/SodiumPlus/key-derivation.md: -------------------------------------------------------------------------------- 1 | ## Key derivation 2 | 3 | > **See also**: [Libsodium's documentation on its key derivation features](https://download.libsodium.org/doc/key_derivation). 4 | 5 | ### crypto_kdf_derive_from_key 6 | 7 | Derive a subkey from a master key. 8 | 9 | **Parameters and their respective types**: 10 | 11 | 1. `{number}` output length (typically you want `32`) 12 | 2. `{number}` subkey ID 13 | 3. `{string|Buffer}` context (must be a string/buffer of length 8) 14 | 4. `{CryptographyKey}` master key 15 | 16 | Returns a `Promise` that resolves to a `CryptographyKey`. 17 | 18 | ### crypto_kdf_keygen 19 | 20 | Returns a `CryptographyKey` object containing a key appropriate 21 | for the `crypto_kdf` API. 22 | 23 | ### Example for crypto_kdf 24 | 25 | ```javascript 26 | const { SodiumPlus } = require('sodium-plus'); 27 | let sodium; 28 | 29 | (async function () { 30 | if (!sodium) sodium = await SodiumPlus.auto(); 31 | let masterKey = await sodium.crypto_kdf_keygen(); 32 | let context = 'Sodium++'; 33 | 34 | let subkey1 = await sodium.crypto_kdf_derive_from_key(32, 1, context, masterKey); 35 | let subkey2 = await sodium.crypto_kdf_derive_from_key(32, 2, context, masterKey); 36 | let subkey3 = await sodium.crypto_kdf_derive_from_key(32, 3, context, masterKey); 37 | 38 | console.log({ 39 | 'master-key': masterKey.getBuffer().toString('hex'), 40 | 'subkey1': subkey1.getBuffer().toString('hex'), 41 | 'subkey2': subkey2.getBuffer().toString('hex'), 42 | 'subkey3': subkey3.getBuffer().toString('hex') 43 | }); 44 | })(); 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/SodiumPlus/sealed-boxes.md: -------------------------------------------------------------------------------- 1 | ## Sealed boxes 2 | 3 | > **See also**: [Libsodium's documentation on its sealed boxes features](https://download.libsodium.org/doc/public-key_cryptography/sealed_boxes). 4 | 5 | ### crypto_box_seal 6 | 7 | Anonymous public-key encryption. (Message integrity is still assured.) 8 | 9 | **Parameters and their respective types**: 10 | 11 | 1. `{string|Buffer}` plaintext 12 | 2. `{X25519PublicKey}` public key 13 | 14 | Returns a `Promise` that resolves to a `Buffer`. 15 | 16 | ### crypto_box_seal_open 17 | 18 | Anonymous public-key decryption. (Message integrity is still assured.) 19 | 20 | **Parameters and their respective types**: 21 | 22 | 1. `{Buffer}` ciphertext 23 | 2. `{X25519PublicKey}` public key 24 | 3. `{X25519SecretKey}` secret key 25 | 26 | Returns a `Promise` that resolves to a `Buffer`. 27 | 28 | ### Example for crypto_box_seal 29 | 30 | ```javascript 31 | const { SodiumPlus } = require('sodium-plus'); 32 | let sodium; 33 | 34 | (async function () { 35 | if (!sodium) sodium = await SodiumPlus.auto(); 36 | let aliceKeypair = await sodium.crypto_box_keypair(); 37 | let aliceSecret = await sodium.crypto_box_secretkey(aliceKeypair); 38 | let alicePublic = await sodium.crypto_box_publickey(aliceKeypair); 39 | 40 | let plaintext = 'Your message goes here'; 41 | 42 | let ciphertext = await sodium.crypto_box_seal(plaintext, alicePublic); 43 | console.log(ciphertext); 44 | 45 | let decrypted = await sodium.crypto_box_seal_open(ciphertext, alicePublic, aliceSecret); 46 | console.log(decrypted.toString()); 47 | })(); 48 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sodium-plus", 3 | "version": "0.9.0", 4 | "description": "The Sodium Cryptography Library", 5 | "author": "Paragon Initiative Enterprises, LLC", 6 | "license": "ISC", 7 | "keywords": [ 8 | "easy-to-use", 9 | "libsodium", 10 | "sodium", 11 | "cryptography", 12 | "cryptography library", 13 | "elliptic curve cryptography", 14 | "ecdh", 15 | "eddsa", 16 | "diffie-hellman", 17 | "authentication", 18 | "encryption", 19 | "digital signatures", 20 | "signatures", 21 | "curve25519", 22 | "ed25519", 23 | "x25519", 24 | "xsalsa20poly1305", 25 | "xchacha20", 26 | "poly1305" 27 | ], 28 | "peerDependencies": { 29 | "sodium-native": "^3.2.0" 30 | }, 31 | "dependencies": { 32 | "buffer": "^5.6.0", 33 | "libsodium-wrappers": "^0.7.6", 34 | "poly1305-js": "^0.4.2", 35 | "typedarray-to-buffer": "^3.1.5", 36 | "xsalsa20": "^1.1.0" 37 | }, 38 | "repository": "https://github.com/paragonie/sodium-plus", 39 | "main": "index.js", 40 | "types": "index.d.ts", 41 | "files": [ 42 | "build", 43 | "build.sh", 44 | "dist/sodium-plus.js", 45 | "dist/sodium-plus.min.js", 46 | "index.d.ts", 47 | "index.js", 48 | "lib/" 49 | ], 50 | "scripts": { 51 | "test": "mocha", 52 | "parallel-test": "mocha-parallel-tests" 53 | }, 54 | "devDependencies": { 55 | "@types/node": "^14.0.24", 56 | "assert": "^2.0.0", 57 | "browserify": "^16.5.1", 58 | "chai": "^4.2.0", 59 | "mkdirp": "^1.0.4", 60 | "mocha-parallel-tests": "^2.3.0", 61 | "mocha": "^8.0.1", 62 | "tinyify": "^2.5.2" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /docs/SodiumPlus/shared-key-authenticated-encryption.md: -------------------------------------------------------------------------------- 1 | ## Shared-key authenticated encryption 2 | 3 | > **See also**: [Libsodium's documentation on its shared-key authenticated encryption features](https://download.libsodium.org/doc/secret-key_cryptography/secretbox). 4 | 5 | ### crypto_secretbox 6 | 7 | Shared-key authenticated encryption. 8 | 9 | **Parameters and their respective types**: 10 | 11 | 1. `{string|Buffer}` Plaintext 12 | 2. `{string|Buffer}` nonce (must be 24 bytes) 13 | 3. `{CryptographyKey}` key 14 | 15 | Returns a `Promise` that resolves to a `Buffer`. 16 | 17 | ### crypto_secretbox_open 18 | 19 | Shared-key authenticated decryption. 20 | 21 | **Parameters and their respective types**: 22 | 23 | 1. `{string|Buffer}` Ciphertext 24 | 2. `{string|Buffer}` nonce (must be 24 bytes) 25 | 3. `{CryptographyKey}` key 26 | 27 | Returns a `Promise` that resolves to a `Buffer`. 28 | Throws a `SodiumError` on decryption failure. 29 | 30 | ### crypto_secretbox_keygen 31 | 32 | Returns a `CryptographyKey` object containing a key appropriate 33 | for the `crypto_secretbox` API. 34 | 35 | ### Example for crypto_secretbox 36 | 37 | ```javascript 38 | const { SodiumPlus } = require('sodium-plus'); 39 | let sodium; 40 | 41 | (async function () { 42 | if (!sodium) sodium = await SodiumPlus.auto(); 43 | let plaintext = 'Your message goes here'; 44 | let key = await sodium.crypto_secretbox_keygen(); 45 | let nonce = await sodium.randombytes_buf(24); 46 | let ciphertext = await sodium.crypto_secretbox( 47 | plaintext, 48 | nonce, 49 | key 50 | ); 51 | 52 | console.log(ciphertext.toString('hex')); 53 | 54 | let decrypted = await sodium.crypto_secretbox_open( 55 | ciphertext, 56 | nonce, 57 | key 58 | ); 59 | 60 | console.log(decrypted.toString()); 61 | })(); 62 | ``` 63 | -------------------------------------------------------------------------------- /docs/SodiumPlus/scalar-multiplication.md: -------------------------------------------------------------------------------- 1 | ## Scalar multiplication over Curve25519 2 | 3 | > **See also**: [Libsodium's documentation on its scalar multiplication features](https://download.libsodium.org/doc/advanced/scalar_multiplication). 4 | 5 | ### crypto_scalarmult 6 | 7 | Elliptic Curve Diffie-Hellman key exchange over Curve25519. 8 | You probably don't want to ever use this directly. 9 | 10 | **Parameters and their respective types**: 11 | 12 | 1. `{X25519SecretKey}` your secret key 13 | 2. `{X25519PublicKey}` their public key 14 | 15 | Returns a `Promise` that resolves to a `CryptographyKey`. 16 | 17 | ### crypto_scalarmult_base 18 | 19 | Generate an X25519PublicKey from an X25519SecretKey. 20 | 21 | **Parameters and their respective types**: 22 | 23 | 1. `{X25519SecretKey}` your secret key 24 | 25 | Returns a `Promise` that resolves to an `X25519PublicKey`. 26 | 27 | ### Example for crypto_scalarmult 28 | 29 | ```javascript 30 | const { SodiumPlus } = require('sodium-plus'); 31 | let sodium; 32 | 33 | (async function () { 34 | if (!sodium) sodium = await SodiumPlus.auto(); 35 | let aliceKeypair = await sodium.crypto_box_keypair(); 36 | let aliceSecret = await sodium.crypto_box_secretkey(aliceKeypair); 37 | let alicePublic = await sodium.crypto_box_publickey(aliceKeypair); 38 | let bobKeypair = await sodium.crypto_box_keypair(); 39 | let bobSecret = await sodium.crypto_box_secretkey(bobKeypair); 40 | let bobPublic = await sodium.crypto_scalarmult_base(bobSecret); 41 | 42 | let aliceToBob = await sodium.crypto_scalarmult(aliceSecret, bobPublic); 43 | let bobToAlice = await sodium.crypto_scalarmult(bobSecret, alicePublic); 44 | console.log({ 45 | 'alice-to-bob': aliceToBob.getBuffer().toString('hex'), 46 | 'bob-to-alice': bobToAlice.getBuffer().toString('hex') 47 | }); 48 | })(); 49 | ``` 50 | -------------------------------------------------------------------------------- /lib/cryptography-key.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* istanbul ignore if */ 4 | if (typeof (Buffer) === 'undefined') { 5 | let Buffer = require('buffer/').Buffer; 6 | } 7 | module.exports = class CryptographyKey { 8 | /** 9 | * Note: We use Object.defineProperty() to hide the buffer inside of the 10 | * CryptographyKey object to prevent accidental leaks. 11 | * 12 | * @param {Buffer} buf 13 | */ 14 | constructor(buf) { 15 | if (!Buffer.isBuffer(buf)) { 16 | throw new TypeError('Argument 1 must be an instance of Buffer.'); 17 | } 18 | Object.defineProperty(this, 'buffer', { 19 | enumerable: false, 20 | value: buf.slice() 21 | }); 22 | } 23 | 24 | /** 25 | * @return {CryptographyKey} 26 | */ 27 | static from() { 28 | return new CryptographyKey(Buffer.from(...arguments)); 29 | } 30 | 31 | /** 32 | * @return {boolean} 33 | */ 34 | isEd25519Key() { 35 | return false; 36 | } 37 | 38 | /** 39 | * @return {boolean} 40 | */ 41 | isX25519Key() { 42 | return false; 43 | } 44 | 45 | /** 46 | * @return {boolean} 47 | */ 48 | isPublicKey() { 49 | return false; 50 | } 51 | 52 | /** 53 | * @return {Number} 54 | */ 55 | getLength() { 56 | return this.buffer.length; 57 | } 58 | 59 | /** 60 | * @return {Buffer} 61 | */ 62 | getBuffer() { 63 | return this.buffer; 64 | } 65 | 66 | /** 67 | * @param {string} encoding 68 | */ 69 | toString(encoding = 'utf-8') { 70 | /* istanbul ignore if */ 71 | return this.getBuffer().toString(encoding); 72 | } 73 | 74 | /** 75 | * @return {Buffer} 76 | */ 77 | slice() { 78 | return this.buffer.slice(...arguments); 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /docs/SodiumPlus/stream-ciphers.md: -------------------------------------------------------------------------------- 1 | ## Stream Ciphers 2 | 3 | > **See also**: [Libsodium's documentation on its stream cipher features](https://download.libsodium.org/doc/advanced/stream_ciphers/xsalsa20). 4 | 5 | ### crypto_stream 6 | 7 | Obtain an arbitrary-length stream of pseudorandom bytes from a given 8 | nonce and key. 9 | 10 | **Parameters and their respective types**: 11 | 12 | 1. `{number}` length 13 | 2. `{Buffer}` nonce (must be 24 bytes) 14 | 3. `{CryptographyKey}` key 15 | 16 | Returns a `Promise` that resolves to a `Buffer` containing 17 | the pseudorandom bytes. 18 | 19 | ### crypto_stream_xor 20 | 21 | Encrypt a message with a given nonce and key. 22 | 23 | > [**Danger: Unauthenticated encryption!**](https://tonyarcieri.com/all-the-crypto-code-youve-ever-written-is-probably-broken) 24 | > Without a subsequent message authentication strategy, this is vulnerable to 25 | > chosen-ciphertext attacks. Proceed with caution! 26 | 27 | **Parameters and their respective types**: 28 | 29 | 1. `{string|Buffer}` plaintext 30 | 2. `{Buffer}` nonce (must be 24 bytes) 31 | 3. `{CryptographyKey}` key 32 | 33 | Returns a `Promise` that resolves to a `Buffer` containing 34 | the encrypted bytes. 35 | 36 | ### Example for crypto_stream 37 | 38 | ```javascript 39 | const { SodiumPlus } = require('sodium-plus'); 40 | 41 | let sodium; 42 | (async function () { 43 | if (!sodium) sodium = await SodiumPlus.auto(); 44 | let key = await sodium.crypto_stream_keygen(); 45 | let iv = await sodium.randombytes_buf(24); 46 | let output = await sodium.crypto_stream(64, iv, key); 47 | console.log(output); 48 | 49 | iv = await sodium.randombytes_buf(24); 50 | let plaintext = 'This is a secret message'; 51 | let ciphertext = await sodium.crypto_stream_xor(plaintext, iv, key); 52 | let decrypted = await sodium.crypto_stream_xor(ciphertext, iv, key); 53 | console.log(decrypted.toString()); 54 | })(); 55 | ``` 56 | -------------------------------------------------------------------------------- /test/sodiumplus-pwhashstr-test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const { describe, it } = require('mocha'); 3 | const { expect } = require('chai'); 4 | const { SodiumPlus } = require('../index'); 5 | const VERBOSE = false; 6 | 7 | let sodium; 8 | 9 | (async () => { 10 | if (!sodium) sodium = await SodiumPlus.auto(); 11 | if (VERBOSE) { 12 | console.log({ 13 | 'libsodium-wrappers': sodium.isLibsodiumWrappers(), 14 | 'sodium-native': sodium.isSodiumNative() 15 | }); 16 | } 17 | })(); 18 | 19 | describe('SodiumPlus', () => { 20 | it('SodiumPlus.crypto_pwhash_str', async function () { 21 | this.timeout(0); 22 | if (!sodium) sodium = await SodiumPlus.auto(); 23 | let password = 'correct horse battery staple'; 24 | let hashed = await sodium.crypto_pwhash_str( 25 | password, 26 | sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, 27 | sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE 28 | ); 29 | assert(hashed); 30 | assert(await sodium.crypto_pwhash_str_verify(password, hashed)); 31 | assert(await sodium.crypto_pwhash_str_verify('incorrect password', hashed) === false); 32 | 33 | let needs; 34 | needs = await sodium.crypto_pwhash_str_needs_rehash( 35 | hashed, 36 | sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, 37 | sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE 38 | ); 39 | expect(needs).to.be.equals(false); 40 | needs = await sodium.crypto_pwhash_str_needs_rehash( 41 | hashed, 42 | sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE + 1, 43 | sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE 44 | ); 45 | expect(needs).to.be.equals(true); 46 | needs = await sodium.crypto_pwhash_str_needs_rehash( 47 | hashed, 48 | sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, 49 | sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE << 1 50 | ); 51 | expect(needs).to.be.equals(true); 52 | }); 53 | }); -------------------------------------------------------------------------------- /docs/SodiumPlus/AEAD.md: -------------------------------------------------------------------------------- 1 | ## AEAD 2 | 3 | > **See also:** [Libsodium's documentation on its AEAD features](https://download.libsodium.org/doc/secret-key_cryptography/aead/chacha20-poly1305/xchacha20-poly1305_construction). 4 | 5 | ### crypto_aead_xchacha20poly1305_ietf_decrypt 6 | 7 | Decrypt a message (and optional associated data) with XChaCha20-Poly1305. 8 | 9 | **Parameters and their respective types**: 10 | 11 | 1. `{string|Buffer}` Ciphertext 12 | 2. `{string|Buffer}` nonce (must be 24 bytes) 13 | 3. `{CryptographyKey}` key 14 | 4. `{string|Buffer}` assocData 15 | 16 | Returns a `Promise` that resolves to a `Buffer`. 17 | Throws a `SodiumError` on decryption failure. 18 | 19 | ### crypto_aead_xchacha20poly1305_ietf_encrypt 20 | 21 | Encrypt a message (and optional associated data) with XChaCha20-Poly1305. 22 | 23 | **Parameters and their respective types**: 24 | 25 | 1. `{string|Buffer}` Plaintext 26 | 2. `{string|Buffer}` nonce (must be 24 bytes) 27 | 3. `{CryptographyKey}` key 28 | 4. `{string|Buffer}` assocData 29 | 30 | Returns a `Promise` that resolves to a `Buffer`. 31 | 32 | ### crypto_aead_xchacha20poly1305_ietf_keygen 33 | 34 | Returns a `CryptographyKey` object containing a key appropriate 35 | for the `crypto_aead_xchacha20poly1305_ietf_` API. 36 | 37 | ### Example for crypto_aead_xchacha20poly1305_ietf_* 38 | 39 | ```javascript 40 | const { SodiumPlus } = require('sodium-plus'); 41 | let sodium; 42 | 43 | (async function () { 44 | if (!sodium) sodium = await SodiumPlus.auto(); 45 | let plaintext = 'Your message goes here'; 46 | let key = await sodium.crypto_aead_xchacha20poly1305_ietf_keygen(); 47 | let nonce = await sodium.randombytes_buf(24); 48 | let ciphertext = await sodium.crypto_aead_xchacha20poly1305_ietf_encrypt( 49 | plaintext, 50 | nonce, 51 | key 52 | ); 53 | 54 | console.log(ciphertext.toString('hex')); 55 | 56 | let decrypted = await sodium.crypto_aead_xchacha20poly1305_ietf_decrypt( 57 | ciphertext, 58 | nonce, 59 | key 60 | ); 61 | 62 | console.log(decrypted.toString()); 63 | })(); 64 | ``` -------------------------------------------------------------------------------- /docs/SodiumPlus/password-hashing-and-storage.md: -------------------------------------------------------------------------------- 1 | ## Password hashing and storage 2 | 3 | > **See also**: [Libsodium's documentation on its password hashing features](https://download.libsodium.org/doc/password_hashing). 4 | 5 | ### crypto_pwhash_str 6 | 7 | Get a password hash (in a safe-for-storage format). 8 | 9 | **Parameters and their respective types**: 10 | 11 | 1. `{string|Buffer}` password 12 | 2. `{number}` opslimit (recommeded minimum: `2`) 13 | 3. `{number}` memlimit (recommended mimimum: `67108864` a.k.a. 64MiB) 14 | 15 | Returns a `Promise` that resolves to a `string`. 16 | 17 | ### crypto_pwhash_str_needs_rehash 18 | 19 | Does this password need to be rehashed? (i.e. have the algorithm parameters 20 | we want changed since the hash was generated?) 21 | 22 | **Parameters and their respective types**: 23 | 24 | 1. `{string}` password hash 25 | 2. `{number}` opslimit (recommeded minimum: `2`) 26 | 3. `{number}` memlimit (recommended mimimum: `67108864` a.k.a. 64MiB) 27 | 28 | Returns a `Promise` that resolves to a `boolean`. 29 | 30 | ### crypto_pwhash_str_verify 31 | 32 | Verify a password against a known password hash. 33 | 34 | 1. `{string|Buffer}` password 35 | 2. `{string}` password hash 36 | 37 | Returns a `Promise` that resolves to a `boolean`. 38 | 39 | ### Example for crypto_pwhash_str 40 | 41 | ```javascript 42 | const { SodiumPlus } = require('sodium-plus'); 43 | let sodium; 44 | 45 | (async function () { 46 | if (!sodium) sodium = await SodiumPlus.auto(); 47 | let password = 'correct horse battery staple'; 48 | 49 | // Generating a password hash 50 | let pwhash = await sodium.crypto_pwhash_str( 51 | password, 52 | sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, 53 | sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE 54 | ); 55 | console.log(pwhash); 56 | 57 | // Check that we don't need to rotate hashes 58 | let stale = await sodium.crypto_pwhash_str_needs_rehash( 59 | pwhash, 60 | sodium.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, 61 | sodium.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE 62 | ); 63 | if (stale) { 64 | console.warn('Password needs to be rehashed'); 65 | } 66 | 67 | // Password validation 68 | if (await sodium.crypto_pwhash_str_verify(password, pwhash)) { 69 | console.log("Password valid"); 70 | } else { 71 | console.error("Incorrect password"); 72 | } 73 | })(); 74 | ``` 75 | -------------------------------------------------------------------------------- /lib/polyfill.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const crypto = require('crypto'); 3 | const Poly1305 = require('poly1305-js'); 4 | const Util = require('./util'); 5 | const XSalsa20 = require('xsalsa20'); 6 | 7 | /* istanbul ignore if */ 8 | if (typeof (Buffer) === 'undefined') { 9 | let Buffer = require('buffer/').Buffer; 10 | } 11 | 12 | module.exports = class SodiumPolyfill { 13 | 14 | /** 15 | * @param {string|Buffer} message 16 | * @param {CryptographyKey} key 17 | * @return {Promise} 18 | */ 19 | static async crypto_onetimeauth(message, key) { 20 | return Poly1305.onetimeauth( 21 | await Util.toBuffer(message), 22 | key.getBuffer() 23 | ); 24 | } 25 | 26 | /** 27 | * @param {string|Buffer} message 28 | * @param {CryptographyKey} key 29 | * @param {Buffer} tag 30 | * @return {Promise} 31 | */ 32 | static async crypto_onetimeauth_verify(message, key, tag) { 33 | return Poly1305.onetimeauth_verify( 34 | await Util.toBuffer(message), 35 | key.getBuffer(), 36 | await Util.toBuffer(tag) 37 | ); 38 | } 39 | 40 | /** 41 | * @param {string|Buffer} plaintext 42 | * @param {Buffer} nonce 43 | * @param {CryptographyKey} key 44 | * @return {Promise} 45 | */ 46 | static async crypto_stream_xor(plaintext, nonce, key) { 47 | const stream = XSalsa20(nonce, key.getBuffer()); 48 | const output = stream.update(plaintext); 49 | stream.finalize(); 50 | return Util.toBuffer(output); 51 | } 52 | 53 | /** 54 | * Polyfill crypto_pwhash_str_needs_rehash() for bindings that don't 55 | * include this (somewhat new) helper function. 56 | * 57 | * @param {string|Buffer} hash 58 | * @param {number} opslimit 59 | * @param {number} memlimit 60 | * @return {Promise} 61 | */ 62 | static async crypto_pwhash_str_needs_rehash(hash, opslimit, memlimit) { 63 | const pwhash = (await Util.toBuffer(hash)).toString('utf-8'); 64 | const pieces = pwhash.split('$'); 65 | const expect = 'm=' + (memlimit >> 10) + ',t=' + opslimit + ',p=1'; 66 | if (expect.length !== pieces[3].length) { 67 | return true; 68 | } 69 | return !crypto.timingSafeEqual( 70 | await Util.toBuffer(expect), 71 | await Util.toBuffer(pieces[3]) 72 | ); 73 | } 74 | }; 75 | -------------------------------------------------------------------------------- /docs/SodiumPlus/general-purpose-cryptographic-hash.md: -------------------------------------------------------------------------------- 1 | ## General-purpose cryptographic hash 2 | 3 | > **See also**: [Libsodium's documentation on its generic hashing features](https://download.libsodium.org/doc/hashing/generic_hashing). 4 | 5 | ### crypto_generichash 6 | 7 | General-purpose cryptographic hash (powered by BLAKE2). 8 | 9 | **Parameters and their respective types**: 10 | 11 | 1. `{Buffer}` message 12 | 2. `{CryptographyKey|null}` key (optional) 13 | 3. `{number}` output length (optional, defaults to 32) 14 | 15 | Returns a `Promise` that resolves to a `Buffer`. 16 | 17 | ### crypto_generichash_keygen 18 | 19 | Returns a `CryptographyKey` object containing a key appropriate 20 | for the `crypto_generichash` API. 21 | 22 | ### crypto_generichash_init 23 | 24 | Initialize a BLAKE2 hash context for stream hashing. 25 | 26 | **Parameters and their respective types**: 27 | 28 | 1. `{CryptographyKey|null}` key (optional) 29 | 2. `{number}` output length (optional, defaults to 32) 30 | 31 | Returns a `Promise` that resolves to... well, that depends on your backend. 32 | 33 | * sodium-native returns a `CryptoGenericHashWrap` object. 34 | * libsodium-wrappers returns a number (a buffer's memory address) 35 | 36 | ### crypto_generichash_update 37 | 38 | Update the BLAKE2 hash state with a block of data. 39 | 40 | **Parameters and their respective types**: 41 | 42 | 1. `{*}` hash state (see [crypto_generichash_init()](#crypto_generichash_init)) 43 | 2. `{string|Buffer}` message chunk 44 | 45 | Returns a `Promise` that resolves to `void`. Instead, `state` is updated in-place. 46 | 47 | ### crypto_generichash_final 48 | 49 | Obtain the final BLAKE2 hash output. 50 | 51 | **Parameters and their respective types**: 52 | 53 | 1. `{*}` hash state (see [crypto_generichash_init()](#crypto_generichash_init)) 54 | 2. `{number}` output length (optional, defaults to 32) 55 | 56 | Returns a `Promise` that resolves to a `Buffer`. 57 | 58 | ### Example for crypto_generichash 59 | 60 | ```javascript 61 | const { SodiumPlus } = require('sodium-plus'); 62 | let sodium; 63 | 64 | (async function () { 65 | if (!sodium) sodium = await SodiumPlus.auto(); 66 | let message = 'Any message can go here'; 67 | let hashed = await sodium.crypto_generichash(message); 68 | console.log(hashed.toString('hex')); 69 | 70 | let key = await sodium.crypto_generichash_keygen(); 71 | let hash2 = await sodium.crypto_generichash(message, key, 64); 72 | let state = await sodium.crypto_generichash_init(key, 64); 73 | 74 | await sodium.crypto_generichash_update(state, 'Any message '); 75 | await sodium.crypto_generichash_update(state, 'can go here'); 76 | let hash3 = await sodium.crypto_generichash_final(state, 64); 77 | if (!await sodium.sodium_memcmp(hash2, hash3)) { 78 | throw new Error('Implementation is broken. You should never see this.'); 79 | } 80 | console.log(hash2.toString('hex')); 81 | })(); 82 | ``` 83 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | You always want to use `SodiumPlus` from within an asynchronous function. 4 | 5 | ```javascript 6 | const { SodiumPlus } = require('sodium-plus'); 7 | let sodium; 8 | async function myFunction() { 9 | if (!sodium) sodium = await SodiumPlus.auto(); 10 | 11 | // Now you can use sodium.FUNCTION_NAME_HERE() 12 | } 13 | ``` 14 | 15 | When you use `await SodiumPlus.auto()`, this will automatically load in the best 16 | backend available for your platform. This is the recommended way to use SodiumPlus. 17 | 18 | If you'd rather use a specific backend, you can do the following: 19 | 20 | ```javascript 21 | const { SodiumPlus, SodiumUtil, getBackendObject } = require('sodium-plus'); 22 | let sodium; 23 | 24 | async function myFunction() { 25 | if (!sodium) { 26 | let backend = getBackendObject('LibsodiumWrappers'); 27 | SodiumUtil.populateConstants(backend); 28 | sodium = new SodiumPlus(backend); 29 | } 30 | 31 | // Now you can use sodium.FUNCTION_NAME_HERE() 32 | } 33 | ``` 34 | 35 | To discover what backend you're using at runtime, invoke the `getBackendName()` 36 | method on the `SodiumPlus` object, like so: 37 | 38 | 39 | ```javascript 40 | const { SodiumPlus } = require('sodium-plus'); 41 | let sodium; 42 | async function whichBackend() { 43 | if (!sodium) sodium = await SodiumPlus.auto(); 44 | 45 | console.log(sodium.getBackendName()); 46 | } 47 | ``` 48 | 49 | ## CryptographyKey 50 | 51 | All cryptographic secrets are contained within a `CryptographyKey` object 52 | (or one of its derived classes). You can create and access them like so: 53 | 54 | ```javascript 55 | const { CryptographyKey } = require('sodium-plus'); 56 | let buf = Buffer.alloc(32); 57 | let key = new CryptographyKey(buf); 58 | 59 | // If you do this, the internal buffer will not be visible! 60 | console.log(key); 61 | // CryptographyKey {} 62 | 63 | // You'll need to do this instead: 64 | console.log(key.getBuffer()); 65 | // 66 | ``` 67 | 68 | The following classes inherit from `CryptographyKey`: 69 | 70 | * `Ed25519PublicKey` -- Ed25519 public key 71 | * `Ed25519SecretKey` -- Ed25519 secret key 72 | * `X25519PublicKey` -- X25519 public key 73 | * `X25519SecretKey` -- X25519 secret key 74 | 75 | ## Sodium-Plus in the Browser 76 | 77 | First, download [sodium-plus.min.js](../dist) (or find it on a CDN you trust). 78 | 79 | Next, include the following script tags in your web page: 80 | 81 | ```html5 82 | 83 | 98 | ``` 99 | 100 | You can then use the `sodium` API as documented. 101 | -------------------------------------------------------------------------------- /docs/SodiumPlus/authenticated-public-key-encryption.md: -------------------------------------------------------------------------------- 1 | ## Authenticated public-key encryption 2 | 3 | > **See also**: [Libsodium's documentation on its public-key authenticated encryption features](https://download.libsodium.org/doc/public-key_cryptography/authenticated_encryption). 4 | 5 | ### crypto_box 6 | 7 | Public-key authenticated encryption. 8 | 9 | **Parameters and their respective types**: 10 | 11 | 1. `{string|Buffer}` plaintext 12 | 2. `{Buffer}` nonce (must be 24 bytes) 13 | 3. `{X25519SecretKey}` secret key 14 | 4. `{X25519PublicKey}` public key 15 | 16 | Returns a `Promise` that resolves to a `Buffer`. 17 | 18 | ### crypto_box_open 19 | 20 | Public-key authenticated encryption. 21 | 22 | **Parameters and their respective types**: 23 | 24 | 1. `{Buffer}` ciphertext 25 | 2. `{Buffer}` nonce (must be 24 bytes) 26 | 3. `{X25519SecretKey}` secret key 27 | 4. `{X25519PublicKey}` public key 28 | 29 | Returns a `Promise` that resolves to a `Buffer`. 30 | Throws a `SodiumError` on decryption failure. 31 | 32 | ### crypto_box_keypair 33 | 34 | Returns a `Promise` that resolves to a `CryptographyKey` containing a 64-byte 35 | `Buffer`. The first 32 bytes are your X25519 secret key, the latter 32 are your 36 | X25519 public key. 37 | 38 | ### crypto_box_keypair_from_secretkey_and_publickey 39 | 40 | Combine two X25519 keys (secret, public) into a keypair object. 41 | 42 | **Parameters and their respective types**: 43 | 44 | 1. `{X25519SecretKey}` secret key 45 | 2. `{X25519PublicKey}` public key 46 | 47 | Returns a `Promise` that resolves to a `CryptographyKey`. 48 | 49 | ### crypto_box_publickey 50 | 51 | **Parameters and their respective types**: 52 | 53 | 1. `{CryptographyKey}` (buffer must be 64 bytes long) 54 | 55 | Returns a `Promise` that resolves to a `X25519PublicKey`. 56 | 57 | ### crypto_box_secretkey 58 | 59 | **Parameters and their respective types**: 60 | 61 | 1. `{CryptographyKey}` (buffer must be 64 bytes long) 62 | 63 | Returns a `Promise` that resolves to a `X25519SecretKey`. 64 | 65 | ### crypto_box_publickey_from_secretkey 66 | 67 | Derive the public key from a given X25519 secret key. 68 | 69 | **Parameters and their respective types**: 70 | 71 | 1. `{X25519SecretKey}` 72 | 73 | Returns a `Promise` that resolves to a `X25519PublicKey`. 74 | 75 | ### Example for crypto_box 76 | 77 | ```javascript 78 | const { SodiumPlus } = require('sodium-plus'); 79 | let sodium; 80 | 81 | (async function () { 82 | if (!sodium) sodium = await SodiumPlus.auto(); 83 | let aliceKeypair = await sodium.crypto_box_keypair(); 84 | let aliceSecret = await sodium.crypto_box_secretkey(aliceKeypair); 85 | let alicePublic = await sodium.crypto_box_publickey(aliceKeypair); 86 | let bobKeypair = await sodium.crypto_box_keypair(); 87 | let bobSecret = await sodium.crypto_box_secretkey(bobKeypair); 88 | let bobPublic = await sodium.crypto_box_publickey(bobKeypair); 89 | 90 | let plaintext = 'Your message goes here'; 91 | let nonce = await sodium.randombytes_buf(24); 92 | 93 | let ciphertext = await sodium.crypto_box(plaintext, nonce, aliceSecret, bobPublic); 94 | console.log(ciphertext); 95 | 96 | let decrypted = await sodium.crypto_box_open(ciphertext, nonce, bobSecret, alicePublic); 97 | console.log(decrypted.toString()); 98 | })(); 99 | ``` 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sodium-Plus (Na+) 2 | 3 | [![Build Status](https://github.com/paragonie/sodium-plus/workflows/CI/badge.svg)](https://github.com/paragonie/sodium-plus/actions?workflow=CI) 4 | [![npm version](https://img.shields.io/npm/v/sodium-plus.svg)](https://npm.im/sodium-plus) 5 | 6 | Sodium-Plus delivers a positive cryptography experience for JavaScript developers. 7 | 8 | Sodium-Plus brings you all the benefits of using libsodium in your application 9 | without any of the headaches introduced by the incumbent APIs. 10 | 11 | Sodium-Plus is permissively licensed (ISC) and free to use. 12 | 13 | ## Features 14 | 15 | * **Cross-platform.** 16 | * Yes, this includes [in the browser](docs/getting-started.md#sodium-plus-in-the-browser). 17 | * Pluggable backend with an [auto-loader](docs/getting-started.md): 18 | * If [sodium-native](https://github.com/sodium-friends/sodium-native) 19 | is installed, it will be preferred. 20 | * Otherwise, the default is [libsodium-wrappers](https://github.com/jedisct1/libsodium.js). 21 | * Fully `async`/`await` ready (aside from object constructors). 22 | * Type-safe API: 23 | * Instead of just passing around `Buffer` objects and hoping you got your 24 | argument order correct, `sodium-plus` will throw an Error if you provide 25 | the wrong key type. This prevents you from accidentally introducing a severe 26 | security risk into your application. 27 | 28 | ## Installing 29 | 30 | ### Installing as a Node.js Module 31 | 32 | With NPM: 33 | 34 | ```terminal 35 | npm install sodium-plus 36 | ``` 37 | 38 | You can optionally install `sodium-native` alongside `sodium-plus` if you 39 | want better performance. 40 | 41 | The default configuration is a bit slower, but has a wider reach 42 | (e.g. web browsers). 43 | 44 | ### Installing in a Web Page 45 | 46 | See [this section of the documentation](docs/getting-started.md#sodium-plus-in-the-browser) 47 | for getting started with Sodium-Plus in a web browser. 48 | 49 | ## Using Sodium-Plus in Your Projects 50 | 51 | SodiumPlus is meant to be used asynchronously, like so: 52 | 53 | ```javascript 54 | const { SodiumPlus } = require('sodium-plus'); 55 | 56 | (async function() { 57 | // Select a backend automatically 58 | let sodium = await SodiumPlus.auto(); 59 | 60 | let key = await sodium.crypto_secretbox_keygen(); 61 | let nonce = await sodium.randombytes_buf(24); 62 | let message = 'This is just a test message'; 63 | // Message can be a string, buffer, array, etc. 64 | 65 | let ciphertext = await sodium.crypto_secretbox(message, nonce, key); 66 | console.log(ciphertext); 67 | let decrypted = await sodium.crypto_secretbox_open(ciphertext, nonce, key); 68 | console.log(decrypted.toString('utf-8')); 69 | })(); 70 | ``` 71 | 72 | This should produce output similar to below (but with different random-looking bytes): 73 | 74 | ``` 75 | 76 | This is just a test message 77 | ``` 78 | 79 | ## Documentation 80 | 81 | The documentation is [available online on Github](https://github.com/paragonie/sodium-plus/tree/master/docs)! 82 | 83 | ## Support Contracts 84 | 85 | If your company uses this library in their products or services, you may be 86 | interested in [purchasing a support contract from Paragon Initiative Enterprises](https://paragonie.com/enterprise). 87 | -------------------------------------------------------------------------------- /docs/SodiumPlus/key-exchange.md: -------------------------------------------------------------------------------- 1 | ## Key exchange 2 | 3 | > **See also**: [Libsodium's documentation on its key exchange features](https://download.libsodium.org/doc/key_exchange). 4 | 5 | ### crypto_kx_keypair 6 | 7 | This is functionally identical to [`crypto_box_keypair()`](#crypto_box_keypair). 8 | 9 | Returns a `Promise` that resolves to a `CryptographyKey` with 64 bytes. 10 | 11 | ### crypto_kx_seed_keypair 12 | 13 | Generate an X25519 keypair from a seed. Unlike `crypto_kx_seedpair()`, this is 14 | deterministic from your seed. 15 | 16 | **Parameters and their respective types**: 17 | 18 | 1. `{string|Buffer}` seed 19 | 20 | Returns a `Promise` that resolves to a `CryptographyKey` with 64 bytes. 21 | 22 | ### crypto_kx_client_session_keys 23 | 24 | Perform a key exchange from the client's perspective. 25 | 26 | Returns an array of two CryptographyKey objects: 27 | 28 | * The first is meant for data sent from the server to the client (incoming decryption). 29 | * The second is meant for data sent from the client to the server (outgoing encryption). 30 | 31 | **Parameters and their respective types**: 32 | 33 | 1. `{X25519PublicKey}` client public key (yours) 34 | 2. `{X25519SecretKey}` client secret key (yours) 35 | 1. `{X25519PublicKey}` server public key (theirs) 36 | 37 | Returns a `Promise` that resolves to an array of two `CryptographyKey` objects. 38 | 39 | ### crypto_kx_server_session_keys 40 | 41 | Perform a key exchange from the server's perspective. 42 | 43 | Returns an array of two CryptographyKey objects: 44 | 45 | * The first is meant for data sent from the client to the server (incoming decryption). 46 | * The second is meant for data sent from the server to the client (outgoing encryption). 47 | 48 | **Parameters and their respective types**: 49 | 50 | 1. `{X25519PublicKey}` server public key (yours) 51 | 2. `{X25519SecretKey}` server secret key (yours) 52 | 1. `{X25519PublicKey}` client public key (theirs) 53 | 54 | Returns a `Promise` that resolves to an array of two `CryptographyKey` objects. 55 | 56 | ### Example for crypto_kx 57 | 58 | ```javascript 59 | const { SodiumPlus } = require('sodium-plus'); 60 | let sodium; 61 | 62 | (async function () { 63 | if (!sodium) sodium = await SodiumPlus.auto(); 64 | let clientKeypair = await sodium.crypto_box_keypair(); 65 | let clientSecret = await sodium.crypto_box_secretkey(clientKeypair); 66 | let clientPublic = await sodium.crypto_box_publickey(clientKeypair); 67 | let serverKeypair = await sodium.crypto_kx_seed_keypair('Your static input goes here'); 68 | let serverSecret = await sodium.crypto_box_secretkey(serverKeypair); 69 | let serverPublic = await sodium.crypto_box_publickey(serverKeypair); 70 | let clientIKey, clientOKey, serverIKey, serverOKey; 71 | 72 | [clientIKey, clientOKey] = await sodium.crypto_kx_client_session_keys( 73 | clientPublic, 74 | clientSecret, 75 | serverPublic 76 | ); 77 | [serverIKey, serverOKey] = await sodium.crypto_kx_server_session_keys( 78 | serverPublic, 79 | serverSecret, 80 | clientPublic 81 | ); 82 | 83 | console.log({ 84 | 'client-sees': { 85 | 'incoming': clientIKey.getBuffer().toString('hex'), 86 | 'outgoing': clientOKey.getBuffer().toString('hex') 87 | }, 88 | 'server-sees': { 89 | 'incoming': serverIKey.getBuffer().toString('hex'), 90 | 'outgoing': serverOKey.getBuffer().toString('hex') 91 | } 92 | }); 93 | })(); 94 | ``` 95 | -------------------------------------------------------------------------------- /test/cryptography-key-test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const crypto = require('crypto'); 3 | const { describe, it } = require('mocha'); 4 | const { expect } = require('chai'); 5 | const { 6 | CryptographyKey, 7 | Ed25519PublicKey, 8 | Ed25519SecretKey, 9 | X25519PublicKey, 10 | X25519SecretKey 11 | } = require('../index'); 12 | 13 | let sodium; 14 | describe('CryptographyKey', () => { 15 | it('Internal buffer is hidden from stack traces and iterators', async () => { 16 | let bytes = await crypto.randomBytes(32); 17 | let key = new CryptographyKey(bytes); 18 | assert(Object.keys(key).length === 0, 'There should be no keys when you dump an object!'); 19 | expect(bytes.toString('hex')).to.be.equals(key.getBuffer().toString('hex')); 20 | expect(bytes.toString('hex')).to.be.equals(key.toString('hex')); 21 | }); 22 | it('constructor rejects invalid types', async () => { 23 | let bytes = await crypto.randomBytes(32); 24 | let key = new CryptographyKey(bytes); 25 | // For test coverage 26 | expect(bytes.toString('hex')).to.be.equal(key.slice().toString('hex')); 27 | expect(false).to.be.equal(key.isPublicKey()); 28 | }); 29 | it('constructor rejects invalid types', () => { 30 | expect(() => { 31 | new CryptographyKey(new Uint8Array(32)) 32 | }).to.throw('Argument 1 must be an instance of Buffer.'); 33 | }); 34 | 35 | it('from()', async () => { 36 | let key = CryptographyKey.from('808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f', 'hex'); 37 | expect(key.getBuffer().toString('hex')).to.be.equals('808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f'); 38 | 39 | let ed25519sk = Ed25519SecretKey.from( 40 | '88c6102ed8b3278ae7e95ebcd4ed3f1a513d2fd3c1a88f5ecbda5f95209ce709' + 41 | '324095af3d25e0f205d1a1297d01e810940063d05fc247d2042f6fc2f98a55c2', 42 | 'hex' 43 | ); 44 | expect(ed25519sk instanceof Ed25519SecretKey).to.be.equals(true); 45 | let ed25519pk = Ed25519PublicKey.from( 46 | '324095af3d25e0f205d1a1297d01e810940063d05fc247d2042f6fc2f98a55c2', 47 | 'hex' 48 | ); 49 | expect(ed25519pk instanceof Ed25519PublicKey).to.be.equals(true); 50 | let x25519sk = X25519SecretKey.from( 51 | 'fcb38e648f61e145c96be1a89776754b0a2e28ba57d3024ecae892dc5d93ec26', 52 | 'hex' 53 | ); 54 | expect(x25519sk instanceof X25519SecretKey).to.be.equals(true); 55 | let x25519pk = X25519PublicKey.from( 56 | '81149890dc709032327ab8d2628df8c0c8163f59bbb92a6fc3a83cb34864d503', 57 | 'hex' 58 | ); 59 | expect(x25519pk instanceof X25519PublicKey).to.be.equals(true); 60 | 61 | expect(ed25519sk.isPublicKey()).to.be.equals(false); 62 | expect(ed25519pk.isPublicKey()).to.be.equals(true); 63 | expect(ed25519sk.isEd25519Key()).to.be.equals(true); 64 | expect(ed25519pk.isEd25519Key()).to.be.equals(true); 65 | expect(ed25519sk.isX25519Key()).to.be.equals(false); 66 | expect(ed25519pk.isX25519Key()).to.be.equals(false); 67 | 68 | expect(x25519sk.isPublicKey()).to.be.equals(false); 69 | expect(x25519pk.isPublicKey()).to.be.equals(true); 70 | expect(x25519sk.isEd25519Key()).to.be.equals(false); 71 | expect(x25519pk.isEd25519Key()).to.be.equals(false); 72 | expect(x25519sk.isX25519Key()).to.be.equals(true); 73 | expect(x25519pk.isX25519Key()).to.be.equals(true); 74 | 75 | expect(() => { 76 | new Ed25519SecretKey(Buffer.alloc(32)) 77 | }).to.throw('Ed25519 secret keys must be 64 bytes long'); 78 | 79 | expect(() => { 80 | new Ed25519PublicKey(Buffer.alloc(64)) 81 | }).to.throw('Ed25519 public keys must be 32 bytes long'); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /docs/SodiumPlus/digital-signatures.md: -------------------------------------------------------------------------------- 1 | ## Digital signatures 2 | 3 | > **See also**: [Libsodium's documentation on its public-key signature features](https://download.libsodium.org/doc/public-key_cryptography/public-key_signatures). 4 | 5 | ### crypto_sign 6 | 7 | > See also: [the detached API](#crypto_sign_detached) below. 8 | 9 | Sign a message with Ed25519, returning a signed message (prefixed with the signature). 10 | 11 | **Parameters and their respective types**: 12 | 13 | 1. `{string|Buffer}` message 14 | 2. `{Ed25519SecretKey}` secretKey 15 | 16 | Returns a `Promise` that resolves to a `Buffer`. 17 | 18 | ### crypto_sign_open 19 | 20 | Verify a signed message with Ed25519, returning the original message if the signature 21 | is valid. 22 | 23 | **Parameters and their respective types**: 24 | 25 | 1. `{string|Buffer}` signedMessage 26 | 2. `{Ed25519SecretKey}` publicKey 27 | 28 | Returns a `Promise` that resolves to a `Buffer`. 29 | 30 | ### crypto_sign_detached 31 | 32 | Returns the Ed25519 signature of the message, for the given secret key. 33 | 34 | **Parameters and their respective types**: 35 | 36 | 1. `{string|Buffer}` message 37 | 2. `{Ed25519SecretKey}` secretKey 38 | 39 | Returns a `Promise` that resolves to a `Buffer`. 40 | 41 | ### crypto_sign_verify_detached 42 | 43 | Returns true if the Ed25519 signature is valid for a given message and public key. 44 | 45 | **Parameters and their respective types**: 46 | 47 | 1. `{string|Buffer}` message 48 | 2. `{Ed25519PublicKey}` publicKey 49 | 3. `{Buffer}` signature 50 | 51 | Returns a `Promise` that resolves to a `boolean`. 52 | 53 | ### crypto_sign_keypair 54 | 55 | Returns a `Promise` that resolves to a `CryptographyKey` containing a 96-byte 56 | `Buffer`. The first 64 bytes are your Ed25519 secret key, the latter 32 are your 57 | Ed25519 public key. 58 | 59 | ### crypto_sign_seed_keypair 60 | 61 | **Parameters and their respective types**: 62 | 63 | 1. `{Buffer}` 32 byte seed 64 | 65 | Returns a `Promise` that resolves to a `CryptographyKey` containing a 96-byte 66 | `Buffer`. The first 64 bytes are your Ed25519 secret key, the latter 32 are your 67 | Ed25519 public key. 68 | 69 | ### crypto_sign_publickey 70 | 71 | **Parameters and their respective types**: 72 | 73 | 1. `{CryptographyKey}` (buffer must be 96 bytes long) 74 | 75 | Returns a `Promise` that resolves to a `Ed25519PublicKey`. 76 | 77 | 78 | ### crypto_sign_secretkey 79 | 80 | **Parameters and their respective types**: 81 | 82 | 1. `{CryptographyKey}` (buffer must be 96 bytes long) 83 | 84 | Returns a `Promise` that resolves to a `Ed25519SecretKey`. 85 | 86 | ### crypto_sign_ed25519_sk_to_curve25519 87 | 88 | Obtain a birationally equivalent X25519 secret key, given an Ed25519 secret key. 89 | 90 | **Parameters and their respective types**: 91 | 92 | 1. `{Ed25519SecretKey}` 93 | 94 | Returns a `Promise` that resolves to an `X25519SecretKey`. 95 | 96 | ### crypto_sign_ed25519_pk_to_curve25519 97 | 98 | Obtain a birationally equivalent X25519 public key, given an Ed25519 public key. 99 | 100 | **Parameters and their respective types**: 101 | 102 | 1. `{Ed25519PublicKey}` 103 | 104 | Returns a `Promise` that resolves to an `X25519PublicKey`. 105 | 106 | ### Example for crypto_sign 107 | 108 | ```javascript 109 | const { SodiumPlus } = require('sodium-plus'); 110 | let sodium; 111 | 112 | (async function () { 113 | if (!sodium) sodium = await SodiumPlus.auto(); 114 | let aliceKeypair = await sodium.crypto_sign_keypair(); 115 | let aliceSecret = await sodium.crypto_sign_secretkey(aliceKeypair); 116 | let alicePublic = await sodium.crypto_sign_publickey(aliceKeypair); 117 | 118 | let message = 'This is something I need to sign publicly.'; 119 | 120 | // Detached mode: 121 | let signature = await sodium.crypto_sign_detached(message, aliceSecret); 122 | console.log(signature.toString('hex')); 123 | if (await sodium.crypto_sign_verify_detached(message, alicePublic, signature)) { 124 | console.log("Signature is valid."); 125 | } else { 126 | console.error("Invalid signature!"); 127 | } 128 | 129 | // NaCl (crypto_sign / crypto_sign_open): 130 | let signed = await sodium.crypto_sign(message, aliceSecret); 131 | let opened = await sodium.crypto_sign_open(signed, alicePublic); 132 | console.log(opened.toString()); 133 | })(); 134 | ``` 135 | -------------------------------------------------------------------------------- /docs/SodiumPlus/encrypted-streams.md: -------------------------------------------------------------------------------- 1 | ## Encrypted Streams 2 | 3 | > **See also:** [Libsodium's documentation on its encrypted streams feature](https://download.libsodium.org/doc/secret-key_cryptography/secretstream) 4 | 5 | ### crypto_secretstream_xchacha20poly1305_init_push() 6 | 7 | Initialize a stream for streaming encryption. 8 | 9 | **Parameters and their respective types**: 10 | 11 | 1. `{CryptographyKey}` key 12 | 13 | Returns a `Promise` that resolves to an `array` with 2 elements: 14 | 15 | 1. A 24-byte header that should be included in the encrypted stream. 16 | 2. A backend-specific `state`: 17 | * `LibsodiumWrappers` returns a `number` (a pointer to an internal buffer) 18 | * `SodiumNative` returns a `CryptoSecretstreamXchacha20poly1305StateWrap` 19 | object 20 | 21 | The `{state}` type annotation below refers to one of the backend-specific state 22 | types. 23 | 24 | You'll typically want to use it with list unpacking syntax, like so: 25 | 26 | ``` 27 | [state, header] = await sodium.crypto_secretstream_xchacha20poly1305_init_push(key); 28 | ``` 29 | 30 | ### crypto_secretstream_xchacha20poly1305_init_pull() 31 | 32 | Initialize a stream for streaming decryption. 33 | 34 | **Parameters and their respective types**: 35 | 36 | 1. `{CryptographyKey}` key 37 | 2. `{Buffer}` header (must be 24 bytes) 38 | 39 | Returns a `Promise` that resolves to a backend-specific `state`: 40 | 41 | * `LibsodiumWrappers` returns a `number` (a pointer to an internal buffer) 42 | * `SodiumNative` returns a `CryptoSecretstreamXchacha20poly1305StateWrap` 43 | object 44 | 45 | The `{state}` type annotation below refers to one of the backend-specific state 46 | types. 47 | 48 | ### crypto_secretstream_xchacha20poly1305_push() 49 | 50 | Encrypt some data in a stream. 51 | 52 | **Parameters and their respective types**: 53 | 54 | 1. `{state}` state 55 | 2. `{string|Buffer}` message 56 | 3. `{string|Buffer}` (optional) additional associated data 57 | 4. `{number}` tag (default = 0, see libsodium docs) 58 | 59 | Returns a `Promise` that resolves to a `Buffer` containing the ciphertext. 60 | 61 | ### crypto_secretstream_xchacha20poly1305_pull() 62 | 63 | Decrypt some data in a stream. 64 | 65 | **Parameters and their respective types**: 66 | 67 | 1. `{state}` state 68 | 2. `{string|Buffer}` ciphertext 69 | 3. `{string|Buffer}` (optional) additional associated data 70 | 4. `{number}` tag (default = 0, see libsodium docs) 71 | 72 | Returns a `Promise` that resolves to a `Buffer` containing 73 | decrypted plaintext. 74 | 75 | ### crypto_secretstream_xchacha20poly1305_keygen() 76 | 77 | Returns a `CryptographyKey` object containing a key appropriate 78 | for the `crypto_secretstream` API. 79 | 80 | ### crypto_secretstream_xchacha20poly1305_rekey() 81 | 82 | Deterministic re-keying of the internal state. 83 | 84 | **Parameters and their respective types**: 85 | 86 | 1. `{state}` state 87 | 88 | Returns a `Promise` that resolves to `undefined`. Instead, 89 | the `state` variable is overwritten in-place. 90 | 91 | ### Example for crypto_secretstream_xchacha20poly1305 92 | 93 | ```javascript 94 | const fsp = require('fs').promises; 95 | const path = require('path'); 96 | const { SodiumPlus } = require('sodium-plus'); 97 | 98 | let sodium; 99 | (async function () { 100 | if (!sodium) sodium = await SodiumPlus.auto(); 101 | 102 | let key = await sodium.crypto_secretstream_xchacha20poly1305_keygen(); 103 | let pushState, pullState, header; 104 | [pushState, header] = await sodium.crypto_secretstream_xchacha20poly1305_init_push(key); 105 | 106 | // Get a test input from the text file. 107 | let longText = await fsp.readFile(path.join(__dirname, 'encrypted-streams.md')); 108 | let chunk, readUntil; 109 | let ciphertext = Buffer.concat([header]); 110 | 111 | // How big are our chunks going to be? 112 | let PUSH_CHUNK_SIZE = await sodium.randombytes_uniform(longText.length - 32) + 32; 113 | let PULL_CHUNK_SIZE = PUSH_CHUNK_SIZE + sodium.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES; 114 | 115 | // Encryption... 116 | for (let i = 0; i < longText.length; i += PUSH_CHUNK_SIZE) { 117 | readUntil = (i + PUSH_CHUNK_SIZE) > longText.length ? longText.length : i + PUSH_CHUNK_SIZE; 118 | chunk = await sodium.crypto_secretstream_xchacha20poly1305_push( 119 | pushState, 120 | longText.slice(i, readUntil) 121 | ); 122 | ciphertext = Buffer.concat([ciphertext, chunk]); 123 | } 124 | 125 | pullState = await sodium.crypto_secretstream_xchacha20poly1305_init_pull(key, header); 126 | // Decrypt, starting at 24 (after the header, which we already have) 127 | let decrypted = Buffer.alloc(0); 128 | for (let i = 24; i < ciphertext.length; i += PULL_CHUNK_SIZE) { 129 | readUntil = (i + PULL_CHUNK_SIZE) > ciphertext.length ? ciphertext.length : i + PULL_CHUNK_SIZE; 130 | chunk = await sodium.crypto_secretstream_xchacha20poly1305_pull( 131 | pullState, 132 | ciphertext.slice(i, readUntil) 133 | ); 134 | decrypted = Buffer.concat([decrypted, chunk]); 135 | } 136 | console.log(decrypted.toString()); 137 | })(); 138 | ``` 139 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* istanbul ignore if */ 4 | if (typeof (Buffer) === 'undefined') { 5 | let Buffer = require('buffer/').Buffer; 6 | } 7 | 8 | const arrayToBuffer = require('typedarray-to-buffer'); 9 | 10 | module.exports = class Util 11 | { 12 | static async cloneBuffer(buf) { 13 | return Buffer.from(buf); 14 | } 15 | 16 | /** 17 | * Define the sodium constants 18 | * 19 | * @param {object} anyobject 20 | * @return {object} 21 | */ 22 | static populateConstants(anyobject) { 23 | anyobject.LIBRARY_VERSION_MAJOR = 10; 24 | anyobject.LIBRARY_VERSION_MINOR = 2; 25 | anyobject.VERSION_STRING = '1.0.17'; 26 | anyobject.BASE64_VARIANT_ORIGINAL = 1; 27 | anyobject.BASE64_VARIANT_ORIGINAL_NO_PADDING = 3; 28 | anyobject.BASE64_VARIANT_URLSAFE = 5; 29 | anyobject.BASE64_VARIANT_URLSAFE_NO_PADDING = 7; 30 | anyobject.CRYPTO_AEAD_AES256GCM_KEYBYTES = 32; 31 | anyobject.CRYPTO_AEAD_AES256GCM_NSECBYTES = 0; 32 | anyobject.CRYPTO_AEAD_AES256GCM_NPUBBYTES = 12; 33 | anyobject.CRYPTO_AEAD_AES256GCM_ABYTES = 16; 34 | anyobject.CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES = 32; 35 | anyobject.CRYPTO_AEAD_CHACHA20POLY1305_NSECBYTES = 0; 36 | anyobject.CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES = 8; 37 | anyobject.CRYPTO_AEAD_CHACHA20POLY1305_ABYTES = 16; 38 | anyobject.CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES = 32; 39 | anyobject.CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES = 0; 40 | anyobject.CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES = 12; 41 | anyobject.CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES = 16; 42 | anyobject.CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES = 32; 43 | anyobject.CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NSECBYTES = 0; 44 | anyobject.CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES = 24; 45 | anyobject.CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES = 16; 46 | anyobject.CRYPTO_AUTH_BYTES = 32; 47 | anyobject.CRYPTO_AUTH_KEYBYTES = 32; 48 | anyobject.CRYPTO_BOX_SEALBYTES = 16; 49 | anyobject.CRYPTO_BOX_SECRETKEYBYTES = 32; 50 | anyobject.CRYPTO_BOX_PUBLICKEYBYTES = 32; 51 | anyobject.CRYPTO_BOX_KEYPAIRBYTES = 64; 52 | anyobject.CRYPTO_BOX_MACBYTES = 16; 53 | anyobject.CRYPTO_BOX_NONCEBYTES = 24; 54 | anyobject.CRYPTO_BOX_SEEDBYTES = 32; 55 | anyobject.CRYPTO_KDF_BYTES_MIN = 16; 56 | anyobject.CRYPTO_KDF_BYTES_MAX = 64; 57 | anyobject.CRYPTO_KDF_CONTEXTBYTES = 8; 58 | anyobject.CRYPTO_KDF_KEYBYTES = 32; 59 | anyobject.CRYPTO_KX_BYTES = 32; 60 | anyobject.CRYPTO_KX_PRIMITIVE = 'x25519blake2b'; 61 | anyobject.CRYPTO_KX_SEEDBYTES = 32; 62 | anyobject.CRYPTO_KX_KEYPAIRBYTES = 64; 63 | anyobject.CRYPTO_KX_PUBLICKEYBYTES = 32; 64 | anyobject.CRYPTO_KX_SECRETKEYBYTES = 32; 65 | anyobject.CRYPTO_KX_SESSIONKEYBYTES = 32; 66 | anyobject.CRYPTO_GENERICHASH_BYTES = 32; 67 | anyobject.CRYPTO_GENERICHASH_BYTES_MIN = 16; 68 | anyobject.CRYPTO_GENERICHASH_BYTES_MAX = 64; 69 | anyobject.CRYPTO_GENERICHASH_KEYBYTES = 32; 70 | anyobject.CRYPTO_GENERICHASH_KEYBYTES_MIN = 16; 71 | anyobject.CRYPTO_GENERICHASH_KEYBYTES_MAX = 64; 72 | anyobject.CRYPTO_GENERICHASH_STATEBYTES = 384; 73 | anyobject.CRYPTO_PWHASH_SALTBYTES = 16; 74 | anyobject.CRYPTO_PWHASH_STRPREFIX = '$argon2id$'; 75 | anyobject.CRYPTO_PWHASH_ALG_ARGON2I13 = 1; 76 | anyobject.CRYPTO_PWHASH_ALG_ARGON2ID13 = 2; 77 | anyobject.CRYPTO_PWHASH_ALG_DEFAULT = anyobject.CRYPTO_PWHASH_ALG_ARGON2ID13; 78 | anyobject.CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE = 2; 79 | anyobject.CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE = 67108864; 80 | anyobject.CRYPTO_PWHASH_OPSLIMIT_MODERATE = 3; 81 | anyobject.CRYPTO_PWHASH_MEMLIMIT_MODERATE = 268435456; 82 | anyobject.CRYPTO_PWHASH_OPSLIMIT_SENSITIVE = 4; 83 | anyobject.CRYPTO_PWHASH_MEMLIMIT_SENSITIVE = 1073741824; 84 | anyobject.CRYPTO_PWHASH_SCRYPTSALSA208SHA256_SALTBYTES = 32; 85 | anyobject.CRYPTO_SCALARMULT_BYTES = 32; 86 | anyobject.CRYPTO_SCALARMULT_SCALARBYTES = 32; 87 | anyobject.CRYPTO_SHORTHASH_BYTES = 8; 88 | anyobject.CRYPTO_SHORTHASH_KEYBYTES = 16; 89 | anyobject.CRYPTO_SECRETBOX_KEYBYTES = 32; 90 | anyobject.CRYPTO_SECRETBOX_MACBYTES = 16; 91 | anyobject.CRYPTO_SECRETBOX_NONCEBYTES = 24; 92 | anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_STATEBYTES = 52; 93 | anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES = 17; 94 | anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES = 24; 95 | anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES = 32; 96 | anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH = 0; 97 | anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PULL = 1; 98 | anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY = 2; 99 | anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL = 3; 100 | anyobject.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX = 0x3fffffff80; 101 | anyobject.CRYPTO_SIGN_BYTES = 64; 102 | anyobject.CRYPTO_SIGN_SEEDBYTES = 32; 103 | anyobject.CRYPTO_SIGN_PUBLICKEYBYTES = 32; 104 | anyobject.CRYPTO_SIGN_SECRETKEYBYTES = 64; 105 | anyobject.CRYPTO_SIGN_KEYPAIRBYTES = 96; 106 | anyobject.CRYPTO_STREAM_KEYBYTES = 32; 107 | anyobject.CRYPTO_STREAM_NONCEBYTES = 24; 108 | return anyobject; 109 | } 110 | 111 | /** 112 | * Coerce input to a Buffer, throwing a TypeError if it cannot be coerced. 113 | * 114 | * @param {string|Buffer|Uint8Array|Promise} stringOrBuffer 115 | * @returns {Buffer} 116 | */ 117 | static async toBuffer(stringOrBuffer) 118 | { 119 | if (Buffer.isBuffer(stringOrBuffer)) { 120 | return stringOrBuffer; 121 | } else if (stringOrBuffer === null) { 122 | return null; 123 | } else if (typeof(stringOrBuffer) === 'string') { 124 | return Buffer.from(stringOrBuffer, 'binary'); 125 | } else if (stringOrBuffer instanceof Uint8Array) { 126 | return arrayToBuffer(stringOrBuffer); 127 | } else if (stringOrBuffer instanceof Promise) { 128 | return await stringOrBuffer; 129 | } else { 130 | throw new TypeError('Invalid type; string or buffer expected'); 131 | } 132 | } 133 | }; 134 | -------------------------------------------------------------------------------- /docs/SodiumPlus/README.md: -------------------------------------------------------------------------------- 1 | # SodiumPlus Methods 2 | 3 | This describes the methods in the public API for Sodium-Plus. 4 | If you're not sure which method to use, please refer to the 5 | [Libsodium Quick Reference](https://paragonie.com/blog/2017/06/libsodium-quick-reference-quick-comparison-similar-functions-and-which-one-use) 6 | for guidance. 7 | 8 | * [AEAD (XChaCha20-Poly1305)](AEAD.md#aead) 9 | * [crypto_aead_xchacha20poly1305_ietf_decrypt](AEAD.md#crypto_aead_xchacha20poly1305_ietf_decrypt) 10 | * [crypto_aead_xchacha20poly1305_ietf_encrypt](AEAD.md#crypto_aead_xchacha20poly1305_ietf_encrypt) 11 | * [crypto_aead_xchacha20poly1305_ietf_keygen](AEAD.md#crypto_aead_xchacha20poly1305_ietf_keygen) 12 | * [Example for crypto_aead_xchacha20poly1305_ietf_*](AEAD.md#example-for-crypto_aead_xchacha20poly1305_ietf_) 13 | * [Shared-key authentication](shared-key-authentication.md) 14 | * [crypto_auth](shared-key-authentication.md#crypto_auth) 15 | * [crypto_auth_verify](shared-key-authentication.md#crypto_auth_verify) 16 | * [crypto_auth_keygen](shared-key-authentication.md#crypto_auth_keygen) 17 | * [Example for crypto_auth](shared-key-authentication.md#example-for-crypto_auth) 18 | * [Authenticated public-key encryption](authenticated-public-key-encryption.md) 19 | * [crypto_box](authenticated-public-key-encryption.md#crypto_box) 20 | * [crypto_box_open](authenticated-public-key-encryption.md#crypto_box_open) 21 | * [crypto_box_keypair](authenticated-public-key-encryption.md#crypto_box_keypair) 22 | * [crypto_box_keypair_from_secretkey_and_secretkey](authenticated-public-key-encryption.md#crypto_box_keypair_from_secretkey_and_secretkey) 23 | * [crypto_box_publickey](authenticated-public-key-encryption.md#crypto_box_publickey) 24 | * [crypto_box_secretkey](authenticated-public-key-encryption.md#crypto_box_secretkey) 25 | * [crypto_box_publickey_from_secretkey](authenticated-public-key-encryption.md#crypto_box_publickey_from_secretkey) 26 | * [Example for crypto_box](authenticated-public-key-encryption.md#example-for-crypto_box) 27 | * [Sealed boxes (anonymous public-key encryption)](sealed-boxes.md) 28 | * [crypto_box_seal](sealed-boxes.md#crypto_box_seal) 29 | * [crypto_box_seal_open](sealed-boxes.md#crypto_box_seal_open) 30 | * [Example for crypto_box_seal](sealed-boxes.md#example-for-crypto_box_seal) 31 | * [General-purpose cryptographic hash](general-purpose-cryptographic-hash.md) 32 | * [crypto_generichash](general-purpose-cryptographic-hash.md#crypto_generichash) 33 | * [crypto_generichash_init](general-purpose-cryptographic-hash.md#crypto_generichash_init) 34 | * [crypto_generichash_update](general-purpose-cryptographic-hash.md#crypto_generichash_update) 35 | * [crypto_generichash_final](general-purpose-cryptographic-hash.md#crypto_generichash_final) 36 | * [crypto_generichash_keygen](general-purpose-cryptographic-hash.md#crypto_generichash_keygen) 37 | * [Example for crypto_generichash](general-purpose-cryptographic-hash.md#example-for-crypto_generichash) 38 | * [Key derivation](key-derivation.md) 39 | * [crypto_kdf_derive_from_key](key-derivation.md#crypto_kdf_derive_from_key) 40 | * [crypto_kdf_keygen](key-derivation.md#crypto_kdf_keygen) 41 | * [Example for crypto_kdf](key-derivation.md#example-for-crypto_kdf) 42 | * [Key exchange](key-exchange.md) 43 | * [crypto_kx_keypair](key-exchange.md#crypto_kx_keypair) 44 | * [crypto_kx_seed_keypair](key-exchange.md#crypto_kx_seed_keypair) 45 | * [crypto_kx_client_session_keys](key-exchange.md#crypto_kx_client_session_keys) 46 | * [crypto_kx_server_session_keys](key-exchange.md#crypto_kx_server_session_keys) 47 | * [Example for crypto_kx](key-exchange.md#example-for-crypto_kx) 48 | * [One-time authentication](one-time-authentication.md) 49 | * [crypto_onetimeauth](one-time-authentication.md#crypto_onetimeauth) 50 | * [crypto_onetimeauth_verify](one-time-authentication.md#crypto_onetimeauth_verify) 51 | * [crypto_onetimeauth_keygen](one-time-authentication.md#crypto_onetimeauth_keygen) 52 | * [Example for crypto_onetimeauth](one-time-authentication.md#example-for-crypto_onetimeauth) 53 | * [Password-based key derivation](password-based-key-derivation.md) 54 | * [crypto_pwhash](password-based-key-derivation.md#crypto_pwhash) 55 | * [Example for crypto_pwhash](password-based-key-derivation.md#example-for-crypto_pwhash) 56 | * [Password hashing and storage](password-hashing-and-storage.md) 57 | * [crypto_pwhash_str](password-hashing-and-storage.md#crypto_pwhash_str) 58 | * [crypto_pwhash_str_needs_rehash](password-hashing-and-storage.md#crypto_pwhash_str_needs_rehash) 59 | * [crypto_pwhash_str_verify](password-hashing-and-storage.md#crypto_pwhash_str_verify) 60 | * [Example for crypto_pwhash_str](password-hashing-and-storage.md#example-for-crypto_pwhash_str) 61 | * [Scalar multiplication over Curve25519 (advanced)](scalar-multiplication.md) 62 | * [crypto_scalarmult](scalar-multiplication.md#crypto_scalarmult) 63 | * [crypto_scalarmult_base](scalar-multiplication.md#crypto_scalarmult_base) 64 | * [Example for crypto_scalarmult](scalar-multiplication.md#example-for-crypto_scalarmult) 65 | * [Shared-key authenticated encryption](shared-key-authenticated-encryption.md) 66 | * [crypto_secretbox](shared-key-authenticated-encryption.md#crypto_secretbox) 67 | * [crypto_secretbox_open](shared-key-authenticated-encryption.md#crypto_secretbox_open) 68 | * [crypto_secretbox_keygen](shared-key-authenticated-encryption.md#crypto_secretbox_keygen) 69 | * [Example for crypto_secretbox](shared-key-authenticated-encryption.md#example-for-crypto_secretbox) 70 | * [Encrypted streams](encrypted-streams.md) 71 | * [crypto_secretstream_xchacha20poly1305_init_push](encrypted-streams.md#crypto_secretstream_xchacha20poly1305_init_push) 72 | * [crypto_secretstream_xchacha20poly1305_init_pull](encrypted-streams.md#crypto_secretstream_xchacha20poly1305_init_pull) 73 | * [crypto_secretstream_xchacha20poly1305_push](encrypted-streams.md#crypto_secretstream_xchacha20poly1305_push) 74 | * [crypto_secretstream_xchacha20poly1305_pull](encrypted-streams.md#crypto_secretstream_xchacha20poly1305_pull) 75 | * [crypto_secretstream_xchacha20poly1305_keygen](encrypted-streams.md#crypto_secretstream_xchacha20poly1305_keygen) 76 | * [crypto_secretstream_xchacha20poly1305_rekey](encrypted-streams.md#crypto_secretstream_xchacha20poly1305_rekey) 77 | * [Example for crypto_secretstream_xchacha20poly1305](encrypted-streams.md#example-for-crypto_secretstream_xchacha20poly1305) 78 | * [Short-input hashing](short-input-hashing.md) 79 | * [crypto_shorthash](short-input-hashing.md#crypto_shorthash) 80 | * [crypto_shorthash_keygen](short-input-hashing.md#crypto_shorthash_keygen) 81 | * [Example for crypto_shorthash](short-input-hashing.md#example-for-crypto_shorthash) 82 | * [Digital signatures](digital-signatures.md) 83 | * [crypto_sign](digital-signatures.md#crypto_sign) 84 | * [crypto_sign_open](digital-signatures.md#crypto_sign_open) 85 | * [crypto_sign_detached](digital-signatures.md#crypto_sign_detached) 86 | * [crypto_sign_verify_detached](digital-signatures.md#crypto_sign_verify_detached) 87 | * [crypto_sign_keypair](digital-signatures.md#crypto_sign_keypair) 88 | * [crypto_sign_publickey](digital-signatures.md#crypto_sign_publickey) 89 | * [crypto_sign_secretkey](digital-signatures.md#crypto_sign_secretkey) 90 | * [crypto_sign_ed25519_sk_to_curve25519](digital-signatures.md#crypto_sign_ed25519_sk_to_curve25519) 91 | * [crypto_sign_ed25519_pk_to_curve25519](digital-signatures.md#crypto_sign_ed25519_pk_to_curve25519) 92 | * [Example for crypto_sign](digital-signatures.md#example-for-crypto_sign) 93 | * [Randomness](randomness.md) 94 | * [randombytes_buf](randomness.md#randombytes_buf) 95 | * [randombytes_uniform](randomness.md#randombytes_uniform) 96 | * [Example for randombytes](randomness.md#example-for-randombytes) 97 | * [Utilities](utilities.md) 98 | * [sodium_bin2hex](utilities.md#sodium_bin2hex) 99 | * [sodium_hex2bin](utilities.md#sodium_bin2hex) 100 | -------------------------------------------------------------------------------- /test/longtext.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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. 4 | 5 | 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.** 6 | 7 | ## JavaScript Cryptography, Considered 8 | 9 | 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. 10 | 11 | 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. 12 | 13 | 24 | 25 | ## What PIE Has Already Done for JavaScript Cryptography 26 | 27 | ### Sodium-Plus - A Positive Experience for JS Cryptography 28 | 29 | 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). 30 | 31 | 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. 32 | 33 | Instead of writing code like this: 34 | 35 |
const sodium = require('sodium-native');
 36 | 
 37 | // Key generation
 38 | let aliceSecret = Buffer.alloc(32);
 39 | let alicePublic = Buffer.alloc(32);
 40 | let bobSecret = Buffer.alloc(32);
 41 | let bobPublic = Buffer.alloc(32);
 42 | sodium.crypto_box_keypair(alicePublic, aliceSecret);
 43 | sodium.crypto_box_keypair(bobPublic, bobSecret);
 44 | 
 45 | // Nonce
 46 | let nonce = Buffer.alloc(24);
 47 | sodium.randombytes_buf(nonce);
 48 | 
 49 | // Plaintext
 50 | let message = 'A string I want to encrypt.';
 51 | let plaintext = Buffer.from(message);
 52 | 
 53 | // Encrypt
 54 | let ciphertext = Buffer.alloc(plaintext.length + 16);
 55 | sodium.crypto_box_easy(ciphertext, plaintext, nonce, bobPublic, aliceSecret);
 56 | console.log(ciphertext.toString('hex'));
 57 | 
 58 | // Decrypt
 59 | let decrypted = Buffer.alloc(ciphertext.length - 16);
 60 | sodium.crypto_box_open_easy(decrypted, ciphertext, nonce, alicePublic, bobSecret);
 61 | console.log(decrypted.toString());
 62 | 
63 | 64 | ...you can just write this: 65 | 66 |
const { SodiumPlus } = require('sodium-plus');
 67 | let sodium;
 68 | 
 69 | (async function () {
 70 |     if (!sodium) sodium = await SodiumPlus.auto();
 71 |     let aliceKeypair = await sodium.crypto_box_keypair();
 72 |         let aliceSecret = await sodium.crypto_box_secretkey(aliceKeypair);
 73 |         let alicePublic = await sodium.crypto_box_publickey(aliceKeypair);
 74 |     let bobKeypair = await sodium.crypto_box_keypair();
 75 |         let bobSecret = await sodium.crypto_box_secretkey(bobKeypair);
 76 |         let bobPublic = await sodium.crypto_box_publickey(bobKeypair);
 77 |     
 78 |     let nonce = await sodium.randombytes_buf(24);
 79 |     let plaintext = 'Your message goes here';
 80 |     let ciphertext = await sodium.crypto_box(plaintext, nonce, aliceSecret, bobPublic);    
 81 |     console.log(ciphertext.toString('hex'));
 82 | 
 83 |     let decrypted = await sodium.crypto_box_open(ciphertext, nonce, bobSecret, alicePublic);
 84 |     console.log(decrypted.toString());
 85 | })();
 86 | 
87 | 88 | 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. 89 | 90 | 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. 91 | 92 | 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. 93 | 94 | Check out [the Sodium-Plus documentation](https://github.com/paragonie/sodium-plus/tree/master/docs) for more information. 95 | 96 | ### Certainty.js: CACert Management for JavaScript Projects 97 | 98 | 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. 99 | 100 | 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). 101 | 102 |
const {Certainty} = require('certainty-js');
103 | const http = require('request-promise-native');
104 | 
105 | (async function () {
106 |     let options = {
107 |         'ca': await Certainty.getLatestCABundle('/path/to/directory'),
108 |         'uri': 'https://php-chronicle.pie-hosted.com/chronicle/lasthash',
109 |         'minVersion': 'TLSv1.2',
110 |         'strictSSL': true,
111 |         'timeout': 30000
112 |     };
113 | 
114 |     // Send request...
115 |     console.log(await http.get(options));
116 | })();
117 | 118 | The next releases of Certainty.js will include the LocalCACertBuilder features from the PHP version, as well as a refactor to use `Sodium-Plus`. 119 | 120 | ### CipherSweet.js 121 | 122 | 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? 123 | 124 | 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). 125 | 126 | CipherSweet has already been ported from PHP to Node.js, with other languages coming soon. 127 | 128 | 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). 129 | 130 | ## Our Work Continues 131 | 132 | Like many other programming languages, JavaScript has its own needs and unique challenges. We 133 | 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. 134 | 135 | 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). 136 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Table of Contents 2 | 3 | * [Table of Contents](#table-of-contents) (you are here) 4 | * [Getting Started](getting-started.md#getting-started) 5 | * [CryptographyKey](getting-started.md#cryptographykey) 6 | * [Sodium-Plus in the Browser](getting-started.md#sodium-plus-in-the-browser) 7 | * [SodiumPlus Methods](SodiumPlus#sodiumplus-methods) 8 | * [AEAD (XChaCha20-Poly1305)](SodiumPlus/AEAD.md#aead) 9 | * [crypto_aead_xchacha20poly1305_ietf_decrypt](SodiumPlus/AEAD.md#crypto_aead_xchacha20poly1305_ietf_decrypt) 10 | * [crypto_aead_xchacha20poly1305_ietf_encrypt](SodiumPlus/AEAD.md#crypto_aead_xchacha20poly1305_ietf_encrypt) 11 | * [crypto_aead_xchacha20poly1305_ietf_keygen](SodiumPlus/AEAD.md#crypto_aead_xchacha20poly1305_ietf_keygen) 12 | * [Example for crypto_aead_xchacha20poly1305_ietf_*](SodiumPlus/AEAD.md#example-for-crypto_aead_xchacha20poly1305_ietf_) 13 | * [Shared-key authentication](SodiumPlus/shared-key-authentication.md) 14 | * [crypto_auth](SodiumPlus/shared-key-authentication.md#crypto_auth) 15 | * [crypto_auth_verify](SodiumPlus/shared-key-authentication.md#crypto_auth_verify) 16 | * [crypto_auth_keygen](SodiumPlus/shared-key-authentication.md#crypto_auth_keygen) 17 | * [Example for crypto_auth](SodiumPlus/shared-key-authentication.md#example-for-crypto_auth) 18 | * [Authenticated public-key encryption](SodiumPlus/authenticated-public-key-encryption.md) 19 | * [crypto_box](SodiumPlus/authenticated-public-key-encryption.md#crypto_box) 20 | * [crypto_box_open](SodiumPlus/authenticated-public-key-encryption.md#crypto_box_open) 21 | * [crypto_box_keypair](SodiumPlus/authenticated-public-key-encryption.md#crypto_box_keypair) 22 | * [crypto_box_keypair_from_secretkey_and_publickey](SodiumPlus/authenticated-public-key-encryption.md#crypto_box_keypair_from_secretkey_and_publickey) 23 | * [crypto_box_publickey](SodiumPlus/authenticated-public-key-encryption.md#crypto_box_publickey) 24 | * [crypto_box_secretkey](SodiumPlus/authenticated-public-key-encryption.md#crypto_box_secretkey) 25 | * [crypto_box_publickey_from_secretkey](SodiumPlus/authenticated-public-key-encryption.md#crypto_box_publickey_from_secretkey) 26 | * [Example for crypto_box](SodiumPlus/authenticated-public-key-encryption.md#example-for-crypto_box) 27 | * [Sealed boxes (anonymous public-key encryption)](SodiumPlus/sealed-boxes.md) 28 | * [crypto_box_seal](SodiumPlus/sealed-boxes.md#crypto_box_seal) 29 | * [crypto_box_seal_open](SodiumPlus/sealed-boxes.md#crypto_box_seal_open) 30 | * [Example for crypto_box_seal](SodiumPlus/sealed-boxes.md#example-for-crypto_box_seal) 31 | * [General-purpose cryptographic hash](SodiumPlus/general-purpose-cryptographic-hash.md) 32 | * [crypto_generichash](SodiumPlus/general-purpose-cryptographic-hash.md#crypto_generichash) 33 | * [crypto_generichash_init](SodiumPlus/general-purpose-cryptographic-hash.md#crypto_generichash_init) 34 | * [crypto_generichash_update](SodiumPlus/general-purpose-cryptographic-hash.md#crypto_generichash_update) 35 | * [crypto_generichash_final](SodiumPlus/general-purpose-cryptographic-hash.md#crypto_generichash_final) 36 | * [crypto_generichash_keygen](SodiumPlus/general-purpose-cryptographic-hash.md#crypto_generichash_keygen) 37 | * [Example for crypto_generichash](SodiumPlus/general-purpose-cryptographic-hash.md#example-for-crypto_generichash) 38 | * [Key derivation](SodiumPlus/key-derivation.md) 39 | * [crypto_kdf_derive_from_key](SodiumPlus/key-derivation.md#crypto_kdf_derive_from_key) 40 | * [crypto_kdf_keygen](SodiumPlus/key-derivation.md#crypto_kdf_keygen) 41 | * [Example for crypto_kdf](SodiumPlus/key-derivation.md#example-for-crypto_kdf) 42 | * [Key exchange](SodiumPlus/key-exchange.md) 43 | * [crypto_kx_keypair](SodiumPlus/key-exchange.md#crypto_kx_keypair) 44 | * [crypto_kx_seed_keypair](SodiumPlus/key-exchange.md#crypto_kx_seed_keypair) 45 | * [crypto_kx_client_session_keys](SodiumPlus/key-exchange.md#crypto_kx_client_session_keys) 46 | * [crypto_kx_server_session_keys](SodiumPlus/key-exchange.md#crypto_kx_server_session_keys) 47 | * [Example for crypto_kx](SodiumPlus/key-exchange.md#example-for-crypto_kx) 48 | * [One-time authentication](SodiumPlus/one-time-authentication.md) 49 | * [crypto_onetimeauth](SodiumPlus/one-time-authentication.md#crypto_onetimeauth) 50 | * [crypto_onetimeauth_verify](SodiumPlus/one-time-authentication.md#crypto_onetimeauth_verify) 51 | * [crypto_onetimeauth_keygen](SodiumPlus/one-time-authentication.md#crypto_onetimeauth_keygen) 52 | * [Example for crypto_onetimeauth](SodiumPlus/one-time-authentication.md#example-for-crypto_onetimeauth) 53 | * [Password-based key derivation](SodiumPlus/password-based-key-derivation.md) 54 | * [crypto_pwhash](SodiumPlus/password-based-key-derivation.md#crypto_pwhash) 55 | * [Example for crypto_pwhash](SodiumPlus/password-based-key-derivation.md#example-for-crypto_pwhash) 56 | * [Password hashing and storage](SodiumPlus/password-hashing-and-storage.md) 57 | * [crypto_pwhash_str](SodiumPlus/password-hashing-and-storage.md#crypto_pwhash_str) 58 | * [crypto_pwhash_str_needs_rehash](SodiumPlus/password-hashing-and-storage.md#crypto_pwhash_str_needs_rehash) 59 | * [crypto_pwhash_str_verify](SodiumPlus/password-hashing-and-storage.md#crypto_pwhash_str_verify) 60 | * [Example for crypto_pwhash_str](SodiumPlus/password-hashing-and-storage.md#example-for-crypto_pwhash_str) 61 | * [Scalar multiplication over Curve25519 (advanced)](SodiumPlus/scalar-multiplication.md) 62 | * [crypto_scalarmult](SodiumPlus/scalar-multiplication.md#crypto_scalarmult) 63 | * [crypto_scalarmult_base](SodiumPlus/scalar-multiplication.md#crypto_scalarmult_base) 64 | * [Example for crypto_scalarmult](SodiumPlus/scalar-multiplication.md#example-for-crypto_scalarmult) 65 | * [Shared-key authenticated encryption](SodiumPlus/shared-key-authenticated-encryption.md) 66 | * [crypto_secretbox](SodiumPlus/shared-key-authenticated-encryption.md#crypto_secretbox) 67 | * [crypto_secretbox_open](SodiumPlus/shared-key-authenticated-encryption.md#crypto_secretbox_open) 68 | * [crypto_secretbox_keygen](SodiumPlus/shared-key-authenticated-encryption.md#crypto_secretbox_keygen) 69 | * [Example for crypto_secretbox](SodiumPlus/shared-key-authenticated-encryption.md#example-for-crypto_secretbox) 70 | * [Encrypted streams](SodiumPlus/encrypted-streams.md) 71 | * [crypto_secretstream_xchacha20poly1305_init_push](SodiumPlus/encrypted-streams.md#crypto_secretstream_xchacha20poly1305_init_push) 72 | * [crypto_secretstream_xchacha20poly1305_init_pull](SodiumPlus/encrypted-streams.md#crypto_secretstream_xchacha20poly1305_init_pull) 73 | * [crypto_secretstream_xchacha20poly1305_push](SodiumPlus/encrypted-streams.md#crypto_secretstream_xchacha20poly1305_push) 74 | * [crypto_secretstream_xchacha20poly1305_pull](SodiumPlus/encrypted-streams.md#crypto_secretstream_xchacha20poly1305_pull) 75 | * [crypto_secretstream_xchacha20poly1305_keygen](SodiumPlus/encrypted-streams.md#crypto_secretstream_xchacha20poly1305_keygen) 76 | * [crypto_secretstream_xchacha20poly1305_rekey](SodiumPlus/encrypted-streams.md#crypto_secretstream_xchacha20poly1305_rekey) 77 | * [Example for crypto_secretstream_xchacha20poly1305](SodiumPlus/encrypted-streams.md#example-for-crypto_secretstream_xchacha20poly1305) 78 | * [Short-input hashing](SodiumPlus/short-input-hashing.md) 79 | * [crypto_shorthash](SodiumPlus/short-input-hashing.md#crypto_shorthash) 80 | * [crypto_shorthash_keygen](SodiumPlus/short-input-hashing.md#crypto_shorthash_keygen) 81 | * [Example for crypto_shorthash](SodiumPlus/short-input-hashing.md#example-for-crypto_shorthash) 82 | * [Digital signatures](SodiumPlus/digital-signatures.md) 83 | * [crypto_sign](SodiumPlus/digital-signatures.md#crypto_sign) 84 | * [crypto_sign_open](SodiumPlus/digital-signatures.md#crypto_sign_open) 85 | * [crypto_sign_detached](SodiumPlus/digital-signatures.md#crypto_sign_detached) 86 | * [crypto_sign_verify_detached](SodiumPlus/digital-signatures.md#crypto_sign_verify_detached) 87 | * [crypto_sign_keypair](SodiumPlus/digital-signatures.md#crypto_sign_keypair) 88 | * [crypto_sign_publickey](SodiumPlus/digital-signatures.md#crypto_sign_publickey) 89 | * [crypto_sign_secretkey](SodiumPlus/digital-signatures.md#crypto_sign_secretkey) 90 | * [crypto_sign_ed25519_sk_to_curve25519](SodiumPlus/digital-signatures.md#crypto_sign_ed25519_sk_to_curve25519) 91 | * [crypto_sign_ed25519_pk_to_curve25519](SodiumPlus/digital-signatures.md#crypto_sign_ed25519_pk_to_curve25519) 92 | * [Example for crypto_sign](SodiumPlus/digital-signatures.md#example-for-crypto_sign) 93 | * [Randomness](SodiumPlus/randomness.md) 94 | * [randombytes_buf](SodiumPlus/randomness.md#randombytes_buf) 95 | * [randombytes_uniform](SodiumPlus/randomness.md#randombytes_uniform) 96 | * [Example for randombytes](SodiumPlus/randomness.md#example-for-randombytes) 97 | * [Utilities](SodiumPlus/utilities.md) 98 | * [sodium_bin2hex](SodiumPlus/utilities.md#sodium_bin2hex) 99 | * [sodium_hex2bin](SodiumPlus/utilities.md#sodium_bin2hex) 100 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // Helper for defining opaque types like crypto_secretstream_xchacha20poly1305_state. 4 | declare const brand: unique symbol; 5 | interface Opaque { 6 | readonly [brand]: T; 7 | } 8 | 9 | // Separate the tag constants that crypto_secretstream_xchacha20poly1305_* functions 10 | // take so that we can use them to limit the input values for those functions. 11 | interface CryptoSecretStreamTagConstants { 12 | CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH: 0; 13 | CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PULL: 1; 14 | CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY: 2; 15 | CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL: 3; 16 | } 17 | type CryptoSecretStreamTagValues = CryptoSecretStreamTagConstants[keyof CryptoSecretStreamTagConstants]; 18 | 19 | interface Constants extends CryptoSecretStreamTagConstants { 20 | LIBRARY_VERSION_MAJOR: number; 21 | LIBRARY_VERSION_MINOR: number; 22 | VERSION_STRING: string; 23 | BASE64_VARIANT_ORIGINAL: number; 24 | BASE64_VARIANT_ORIGINAL_NO_PADDING: number; 25 | BASE64_VARIANT_URLSAFE: number; 26 | BASE64_VARIANT_URLSAFE_NO_PADDING: number; 27 | CRYPTO_AEAD_AES256GCM_KEYBYTES: number; 28 | CRYPTO_AEAD_AES256GCM_NSECBYTES: number; 29 | CRYPTO_AEAD_AES256GCM_NPUBBYTES: number; 30 | CRYPTO_AEAD_AES256GCM_ABYTES: number; 31 | CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES: number; 32 | CRYPTO_AEAD_CHACHA20POLY1305_NSECBYTES: number; 33 | CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES: number; 34 | CRYPTO_AEAD_CHACHA20POLY1305_ABYTES: number; 35 | CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES: number; 36 | CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES: number; 37 | CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES: number; 38 | CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES: number; 39 | CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES: number; 40 | CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NSECBYTES: number; 41 | CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES: number; 42 | CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES: number; 43 | CRYPTO_AUTH_BYTES: number; 44 | CRYPTO_AUTH_KEYBYTES: number; 45 | CRYPTO_BOX_SEALBYTES: number; 46 | CRYPTO_BOX_SECRETKEYBYTES: number; 47 | CRYPTO_BOX_PUBLICKEYBYTES: number; 48 | CRYPTO_BOX_KEYPAIRBYTES: number; 49 | CRYPTO_BOX_MACBYTES: number; 50 | CRYPTO_BOX_NONCEBYTES: number; 51 | CRYPTO_BOX_SEEDBYTES: number; 52 | CRYPTO_KDF_BYTES_MIN: number; 53 | CRYPTO_KDF_BYTES_MAX: number; 54 | CRYPTO_KDF_CONTEXTBYTES: number; 55 | CRYPTO_KDF_KEYBYTES: number; 56 | CRYPTO_KX_BYTES: number; 57 | CRYPTO_KX_PRIMITIVE: string; 58 | CRYPTO_KX_SEEDBYTES: number; 59 | CRYPTO_KX_KEYPAIRBYTES: number; 60 | CRYPTO_KX_PUBLICKEYBYTES: number; 61 | CRYPTO_KX_SECRETKEYBYTES: number; 62 | CRYPTO_KX_SESSIONKEYBYTES: number; 63 | CRYPTO_GENERICHASH_BYTES: number; 64 | CRYPTO_GENERICHASH_BYTES_MIN: number; 65 | CRYPTO_GENERICHASH_BYTES_MAX: number; 66 | CRYPTO_GENERICHASH_KEYBYTES: number; 67 | CRYPTO_GENERICHASH_KEYBYTES_MIN: number; 68 | CRYPTO_GENERICHASH_KEYBYTES_MAX: number; 69 | CRYPTO_PWHASH_SALTBYTES: number; 70 | CRYPTO_PWHASH_STRPREFIX: string; 71 | CRYPTO_PWHASH_ALG_ARGON2I13: number; 72 | CRYPTO_PWHASH_ALG_ARGON2ID13: number; 73 | CRYPTO_PWHASH_ALG_DEFAULT: number; 74 | CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE: number; 75 | CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE: number; 76 | CRYPTO_PWHASH_OPSLIMIT_MODERATE: number; 77 | CRYPTO_PWHASH_MEMLIMIT_MODERATE: number; 78 | CRYPTO_PWHASH_OPSLIMIT_SENSITIVE: number; 79 | CRYPTO_PWHASH_MEMLIMIT_SENSITIVE: number; 80 | CRYPTO_PWHASH_SCRYPTSALSA208SHA256_SALTBYTES: number; 81 | CRYPTO_SCALARMULT_BYTES: number; 82 | CRYPTO_SCALARMULT_SCALARBYTES: number; 83 | CRYPTO_SHORTHASH_BYTES: number; 84 | CRYPTO_SHORTHASH_KEYBYTES: number; 85 | CRYPTO_SECRETBOX_KEYBYTES: number; 86 | CRYPTO_SECRETBOX_MACBYTES: number; 87 | CRYPTO_SECRETBOX_NONCEBYTES: number; 88 | CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES: number; 89 | CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES: number; 90 | CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES: number; 91 | CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX: number; 92 | CRYPTO_SIGN_BYTES: number; 93 | CRYPTO_SIGN_SEEDBYTES: number; 94 | CRYPTO_SIGN_PUBLICKEYBYTES: number; 95 | CRYPTO_SIGN_SECRETKEYBYTES: number; 96 | CRYPTO_SIGN_KEYPAIRBYTES: number; 97 | CRYPTO_STREAM_KEYBYTES: number; 98 | CRYPTO_STREAM_NONCEBYTES: number; 99 | } 100 | 101 | declare namespace Module { 102 | export type crypto_secretstream_xchacha20poly1305_state = Opaque< 103 | "crypto_secretstream_xchacha20poly1305_state" 104 | >; 105 | export type crypto_generichash_state = Opaque<"crypto_generichash_state">; 106 | export type Backend = Opaque<"Backend">; 107 | 108 | export class CryptographyKey { 109 | // Deny types that would otherwise structurally match CryptographyKey. 110 | // See: https://michalzalecki.com/nominal-typing-in-typescript/ 111 | private readonly __nominal: void; 112 | 113 | constructor(buf: Buffer); 114 | static from(...args: Parameters): CryptographyKey; 115 | isEd25519Key(): boolean; 116 | isX25519Key(): boolean; 117 | isPublicKey(): boolean; 118 | getLength(): number; 119 | getBuffer(): Buffer; 120 | toString(encoding?: string): string; 121 | slice(): Buffer; 122 | } 123 | export class Ed25519PublicKey extends CryptographyKey { 124 | readonly keyType: "ed25519"; 125 | readonly publicKey: true; 126 | static from(...args: Parameters): Ed25519PublicKey; 127 | } 128 | export class Ed25519SecretKey extends CryptographyKey { 129 | readonly keyType: "ed25519"; 130 | readonly publicKey: false; 131 | static from(...args: Parameters): Ed25519SecretKey; 132 | } 133 | export class X25519PublicKey extends CryptographyKey { 134 | readonly keyType: "x25519"; 135 | readonly publicKey: true; 136 | static from(...args: Parameters): X25519PublicKey; 137 | } 138 | export class X25519SecretKey extends CryptographyKey { 139 | readonly keyType: "x25519"; 140 | readonly publicKey: false; 141 | static from(...args: Parameters): X25519SecretKey; 142 | } 143 | 144 | export class SodiumError extends Error {} 145 | 146 | export function getBackendObject( 147 | type?: "SodiumNative" | "LibsodiumWrappers" 148 | ): Backend; 149 | 150 | // Mix in Constants.* to SodiumPlus instances. 151 | export interface SodiumPlus extends Constants {} 152 | export class SodiumPlus { 153 | readonly backend: Backend; 154 | 155 | constructor(backend: Backend); 156 | 157 | getBackendName(): string; 158 | isSodiumNative(): boolean; 159 | isLibsodiumWrappers(): boolean; 160 | 161 | static auto(): Promise; 162 | ensureLoaded(): Promise; 163 | 164 | crypto_aead_xchacha20poly1305_ietf_decrypt( 165 | ciphertext: string | Buffer, 166 | nonce: string | Buffer, 167 | key: CryptographyKey, 168 | assocData?: string | Buffer 169 | ): Promise; 170 | crypto_aead_xchacha20poly1305_ietf_encrypt( 171 | plaintext: string | Buffer, 172 | nonce: string | Buffer, 173 | key: CryptographyKey, 174 | assocData?: string | Buffer 175 | ): Promise; 176 | crypto_aead_xchacha20poly1305_ietf_keygen(): Promise; 177 | 178 | crypto_auth( 179 | message: string | Buffer, 180 | key: CryptographyKey 181 | ): Promise; 182 | crypto_auth_keygen(): Promise; 183 | crypto_auth_verify( 184 | message: string | Buffer, 185 | key: CryptographyKey, 186 | mac: Buffer 187 | ): Promise; 188 | crypto_box( 189 | plaintext: string | Buffer, 190 | nonce: Buffer, 191 | myPrivateKey: X25519SecretKey, 192 | theirPublicKey: X25519PublicKey 193 | ): Promise; 194 | crypto_box_open( 195 | ciphertext: Buffer, 196 | nonce: Buffer, 197 | myPrivateKey: X25519SecretKey, 198 | theirPublicKey: X25519PublicKey 199 | ): Promise; 200 | 201 | crypto_box_keypair(): Promise; 202 | crypto_box_keypair_from_secretkey_and_secretkey( 203 | sKey: X25519SecretKey, 204 | pKey: X25519PublicKey 205 | ): Promise; 206 | crypto_box_secretkey(keypair: CryptographyKey): Promise; 207 | crypto_box_publickey(keypair: CryptographyKey): Promise; 208 | crypto_box_publickey_from_secretkey( 209 | secretKey: X25519SecretKey 210 | ): Promise; 211 | crypto_box_seal( 212 | plaintext: string | Buffer, 213 | publicKey: X25519PublicKey 214 | ): Promise; 215 | crypto_box_seal_open( 216 | ciphertext: Buffer, 217 | publicKey: X25519PublicKey, 218 | secretKey: X25519SecretKey 219 | ): Promise; 220 | 221 | crypto_generichash( 222 | message: string | Buffer, 223 | key?: CryptographyKey | null, 224 | outputLength?: number 225 | ): Promise; 226 | 227 | crypto_generichash_init( 228 | key?: CryptographyKey | null, 229 | outputLength?: number 230 | ): Promise; 231 | crypto_generichash_update( 232 | state: crypto_generichash_state, 233 | message: string | Buffer 234 | ): Promise; 235 | crypto_generichash_final( 236 | state: crypto_generichash_state, 237 | outputLength?: number 238 | ): Promise; 239 | crypto_generichash_keygen(): Promise; 240 | 241 | crypto_kdf_derive_from_key( 242 | length: number, 243 | subKeyId: number, 244 | context: string | Buffer, 245 | key: CryptographyKey 246 | ): Promise; 247 | crypto_kdf_keygen(): Promise; 248 | 249 | crypto_kx_keypair(): Promise; 250 | crypto_kx_seed_keypair(seed: string | Buffer): Promise; 251 | crypto_kx_client_session_keys( 252 | clientPublicKey: X25519PublicKey, 253 | clientSecretKey: X25519SecretKey, 254 | serverPublicKey: X25519PublicKey 255 | ): Promise; 256 | crypto_kx_server_session_keys( 257 | serverPublicKey: X25519PublicKey, 258 | serverSecretKey: X25519SecretKey, 259 | clientPublicKey: X25519PublicKey 260 | ): Promise; 261 | 262 | crypto_onetimeauth( 263 | message: string | Buffer, 264 | key: CryptographyKey 265 | ): Promise; 266 | crypto_onetimeauth_verify( 267 | message: string | Buffer, 268 | key: CryptographyKey, 269 | tag: Buffer 270 | ): Promise; 271 | crypto_onetimeauth_keygen(): Promise; 272 | 273 | crypto_pwhash( 274 | length: number, 275 | password: string | Buffer, 276 | salt: Buffer, 277 | opslimit: number, 278 | memlimit: number, 279 | algorithm?: number | null 280 | ): Promise; 281 | crypto_pwhash_str( 282 | password: string | Buffer, 283 | opslimit: number, 284 | memlimit: number 285 | ): Promise; 286 | crypto_pwhash_str_verify( 287 | password: string | Buffer, 288 | hash: string | Buffer 289 | ): Promise; 290 | crypto_pwhash_str_needs_rehash( 291 | hash: string | Buffer, 292 | opslimit: number, 293 | memlimit: number 294 | ): Promise; 295 | 296 | crypto_scalarmult( 297 | secretKey: X25519SecretKey, 298 | publicKey: X25519PublicKey 299 | ): Promise; 300 | crypto_scalarmult_base( 301 | secretKey: X25519SecretKey 302 | ): Promise; 303 | 304 | crypto_secretbox( 305 | plaintext: string | Buffer, 306 | nonce: Buffer, 307 | key: CryptographyKey 308 | ): Promise; 309 | crypto_secretbox_open( 310 | ciphertext: Buffer, 311 | nonce: Buffer, 312 | key: CryptographyKey 313 | ): Promise; 314 | crypto_secretbox_keygen(): Promise; 315 | 316 | crypto_secretstream_xchacha20poly1305_init_push( 317 | key: CryptographyKey 318 | ): Promise; 319 | crypto_secretstream_xchacha20poly1305_init_pull( 320 | key: Buffer, 321 | header: CryptographyKey 322 | ): Promise; 323 | crypto_secretstream_xchacha20poly1305_push( 324 | state: crypto_secretstream_xchacha20poly1305_state, 325 | message: string | Buffer, 326 | ad?: string | Buffer, 327 | tag?: CryptoSecretStreamTagValues 328 | ): Promise; 329 | crypto_secretstream_xchacha20poly1305_pull( 330 | state: crypto_secretstream_xchacha20poly1305_state, 331 | ciphertext: Buffer, 332 | ad?: string | Buffer, 333 | tag?: CryptoSecretStreamTagValues 334 | ): Promise; 335 | crypto_secretstream_xchacha20poly1305_rekey( 336 | state: crypto_secretstream_xchacha20poly1305_state 337 | ): Promise; 338 | crypto_secretstream_xchacha20poly1305_keygen(): Promise; 339 | 340 | crypto_shorthash( 341 | message: string | Buffer, 342 | key: CryptographyKey 343 | ): Promise; 344 | crypto_shorthash_keygen(): Promise; 345 | 346 | crypto_sign( 347 | message: string | Buffer, 348 | secretKey: Ed25519SecretKey 349 | ): Promise; 350 | crypto_sign_open( 351 | message: string | Buffer, 352 | publicKey: Ed25519PublicKey 353 | ): Promise; 354 | crypto_sign_detached( 355 | message: string | Buffer, 356 | secretKey: Ed25519SecretKey 357 | ): Promise; 358 | crypto_sign_verify_detached( 359 | message: string | Buffer, 360 | publicKey: Ed25519PublicKey, 361 | signature: Buffer 362 | ): Promise; 363 | crypto_sign_secretkey(keypair: CryptographyKey): Promise; 364 | crypto_sign_publickey(keypair: CryptographyKey): Promise; 365 | crypto_sign_seed_keypair(seed: Buffer): Promise; 366 | crypto_sign_keypair(): Promise; 367 | 368 | crypto_sign_ed25519_sk_to_curve25519( 369 | sk: Ed25519SecretKey 370 | ): Promise; 371 | 372 | crypto_sign_ed25519_pk_to_curve25519( 373 | pk: Ed25519PublicKey 374 | ): Promise; 375 | 376 | crypto_stream( 377 | length: number, 378 | nonce: Buffer, 379 | key: CryptographyKey 380 | ): Promise; 381 | crypto_stream_xor( 382 | plaintext: string | Buffer, 383 | nonce: Buffer, 384 | key: CryptographyKey 385 | ): Promise; 386 | crypto_stream_keygen(): Promise; 387 | 388 | randombytes_buf(num: number): Promise; 389 | randombytes_uniform(upperBound: number): Promise; 390 | sodium_add(val: Buffer, addv: Buffer): Promise; 391 | sodium_bin2hex(encoded: Buffer): Promise; 392 | sodium_compare(b1: Buffer, b2: Buffer): Promise; 393 | sodium_hex2bin(encoded: Buffer|string): Promise; 394 | sodium_increment(buf: Buffer): Promise; 395 | sodium_is_zero(buf: Buffer, len: number): Promise; 396 | sodium_memcmp(b1: Buffer, b2: Buffer): Promise; 397 | sodium_memzero(buf: Buffer): Promise; 398 | sodium_pad(buf: string | Buffer, blockSize: number): Promise; 399 | sodium_unpad(buf: string | Buffer, blockSize: number): Promise; 400 | } 401 | 402 | export class SodiumUtil { 403 | static cloneBuffer(buf: Buffer): Promise; 404 | static populateConstants(anyobject: T): T & Constants; 405 | static toBuffer( 406 | stringOrBuffer: string | Buffer | Uint8Array | Promise 407 | ): Promise; 408 | } 409 | 410 | export class SodiumPolyfill { 411 | static crypto_onetimeauth( 412 | message: string | Buffer, 413 | key: CryptographyKey 414 | ): Promise; 415 | static crypto_onetimeauth_verify( 416 | message: string | Buffer, 417 | key: CryptographyKey, 418 | tag: Buffer 419 | ): Promise; 420 | static crypto_stream_xor( 421 | plaintext: string | Buffer, 422 | nonce: Buffer, 423 | key: CryptographyKey 424 | ): Promise; 425 | static crypto_pwhash_str_needs_rehash( 426 | hash: string | Buffer, 427 | opslimit: number, 428 | memlimit: number 429 | ): Promise; 430 | } 431 | } 432 | 433 | export = Module; 434 | -------------------------------------------------------------------------------- /lib/backend/libsodium-wrappers.js: -------------------------------------------------------------------------------- 1 | const _sodium = require('libsodium-wrappers'); 2 | const Backend = require('../backend'); 3 | const CryptographyKey = require('../cryptography-key'); 4 | const Polyfill = require('../polyfill'); 5 | const Util = require('../util'); 6 | const SodiumError = require('../sodium-error'); 7 | const toBuffer = require('typedarray-to-buffer'); 8 | /* istanbul ignore if */ 9 | if (typeof (Buffer) === 'undefined') { 10 | let Buffer = require('buffer/').Buffer; 11 | } 12 | 13 | /* istanbul ignore next */ 14 | module.exports = class LibsodiumWrappersBackend extends Backend { 15 | constructor(lib) { 16 | super(lib); 17 | this.sodium = lib; 18 | this.backendName = 'LibsodiumWrappersBackend'; 19 | } 20 | 21 | static async init() { 22 | await _sodium.ready; 23 | return new LibsodiumWrappersBackend(_sodium); 24 | } 25 | 26 | /** 27 | * 28 | * @param {String|Buffer} ciphertext 29 | * @param {String|Buffer} assocData 30 | * @param {String|Buffer} nonce 31 | * @param {CryptographyKey} key 32 | * @return {Promise} 33 | */ 34 | async crypto_aead_xchacha20poly1305_ietf_decrypt(ciphertext, assocData, nonce, key) { 35 | return toBuffer( 36 | this.sodium.crypto_aead_xchacha20poly1305_ietf_decrypt( 37 | null, 38 | ciphertext, 39 | assocData, 40 | nonce, 41 | key.getBuffer() 42 | ) 43 | ); 44 | } 45 | 46 | /** 47 | * 48 | * @param {String|Buffer} plaintext 49 | * @param {String|Buffer} assocData 50 | * @param {String|Buffer} nonce 51 | * @param {CryptographyKey} key 52 | * @return {Promise} 53 | */ 54 | async crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, assocData, nonce, key) { 55 | return toBuffer( 56 | this.sodium.crypto_aead_xchacha20poly1305_ietf_encrypt( 57 | plaintext, 58 | assocData, 59 | null, 60 | nonce, 61 | key.getBuffer() 62 | ) 63 | ); 64 | } 65 | 66 | /** 67 | * @param {String|Buffer} message 68 | * @param {CryptographyKey} key 69 | * @return {Promise} 70 | */ 71 | async crypto_auth(message, key) { 72 | return toBuffer( 73 | this.sodium.crypto_auth( 74 | message, 75 | key.getBuffer() 76 | ) 77 | ); 78 | } 79 | 80 | /** 81 | * @param {Buffer} mac 82 | * @param {String|Buffer} message 83 | * @param {CryptographyKey} key 84 | * @return {Promise} 85 | */ 86 | async crypto_auth_verify(mac, message, key) { 87 | return this.sodium.crypto_auth_verify( 88 | mac, 89 | message, 90 | key.getBuffer() 91 | ); 92 | } 93 | 94 | /** 95 | * @param {string|Buffer} plaintext 96 | * @param {Buffer} nonce 97 | * @param {CryptographyKey} sk 98 | * @param {CryptographyKey} pk 99 | * @return {Promise} 100 | * 101 | */ 102 | async crypto_box(plaintext, nonce, sk, pk) { 103 | return Util.toBuffer( 104 | await this.sodium.crypto_box_easy( 105 | await Util.toBuffer(plaintext), 106 | await Util.toBuffer(nonce), 107 | pk.getBuffer(), 108 | sk.getBuffer() 109 | ) 110 | ); 111 | } 112 | 113 | /** 114 | * @param {Buffer} ciphertext 115 | * @param {Buffer} nonce 116 | * @param {CryptographyKey} sk 117 | * @param {CryptographyKey} pk 118 | * @return {Promise} 119 | */ 120 | async crypto_box_open(ciphertext, nonce, sk, pk) { 121 | return Util.toBuffer( 122 | await this.sodium.crypto_box_open_easy( 123 | await Util.toBuffer(ciphertext), 124 | await Util.toBuffer(nonce), 125 | pk.getBuffer(), 126 | sk.getBuffer() 127 | ) 128 | ); 129 | } 130 | 131 | /** 132 | * @param {string|Buffer} plaintext 133 | * @param {CryptographyKey} pk 134 | * @return {Promise} 135 | * 136 | */ 137 | async crypto_box_seal(plaintext, pk) { 138 | return Util.toBuffer( 139 | await this.sodium.crypto_box_seal( 140 | await Util.toBuffer(plaintext), 141 | pk.getBuffer() 142 | ) 143 | ); 144 | } 145 | 146 | /** 147 | * @param {Buffer} ciphertext 148 | * @param {CryptographyKey} pk 149 | * @param {CryptographyKey} sk 150 | * @return {Promise} 151 | */ 152 | async crypto_box_seal_open(ciphertext, pk, sk) { 153 | return Util.toBuffer( 154 | await this.sodium.crypto_box_seal_open( 155 | await Util.toBuffer(ciphertext), 156 | pk.getBuffer(), 157 | sk.getBuffer() 158 | ) 159 | ); 160 | } 161 | 162 | /** 163 | * @return {Promise} 164 | */ 165 | async crypto_box_keypair() { 166 | const obj = this.sodium.crypto_box_keypair(); 167 | return new CryptographyKey( 168 | Buffer.concat([ 169 | await Util.toBuffer(obj.privateKey), 170 | await Util.toBuffer(obj.publicKey) 171 | ]) 172 | ); 173 | } 174 | 175 | /** 176 | * @param {string|Buffer} message 177 | * @param {CryptographyKey|null} key 178 | * @param {number} outputLength 179 | * @return {Promise} 180 | */ 181 | async crypto_generichash(message, key = null, outputLength = 32) { 182 | if (key) { 183 | return Util.toBuffer( 184 | this.sodium.crypto_generichash( 185 | outputLength, 186 | await Util.toBuffer(message), 187 | key.getBuffer() 188 | ) 189 | ); 190 | } 191 | return Util.toBuffer( 192 | this.sodium.crypto_generichash( 193 | outputLength, 194 | await Util.toBuffer(message) 195 | ) 196 | ); 197 | } 198 | 199 | /** 200 | * @param {CryptographyKey|null} key 201 | * @param {number} outputLength 202 | * @return {Promise} 203 | */ 204 | async crypto_generichash_init(key = null, outputLength = 32) { 205 | if (key) { 206 | return this.sodium.crypto_generichash_init(key.getBuffer(), outputLength); 207 | } 208 | return this.sodium.crypto_generichash_init(null, outputLength); 209 | } 210 | 211 | /** 212 | * @param {*} state 213 | * @param {string|Buffer} message 214 | * @return {Promise<*>} 215 | */ 216 | async crypto_generichash_update(state, message) { 217 | return this.sodium.crypto_generichash_update(state, await Util.toBuffer(message)); 218 | } 219 | 220 | /** 221 | * @param {*} state 222 | * @param {number} outputLength 223 | * @return {Promise} 224 | */ 225 | async crypto_generichash_final(state, outputLength = 32) { 226 | return Util.toBuffer( 227 | this.sodium.crypto_generichash_final(state, outputLength) 228 | ); 229 | } 230 | 231 | /** 232 | * @param {X25519PublicKey} clientPublicKey 233 | * @param {X25519SecretKey} clientSecretKey 234 | * @param {X25519PublicKey} serverPublicKey 235 | * @return {Promise} 236 | */ 237 | async crypto_kx_client_session_keys(clientPublicKey, clientSecretKey, serverPublicKey) { 238 | const gen = this.sodium.crypto_kx_client_session_keys( 239 | clientPublicKey.getBuffer(), 240 | clientSecretKey.getBuffer(), 241 | serverPublicKey.getBuffer(), 242 | ); 243 | return [ 244 | new CryptographyKey(await Util.toBuffer(gen.sharedRx)), 245 | new CryptographyKey(await Util.toBuffer(gen.sharedTx)) 246 | ]; 247 | } 248 | 249 | /** 250 | * @param {X25519PublicKey} serverPublicKey 251 | * @param {X25519SecretKey} serverSecretKey 252 | * @param {X25519PublicKey} clientPublicKey 253 | * @return {Promise} 254 | */ 255 | async crypto_kx_server_session_keys(serverPublicKey, serverSecretKey, clientPublicKey) { 256 | const gen = this.sodium.crypto_kx_server_session_keys( 257 | serverPublicKey.getBuffer(), 258 | serverSecretKey.getBuffer(), 259 | clientPublicKey.getBuffer(), 260 | ); 261 | return [ 262 | new CryptographyKey(await Util.toBuffer(gen.sharedRx)), 263 | new CryptographyKey(await Util.toBuffer(gen.sharedTx)) 264 | ]; 265 | } 266 | 267 | /** 268 | * @param {number} length 269 | * @param {number} subKeyId 270 | * @param {string|Buffer} context 271 | * @param {CryptographyKey} key 272 | * @return {Promise} 273 | */ 274 | async crypto_kdf_derive_from_key(length, subKeyId, context, key) { 275 | return new CryptographyKey( 276 | await Util.toBuffer( 277 | this.sodium.crypto_kdf_derive_from_key( 278 | length, 279 | subKeyId | 0, 280 | context, 281 | key.getBuffer() 282 | ) 283 | ) 284 | ); 285 | } 286 | 287 | /** 288 | * @param {string|Buffer} message 289 | * @param {CryptographyKey} key 290 | * @return {Promise} 291 | */ 292 | async crypto_onetimeauth(message, key) { 293 | if (typeof this.sodium.crypto_onetimeauth === 'undefined') { 294 | return Polyfill.crypto_onetimeauth( 295 | await Util.toBuffer(message), 296 | key 297 | ); 298 | } 299 | return this.sodium.crypto_onetimeauth( 300 | await Util.toBuffer(message), 301 | key.getBuffer() 302 | ); 303 | } 304 | 305 | /** 306 | * @param {string|Buffer} message 307 | * @param {CryptographyKey} key 308 | * @param {Buffer} tag 309 | * @return {Promise} 310 | */ 311 | async crypto_onetimeauth_verify(message, key, tag) { 312 | if (typeof this.sodium.crypto_onetimeauth_verify === 'undefined') { 313 | return Polyfill.crypto_onetimeauth_verify( 314 | await Util.toBuffer(message), 315 | key, 316 | tag 317 | ); 318 | } 319 | return this.sodium.crypto_onetimeauth_verify( 320 | tag, 321 | await Util.toBuffer(message), 322 | key.getBuffer() 323 | ); 324 | } 325 | 326 | /** 327 | * @param {number} length 328 | * @param {string|Buffer} password 329 | * @param {Buffer} salt 330 | * @param {number} opslimit 331 | * @param {number} memlimit 332 | * @param {number} algorithm 333 | * @return {Promise} 334 | */ 335 | async crypto_pwhash(length, password, salt, opslimit, memlimit, algorithm) { 336 | return Util.toBuffer( 337 | this.sodium.crypto_pwhash( 338 | length, 339 | await Util.toBuffer(password), 340 | await Util.toBuffer(salt), 341 | opslimit, 342 | memlimit, 343 | algorithm 344 | ) 345 | ); 346 | } 347 | 348 | /** 349 | * @param {string|Buffer} password 350 | * @param {number} opslimit 351 | * @param {number} memlimit 352 | * @return {Promise} 353 | */ 354 | async crypto_pwhash_str(password, opslimit, memlimit) { 355 | return (await Util.toBuffer( 356 | this.sodium.crypto_pwhash_str( 357 | await Util.toBuffer(password), 358 | opslimit, 359 | memlimit 360 | )) 361 | ).toString('utf-8'); 362 | } 363 | 364 | /** 365 | * @param {string|Buffer} password 366 | * @param {string|Buffer} hash 367 | * @return {Promise} 368 | */ 369 | async crypto_pwhash_str_verify(password, hash) { 370 | return this.sodium.crypto_pwhash_str_verify( 371 | hash.toString('utf-8'), 372 | await Util.toBuffer(password) 373 | ); 374 | } 375 | 376 | /** 377 | * @param {string|Buffer} hash 378 | * @param {number} opslimit 379 | * @param {number} memlimit 380 | * @return {Promise} 381 | */ 382 | async crypto_pwhash_str_needs_rehash(hash, opslimit, memlimit) { 383 | if (typeof (this.sodium.crypto_pwhash_str_needs_rehash) !== 'function') { 384 | return await Polyfill.crypto_pwhash_str_needs_rehash(hash, opslimit, memlimit); 385 | } 386 | return this.sodium.crypto_pwhash_str_needs_rehash(hash, opslimit, memlimit); 387 | } 388 | 389 | /** 390 | * @param {X25519SecretKey} secretKey 391 | * @param {X25519PublicKey} publicKey 392 | * @return {Promise} 393 | */ 394 | async crypto_scalarmult(secretKey, publicKey) { 395 | return new CryptographyKey( 396 | await Util.toBuffer( 397 | this.sodium.crypto_scalarmult(secretKey.getBuffer(), publicKey.getBuffer()) 398 | ) 399 | ); 400 | } 401 | 402 | /** 403 | * @param {string|Buffer} plaintext 404 | * @param {Buffer} nonce 405 | * @param {CryptographyKey} key 406 | * @return {Promise} 407 | */ 408 | async crypto_secretbox(plaintext, nonce, key) { 409 | return Util.toBuffer( 410 | this.sodium.crypto_secretbox_easy( 411 | await Util.toBuffer(plaintext), 412 | nonce, 413 | key.getBuffer() 414 | ) 415 | ); 416 | } 417 | 418 | /** 419 | * @param {Buffer} ciphertext 420 | * @param {Buffer} nonce 421 | * @param {CryptographyKey} key 422 | * @return {Promise} 423 | */ 424 | async crypto_secretbox_open(ciphertext, nonce, key) { 425 | return Util.toBuffer( 426 | this.sodium.crypto_secretbox_open_easy( 427 | await Util.toBuffer(ciphertext), 428 | nonce, 429 | key.getBuffer() 430 | ) 431 | ); 432 | } 433 | 434 | /** 435 | * @param {string|Buffer} message 436 | * @param {CryptographyKey} key 437 | * @return {Promise} 438 | */ 439 | async crypto_shorthash(message, key) { 440 | return Util.toBuffer( 441 | this.sodium.crypto_shorthash( 442 | await Util.toBuffer(message), 443 | key.getBuffer() 444 | ) 445 | ); 446 | } 447 | 448 | /** 449 | * @param {string|Buffer} message, 450 | * @param {Ed25519SecretKey} secretKey 451 | * @return {Promise} 452 | */ 453 | async crypto_sign(message, secretKey) { 454 | return Util.toBuffer( 455 | this.sodium.crypto_sign( 456 | await Util.toBuffer(message), 457 | secretKey.getBuffer() 458 | ) 459 | ); 460 | } 461 | 462 | /** 463 | * @param {string|Buffer} message, 464 | * @param {Ed25519PublicKey} publicKey 465 | * @return {Promise} 466 | */ 467 | async crypto_sign_open(message, publicKey) { 468 | return Util.toBuffer( 469 | this.sodium.crypto_sign_open( 470 | message, 471 | publicKey.getBuffer() 472 | ) 473 | ); 474 | } 475 | /** 476 | * @param {string|Buffer} message, 477 | * @param {Ed25519SecretKey} secretKey 478 | * @return {Promise} 479 | */ 480 | async crypto_sign_detached(message, secretKey) { 481 | return Util.toBuffer( 482 | this.sodium.crypto_sign_detached( 483 | await Util.toBuffer(message), 484 | secretKey.getBuffer() 485 | ) 486 | ); 487 | } 488 | 489 | /** 490 | * @param {string|Buffer} message, 491 | * @param {Ed25519PublicKey} publicKey 492 | * @param {Buffer} signature 493 | * @return {Promise} 494 | */ 495 | async crypto_sign_verify_detached(message, publicKey, signature) { 496 | return this.sodium.crypto_sign_verify_detached( 497 | signature, 498 | await Util.toBuffer(message), 499 | publicKey.getBuffer() 500 | ); 501 | } 502 | 503 | /** 504 | * @return {Promise} 505 | */ 506 | async crypto_sign_keypair() { 507 | const obj = this.sodium.crypto_sign_keypair(); 508 | return new CryptographyKey( 509 | Buffer.concat([ 510 | await Util.toBuffer(obj.privateKey), 511 | await Util.toBuffer(obj.publicKey) 512 | ]) 513 | ); 514 | } 515 | 516 | /** 517 | * @param {Buffer} seed 518 | * @return {Promise} 519 | */ 520 | async crypto_sign_seed_keypair(seed) { 521 | const obj = this.sodium.crypto_sign_seed_keypair(seed); 522 | return new CryptographyKey( 523 | Buffer.concat([ 524 | await Util.toBuffer(obj.privateKey), 525 | await Util.toBuffer(obj.publicKey) 526 | ]) 527 | ); 528 | } 529 | 530 | /** 531 | * @param {Ed25519SecretKey} sk 532 | * @return {Promise} 533 | */ 534 | async crypto_sign_ed25519_sk_to_curve25519(sk) { 535 | return Util.toBuffer( 536 | this.sodium.crypto_sign_ed25519_sk_to_curve25519(sk.getBuffer()) 537 | ); 538 | } 539 | 540 | /** 541 | * @param {Ed25519PublicKey} pk 542 | * @return {Promise} 543 | */ 544 | async crypto_sign_ed25519_pk_to_curve25519(pk) { 545 | return Util.toBuffer( 546 | this.sodium.crypto_sign_ed25519_pk_to_curve25519(pk.getBuffer()) 547 | ); 548 | } 549 | 550 | 551 | /** 552 | * @param {number} length 553 | * @param {Buffer} nonce 554 | * @param {CryptographyKey} key 555 | * @return {Promise} 556 | */ 557 | async crypto_stream(length, nonce, key) { 558 | if (typeof (this.sodium.crypto_stream_xor) === 'undefined') { 559 | return Polyfill.crypto_stream_xor( 560 | Buffer.alloc(length, 0), 561 | await Util.toBuffer(nonce), 562 | key 563 | ); 564 | } 565 | return this.sodium.crypto_stream( 566 | length, 567 | await Util.toBuffer(nonce), 568 | key.getBuffer() 569 | ); 570 | } 571 | 572 | /** 573 | * @param {string|Buffer} plaintext 574 | * @param {Buffer} nonce 575 | * @param {CryptographyKey} key 576 | * @return {Promise} 577 | */ 578 | async crypto_stream_xor(plaintext, nonce, key) { 579 | if (typeof (this.sodium.crypto_stream_xor) === 'undefined') { 580 | return Polyfill.crypto_stream_xor( 581 | await Util.toBuffer(plaintext), 582 | await Util.toBuffer(nonce), 583 | key 584 | ) 585 | } 586 | return this.sodium.crypto_stream_xor( 587 | await Util.toBuffer(plaintext), 588 | await Util.toBuffer(nonce), 589 | key.getBuffer() 590 | ); 591 | } 592 | 593 | /** 594 | * 595 | * @param {CryptographyKey} secretKey 596 | * @return {Promise} 597 | */ 598 | async crypto_scalarmult_base(secretKey) { 599 | return Util.toBuffer( 600 | this.sodium.crypto_scalarmult_base(secretKey.getBuffer()) 601 | ); 602 | } 603 | 604 | /** 605 | * @param {CryptographyKey} key 606 | * @return {Promise} [state, header] 607 | */ 608 | async crypto_secretstream_xchacha20poly1305_init_push(key) { 609 | const res = this.sodium.crypto_secretstream_xchacha20poly1305_init_push(key.getBuffer()); 610 | return [res.state, await Util.toBuffer(res.header)]; 611 | } 612 | 613 | /** 614 | * @param {Buffer} header 615 | * @param {CryptographyKey} key 616 | * @return {Promise<*>} Returns the opaque state object 617 | */ 618 | async crypto_secretstream_xchacha20poly1305_init_pull(header, key) { 619 | if (header.length !== this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES) { 620 | throw new SodiumError(`Header must be ${this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES} bytes long`); 621 | } 622 | return this.sodium.crypto_secretstream_xchacha20poly1305_init_pull(header, key.getBuffer()); 623 | } 624 | 625 | /** 626 | * @param {*} state 627 | * @param {string|Buffer} message 628 | * @param {string|Buffer} ad 629 | * @param {number} tag 630 | * @return {Promise} 631 | */ 632 | async crypto_secretstream_xchacha20poly1305_push(state, message, ad = '', tag = 0) { 633 | return Util.toBuffer( 634 | this.sodium.crypto_secretstream_xchacha20poly1305_push( 635 | state, 636 | await Util.toBuffer(message), 637 | ad.length > 0 ? (await Util.toBuffer(ad)) : null, 638 | tag 639 | ) 640 | ); 641 | } 642 | 643 | /** 644 | * @param {*} state 645 | * @param {Buffer} ciphertext 646 | * @param {string|Buffer} ad 647 | * @param {number} tag 648 | * @return {Promise} 649 | */ 650 | async crypto_secretstream_xchacha20poly1305_pull(state, ciphertext, ad = '', tag = 0) { 651 | if (ciphertext.length < this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES) { 652 | throw new SodiumError('Invalid ciphertext size'); 653 | } 654 | const out = this.sodium.crypto_secretstream_xchacha20poly1305_pull( 655 | state, 656 | await Util.toBuffer(ciphertext), 657 | ad.length > 0 ? (await Util.toBuffer(ad)) : null, 658 | tag 659 | ); 660 | if (tag !== out.tag) { 661 | throw new SodiumError(`Invalid tag (Given: ${tag}; Expected: ${out.tag})`); 662 | } 663 | return Util.toBuffer(out.message); 664 | } 665 | 666 | /** 667 | * @param {*} state 668 | * @return {Promise} 669 | */ 670 | async crypto_secretstream_xchacha20poly1305_rekey(state) { 671 | this.sodium.crypto_secretstream_xchacha20poly1305_rekey(state); 672 | } 673 | 674 | /** 675 | * @param {number} number 676 | * @return {Promise} 677 | */ 678 | async randombytes_buf(number) { 679 | return Util.toBuffer(await this.sodium.randombytes_buf(number)); 680 | } 681 | 682 | /** 683 | * @param {number} upperBound 684 | * @return {Promise} 685 | */ 686 | async randombytes_uniform(upperBound) { 687 | return this.sodium.randombytes_uniform(upperBound); 688 | } 689 | 690 | /** 691 | * @param {Uint8Array} val 692 | * @param {Uint8Array} addv 693 | * @return {Promise} 694 | */ 695 | async sodium_add(val, addv) { 696 | const buf = await Util.cloneBuffer(val); 697 | this.sodium.add(buf, addv); 698 | return buf; 699 | } 700 | 701 | /** 702 | * @param {Buffer} buf 703 | * @return {Promise} 704 | */ 705 | async sodium_bin2hex(buf) { 706 | return this.sodium.to_hex(buf); 707 | } 708 | 709 | /** 710 | * @param {Buffer} b1 711 | * @param {Buffer} b2 712 | * @return {Promise} 713 | */ 714 | async sodium_compare(b1, b2) { 715 | return this.sodium.compare(b1, b2); 716 | } 717 | 718 | /** 719 | * @param {Buffer|string} encoded 720 | * @return {Promise} 721 | */ 722 | async sodium_hex2bin(encoded) { 723 | return Buffer.from(this.sodium.from_hex(encoded)); 724 | } 725 | 726 | /** 727 | * @param {Buffer} buf 728 | * @return {Promise} 729 | */ 730 | async sodium_increment(buf) { 731 | return this.sodium.increment(buf); 732 | } 733 | 734 | /** 735 | * @param {Buffer} buf 736 | * @param {number} len 737 | * @return {Promise} 738 | */ 739 | async sodium_is_zero(buf, len) { 740 | return this.sodium.is_zero(buf, len); 741 | } 742 | 743 | /** 744 | * @param {Buffer} b1 745 | * @param {Buffer} b2 746 | * @return {Promise} 747 | */ 748 | async sodium_memcmp(b1, b2) { 749 | return this.sodium.memcmp(b1, b2); 750 | } 751 | 752 | /** 753 | * @param {Buffer} buf 754 | * @return {Promise} 755 | */ 756 | async sodium_memzero(buf) { 757 | this.sodium.memzero(buf); 758 | } 759 | 760 | 761 | /** 762 | * 763 | * @param {string|Buffer} buf 764 | * @param {number} blockSize 765 | * @return {Promise} 766 | */ 767 | async sodium_pad(buf, blockSize) { 768 | return Util.toBuffer( 769 | this.sodium.pad(await Util.toBuffer(buf), blockSize) 770 | ); 771 | } 772 | 773 | /** 774 | * 775 | * @param {string|Buffer} buf 776 | * @param {number} blockSize 777 | * @return {Promise} 778 | */ 779 | async sodium_unpad(buf, blockSize) { 780 | return Util.toBuffer(this.sodium.unpad(buf, blockSize)); 781 | } 782 | }; 783 | -------------------------------------------------------------------------------- /lib/backend/sodiumnative.js: -------------------------------------------------------------------------------- 1 | let loaded = false; 2 | let _sodium; 3 | /* istanbul ignore next */ 4 | try { 5 | _sodium = require('sodium-native'); 6 | loaded = true; 7 | } catch (e) { 8 | _sodium = {}; 9 | } 10 | const Backend = require('../backend'); 11 | const CryptographyKey = require('../cryptography-key'); 12 | const SodiumError = require('../sodium-error'); 13 | const Util = require('../util'); 14 | const toBuffer = require('typedarray-to-buffer'); 15 | /* istanbul ignore if */ 16 | if (typeof (Buffer) === 'undefined') { 17 | let Buffer = require('buffer/').Buffer; 18 | } 19 | 20 | /* istanbul ignore next */ 21 | module.exports = class SodiumNativeBackend extends Backend { 22 | constructor(lib) { 23 | super(lib); 24 | this.sodium = lib; 25 | this.backendName = 'SodiumNativeBackend'; 26 | } 27 | 28 | static async init() { 29 | if (!loaded) { 30 | throw new SodiumError('sodium-native not installed'); 31 | } 32 | return new SodiumNativeBackend(_sodium); 33 | } 34 | 35 | /** 36 | * 37 | * @param {String|Buffer} ciphertext 38 | * @param {String|Buffer} assocData 39 | * @param {String|Buffer} nonce 40 | * @param {CryptographyKey} key 41 | * @return {Promise} 42 | */ 43 | async crypto_aead_xchacha20poly1305_ietf_decrypt(ciphertext, assocData, nonce, key) { 44 | const plaintext = Buffer.alloc(ciphertext.length - 16, 0); 45 | this.sodium.crypto_aead_xchacha20poly1305_ietf_decrypt( 46 | plaintext, 47 | null, 48 | await Util.toBuffer(ciphertext), 49 | await Util.toBuffer(assocData), 50 | await Util.toBuffer(nonce), 51 | key.getBuffer() 52 | ); 53 | return plaintext; 54 | } 55 | 56 | /** 57 | * 58 | * @param {String|Buffer} plaintext 59 | * @param {String|Buffer} assocData 60 | * @param {String|Buffer} nonce 61 | * @param {CryptographyKey} key 62 | * @return {Promise} 63 | */ 64 | async crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, assocData, nonce, key) { 65 | const ciphertext = Buffer.alloc(plaintext.length + 16, 0); 66 | this.sodium.crypto_aead_xchacha20poly1305_ietf_encrypt( 67 | ciphertext, 68 | await Util.toBuffer(plaintext), 69 | await Util.toBuffer(assocData), 70 | null, 71 | await Util.toBuffer(nonce), 72 | key.getBuffer() 73 | ); 74 | return ciphertext; 75 | } 76 | 77 | /** 78 | * @param {String|Buffer} message 79 | * @param {CryptographyKey} key 80 | * @return {Promise} 81 | */ 82 | async crypto_auth(message, key) { 83 | const output = Buffer.alloc(32); 84 | this.sodium.crypto_auth( 85 | output, 86 | await Util.toBuffer(message), 87 | key.getBuffer() 88 | ); 89 | return toBuffer(output); 90 | } 91 | 92 | /** 93 | * @param {Buffer} mac 94 | * @param {String|Buffer} message 95 | * @param {CryptographyKey} key 96 | * @return {Promise} 97 | */ 98 | async crypto_auth_verify(mac, message, key) { 99 | return this.sodium.crypto_auth_verify( 100 | mac, 101 | await Util.toBuffer(message), 102 | key.getBuffer() 103 | ); 104 | } 105 | 106 | /** 107 | * @param {string|Buffer} plaintext 108 | * @param {Buffer} nonce 109 | * @param {CryptographyKey} sk 110 | * @param {CryptographyKey} pk 111 | * @return {Promise} 112 | * 113 | */ 114 | async crypto_box(plaintext, nonce, sk, pk) { 115 | const ciphertext = Buffer.alloc(plaintext.length + 16); 116 | this.sodium.crypto_box_easy( 117 | ciphertext, 118 | await Util.toBuffer(plaintext), 119 | nonce, 120 | pk.getBuffer(), 121 | sk.getBuffer() 122 | ); 123 | return Util.toBuffer(ciphertext); 124 | } 125 | 126 | /** 127 | * @param {Buffer} ciphertext 128 | * @param {Buffer} nonce 129 | * @param {CryptographyKey} sk 130 | * @param {CryptographyKey} pk 131 | * @return {Promise} 132 | */ 133 | async crypto_box_open(ciphertext, nonce, sk, pk) { 134 | const plaintext = Buffer.alloc(ciphertext.length - 16); 135 | const success = this.sodium.crypto_box_open_easy( 136 | plaintext, 137 | ciphertext, 138 | nonce, 139 | pk.getBuffer(), 140 | sk.getBuffer() 141 | ); 142 | if (!success) { 143 | throw new SodiumError('Decryption failed'); 144 | } 145 | return Util.toBuffer(plaintext); 146 | } 147 | 148 | /** 149 | * @param {string|Buffer} plaintext 150 | * @param {CryptographyKey} pk 151 | * @return {Promise} 152 | * 153 | */ 154 | async crypto_box_seal(plaintext, pk) { 155 | const ciphertext = Buffer.alloc(plaintext.length + 48); 156 | this.sodium.crypto_box_seal( 157 | ciphertext, 158 | await Util.toBuffer(plaintext), 159 | pk.getBuffer() 160 | ); 161 | return Util.toBuffer(ciphertext); 162 | } 163 | 164 | /** 165 | * @param {Buffer} ciphertext 166 | * @param {CryptographyKey} pk 167 | * @param {CryptographyKey} sk 168 | * @return {Promise} 169 | */ 170 | async crypto_box_seal_open(ciphertext, pk, sk) { 171 | const plaintext = Buffer.alloc(ciphertext.length - 48); 172 | const success = this.sodium.crypto_box_seal_open( 173 | plaintext, 174 | await Util.toBuffer(ciphertext), 175 | pk.getBuffer(), 176 | sk.getBuffer() 177 | ); 178 | if (!success) { 179 | throw new SodiumError('Decryption failed'); 180 | } 181 | return Util.toBuffer(plaintext); 182 | } 183 | 184 | /** 185 | * @return {Promise} 186 | */ 187 | async crypto_box_keypair() { 188 | const sK = Buffer.alloc(32, 0); 189 | const pK = Buffer.alloc(32, 0); 190 | this.sodium.crypto_box_keypair(sK, pK); 191 | return new CryptographyKey( 192 | Buffer.concat([pK, sK]) 193 | ); 194 | } 195 | 196 | /** 197 | * @param {string|Buffer} message 198 | * @param {CryptographyKey|null} key 199 | * @param {number} outputLength 200 | * @return {Promise} 201 | */ 202 | async crypto_generichash(message, key = null, outputLength = 32) { 203 | const hash = Buffer.alloc(outputLength); 204 | if (key) { 205 | this.sodium.crypto_generichash(hash, await Util.toBuffer(message), key.getBuffer()); 206 | } else { 207 | this.sodium.crypto_generichash(hash, await Util.toBuffer(message)); 208 | } 209 | return hash; 210 | } 211 | 212 | /** 213 | * @param {CryptographyKey|null} key 214 | * @param {number} outputLength 215 | * @return {Promise} 216 | */ 217 | async crypto_generichash_init(key = null, outputLength = 32) { 218 | const state = Buffer.alloc(this.CRYPTO_GENERICHASH_STATEBYTES); 219 | if (key) { 220 | this.sodium.crypto_generichash_init(state, key.getBuffer(), outputLength); 221 | } else { 222 | this.sodium.crypto_generichash_init(state, null, outputLength); 223 | } 224 | return state; 225 | } 226 | 227 | /** 228 | * @param {*} state 229 | * @param {string|Buffer} message 230 | * @return {Promise<*>} 231 | */ 232 | async crypto_generichash_update(state, message) { 233 | this.sodium.crypto_generichash_update(state, await Util.toBuffer(message)); 234 | return state; 235 | } 236 | 237 | /** 238 | * @param {*} state 239 | * @param {number} outputLength 240 | * @return {Promise} 241 | */ 242 | async crypto_generichash_final(state, outputLength = 32) { 243 | const output = Buffer.alloc(outputLength); 244 | this.sodium.crypto_generichash_final(state, output); 245 | return output; 246 | } 247 | 248 | /** 249 | * @param {number} length 250 | * @param {number} subKeyId 251 | * @param {string|Buffer} context 252 | * @param {CryptographyKey} key 253 | * @return {Promise} 254 | */ 255 | async crypto_kdf_derive_from_key(length, subKeyId, context, key) { 256 | const subkey = Buffer.alloc(length, 0); 257 | this.sodium.crypto_kdf_derive_from_key( 258 | subkey, 259 | subKeyId | 0, 260 | await Util.toBuffer(context), 261 | key.getBuffer() 262 | ); 263 | return new CryptographyKey(subkey); 264 | } 265 | 266 | /** 267 | * @param {X25519PublicKey} clientPublicKey 268 | * @param {X25519SecretKey} clientSecretKey 269 | * @param {X25519PublicKey} serverPublicKey 270 | * @return {Promise} 271 | */ 272 | async crypto_kx_client_session_keys(clientPublicKey, clientSecretKey, serverPublicKey) { 273 | const rx = Buffer.alloc(this.CRYPTO_KX_SESSIONKEYBYTES); 274 | const tx = Buffer.alloc(this.CRYPTO_KX_SESSIONKEYBYTES); 275 | this.sodium.crypto_kx_client_session_keys( 276 | rx, 277 | tx, 278 | clientPublicKey.getBuffer(), 279 | clientSecretKey.getBuffer(), 280 | serverPublicKey.getBuffer(), 281 | ); 282 | return [ 283 | new CryptographyKey(rx), 284 | new CryptographyKey(tx) 285 | ]; 286 | } 287 | 288 | /** 289 | * @param {X25519PublicKey} serverPublicKey 290 | * @param {X25519SecretKey} serverSecretKey 291 | * @param {X25519PublicKey} clientPublicKey 292 | * @return {Promise} 293 | */ 294 | async crypto_kx_server_session_keys(serverPublicKey, serverSecretKey, clientPublicKey) { 295 | const rx = Buffer.alloc(this.CRYPTO_KX_SESSIONKEYBYTES); 296 | const tx = Buffer.alloc(this.CRYPTO_KX_SESSIONKEYBYTES); 297 | this.sodium.crypto_kx_server_session_keys( 298 | rx, 299 | tx, 300 | serverPublicKey.getBuffer(), 301 | serverSecretKey.getBuffer(), 302 | clientPublicKey.getBuffer(), 303 | ); 304 | return [ 305 | new CryptographyKey(rx), 306 | new CryptographyKey(tx) 307 | ]; 308 | } 309 | 310 | /** 311 | * @param {string|Buffer} message 312 | * @param {CryptographyKey} key 313 | * @return {Promise} 314 | */ 315 | async crypto_onetimeauth(message, key) { 316 | const output = Buffer.alloc(16); 317 | this.sodium.crypto_onetimeauth( 318 | output, 319 | await Util.toBuffer(message), 320 | key.getBuffer() 321 | ); 322 | return output; 323 | } 324 | 325 | /** 326 | * @param {string|Buffer} message 327 | * @param {CryptographyKey} key 328 | * @param {Buffer} tag 329 | * @return {Promise} 330 | */ 331 | async crypto_onetimeauth_verify(message, key, tag) { 332 | return this.sodium.crypto_onetimeauth_verify( 333 | tag, 334 | await Util.toBuffer(message), 335 | key.getBuffer() 336 | ); 337 | } 338 | 339 | /** 340 | * @param {number} length 341 | * @param {string|Buffer} password 342 | * @param {Buffer} salt 343 | * @param {number} opslimit 344 | * @param {number} memlimit 345 | * @param {number} algorithm 346 | * @return {Promise} 347 | */ 348 | async crypto_pwhash(length, password, salt, opslimit, memlimit, algorithm) { 349 | const hashed = Buffer.alloc(length, 0); 350 | const bufPass = await Util.toBuffer(password); 351 | const bufSalt = await Util.toBuffer(salt); 352 | await new Promise((resolve, reject) => { 353 | this.sodium.crypto_pwhash_async( 354 | hashed, 355 | bufPass, 356 | bufSalt, 357 | opslimit, 358 | memlimit, 359 | algorithm, 360 | (e, res) => { 361 | if (e) return reject(e); 362 | return resolve(res); 363 | } 364 | ); 365 | }); 366 | return hashed; 367 | } 368 | 369 | /** 370 | * @param {string|Buffer} password 371 | * @param {number} opslimit 372 | * @param {number} memlimit 373 | * @return {Promise} 374 | */ 375 | async crypto_pwhash_str(password, opslimit, memlimit) { 376 | const hashed = Buffer.alloc(128, 0); 377 | const bufPass = await Util.toBuffer(password); 378 | await new Promise((resolve, reject) => { 379 | this.sodium.crypto_pwhash_str_async( 380 | hashed, 381 | bufPass, 382 | opslimit, 383 | memlimit, 384 | (e, res) => { 385 | if (e) return reject(e); 386 | return resolve(res); 387 | } 388 | ); 389 | }); 390 | return hashed.toString(); 391 | 392 | } 393 | 394 | /** 395 | * @param {string|Buffer} password 396 | * @param {string|Buffer} hash 397 | * @return {Promise} 398 | */ 399 | async crypto_pwhash_str_verify(password, hash) { 400 | const allocated = Buffer.alloc(128, 0); 401 | (await Util.toBuffer(hash)).copy(allocated, 0, 0); 402 | const bufPass = await Util.toBuffer(password); 403 | return new Promise((resolve, reject) => { 404 | this.sodium.crypto_pwhash_str_verify_async( 405 | allocated, 406 | bufPass, 407 | (e, res) => { 408 | if (e) return reject(e); 409 | return resolve(res); 410 | } 411 | ); 412 | }); 413 | } 414 | 415 | /** 416 | * @param {string|Buffer} hash 417 | * @param {number} opslimit 418 | * @param {number} memlimit 419 | * @return {Promise} 420 | */ 421 | async crypto_pwhash_str_needs_rehash(hash, opslimit, memlimit) { 422 | const allocated = Buffer.alloc(128, 0); 423 | (await Util.toBuffer(hash)).copy(allocated, 0, 0); 424 | return this.sodium.crypto_pwhash_str_needs_rehash( 425 | allocated, 426 | opslimit, 427 | memlimit 428 | ); 429 | } 430 | 431 | /** 432 | * @param {X25519SecretKey} secretKey 433 | * @param {X25519PublicKey} publicKey 434 | * @return {Promise} 435 | */ 436 | async crypto_scalarmult(secretKey, publicKey) { 437 | const shared = Buffer.alloc(32); 438 | this.sodium.crypto_scalarmult(shared, secretKey.getBuffer(), publicKey.getBuffer()); 439 | return new CryptographyKey( 440 | await Util.toBuffer(shared) 441 | ); 442 | } 443 | 444 | /** 445 | * 446 | * @param {CryptographyKey} secretKey 447 | * @return {Promise} 448 | */ 449 | async crypto_scalarmult_base(secretKey) { 450 | const buf = Buffer.alloc(32); 451 | this.sodium.crypto_scalarmult_base(buf, secretKey.getBuffer()); 452 | return buf; 453 | } 454 | 455 | 456 | /** 457 | * @param {string|Buffer} plaintext 458 | * @param {Buffer} nonce 459 | * @param {CryptographyKey} key 460 | * @return {Promise} 461 | */ 462 | async crypto_secretbox(plaintext, nonce, key) { 463 | const encrypted = Buffer.alloc(plaintext.length + 16); 464 | this.sodium.crypto_secretbox_easy( 465 | encrypted, 466 | await Util.toBuffer(plaintext), 467 | nonce, 468 | key.getBuffer() 469 | ); 470 | return encrypted; 471 | } 472 | 473 | /** 474 | * @param {string|Buffer} message 475 | * @param {CryptographyKey} key 476 | * @return {Promise} 477 | */ 478 | async crypto_shorthash(message, key) { 479 | const output = Buffer.alloc(8); 480 | this.sodium.crypto_shorthash( 481 | output, 482 | await Util.toBuffer(message), 483 | key.getBuffer() 484 | ); 485 | return output; 486 | } 487 | 488 | /** 489 | * @param {Buffer} ciphertext 490 | * @param {Buffer} nonce 491 | * @param {CryptographyKey} key 492 | * @return {Promise} 493 | */ 494 | async crypto_secretbox_open(ciphertext, nonce, key) { 495 | const decrypted = Buffer.alloc(ciphertext.length - 16); 496 | if (!this.sodium.crypto_secretbox_open_easy( 497 | decrypted, 498 | ciphertext, 499 | nonce, 500 | key.getBuffer() 501 | )) { 502 | throw new SodiumError('Decryption failure'); 503 | } 504 | return decrypted; 505 | } 506 | 507 | /** 508 | * @param {CryptographyKey} key 509 | * @return {Promise} [state, header] 510 | */ 511 | async crypto_secretstream_xchacha20poly1305_init_push(key) { 512 | const state = Buffer.alloc(this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_STATEBYTES); 513 | const header = Buffer.alloc(this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES); 514 | this.sodium.randombytes_buf(header); 515 | this.sodium.crypto_secretstream_xchacha20poly1305_init_push(state, header, key.getBuffer()); 516 | return [state, header]; 517 | } 518 | 519 | /** 520 | * @param {Buffer} header 521 | * @param {CryptographyKey} key 522 | * @return {Promise<*>} Returns the opaque state object 523 | */ 524 | async crypto_secretstream_xchacha20poly1305_init_pull(header, key) { 525 | if (header.length !== this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES) { 526 | throw new SodiumError(`Header must be ${this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES} bytes long`); 527 | } 528 | const state = Buffer.alloc(this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_STATEBYTES); 529 | this.sodium.crypto_secretstream_xchacha20poly1305_init_pull(state, header, key.getBuffer()); 530 | return state; 531 | } 532 | 533 | /** 534 | * @param {*} state 535 | * @param {string|Buffer} message 536 | * @param {string|Buffer} ad 537 | * @param {number} tag 538 | * @return {Promise} 539 | */ 540 | async crypto_secretstream_xchacha20poly1305_push(state, message, ad = '', tag = 0) { 541 | const ciphertext = Buffer.alloc(message.length + this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES); 542 | this.sodium.crypto_secretstream_xchacha20poly1305_push( 543 | state, 544 | ciphertext, 545 | await Util.toBuffer(message), 546 | ad.length > 0 ? (await Util.toBuffer(ad)) : null, 547 | Buffer.from([tag]) 548 | ); 549 | return ciphertext; 550 | } 551 | 552 | /** 553 | * @param {*} state 554 | * @param {Buffer} ciphertext 555 | * @param {string|Buffer} ad 556 | * @param {number} tag 557 | * @return {Promise} 558 | */ 559 | async crypto_secretstream_xchacha20poly1305_pull(state, ciphertext, ad = '', tag = 0) { 560 | if (ciphertext.length < this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES) { 561 | throw new SodiumError('Invalid ciphertext size'); 562 | } 563 | const plaintext = Buffer.alloc(ciphertext.length - this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES); 564 | this.sodium.crypto_secretstream_xchacha20poly1305_pull( 565 | state, 566 | plaintext, 567 | Buffer.from([tag]), 568 | ciphertext, 569 | ad.length > 0 ? (await Util.toBuffer(ad)) : null 570 | ); 571 | return plaintext; 572 | } 573 | 574 | /** 575 | * @param {*} state 576 | * @return {Promise} 577 | */ 578 | async crypto_secretstream_xchacha20poly1305_rekey(state) { 579 | this.sodium.crypto_secretstream_xchacha20poly1305_rekey(state); 580 | } 581 | 582 | /** 583 | * @param {string|Buffer} message, 584 | * @param {Ed25519SecretKey} secretKey 585 | * @return {Promise} 586 | */ 587 | async crypto_sign(message, secretKey) { 588 | const signed = Buffer.alloc(message.length + 64); 589 | this.sodium.crypto_sign(signed, await Util.toBuffer(message), secretKey.getBuffer()); 590 | return signed; 591 | } 592 | 593 | /** 594 | * @param {Buffer} signedMessage, 595 | * @param {Ed25519PublicKey} publicKey 596 | * @return {Promise} 597 | */ 598 | async crypto_sign_open(signedMessage, publicKey) { 599 | const original = Buffer.alloc(signedMessage.length - 64); 600 | this.sodium.crypto_sign_open(original, await Util.toBuffer(signedMessage), publicKey.getBuffer()); 601 | return original; 602 | } 603 | 604 | /** 605 | * @param {string|Buffer} message, 606 | * @param {Ed25519SecretKey} secretKey 607 | * @return {Promise} 608 | */ 609 | async crypto_sign_detached(message, secretKey) { 610 | const signature = Buffer.alloc(64); 611 | this.sodium.crypto_sign_detached(signature, await Util.toBuffer(message), secretKey.getBuffer()); 612 | return signature; 613 | } 614 | 615 | /** 616 | * @param {string|Buffer} message, 617 | * @param {Ed25519PublicKey} publicKey 618 | * @param {Buffer} signature 619 | * @return {Promise} 620 | */ 621 | async crypto_sign_verify_detached(message, publicKey, signature) { 622 | return this.sodium.crypto_sign_verify_detached( 623 | signature, 624 | await Util.toBuffer(message), 625 | publicKey.getBuffer() 626 | ); 627 | } 628 | 629 | /** 630 | * @return {Promise} 631 | */ 632 | async crypto_sign_keypair() { 633 | const sK = Buffer.alloc(64, 0); 634 | const pK = Buffer.alloc(32, 0); 635 | this.sodium.crypto_sign_keypair(pK, sK); 636 | return new CryptographyKey( 637 | Buffer.concat([sK, pK]) 638 | ); 639 | } 640 | 641 | /** 642 | * @param {Buffer} seed 643 | * @return {Promise} 644 | */ 645 | async crypto_sign_seed_keypair(seed) { 646 | const sK = Buffer.alloc(64, 0); 647 | const pK = Buffer.alloc(32, 0); 648 | this.sodium.crypto_sign_seed_keypair(pK, sK, seed); 649 | return new CryptographyKey( 650 | Buffer.concat([sK, pK]) 651 | ); 652 | } 653 | 654 | /** 655 | * @param {Ed25519SecretKey} sk 656 | * @return {Promise} 657 | */ 658 | async crypto_sign_ed25519_sk_to_curve25519(sk) { 659 | const xsk = Buffer.alloc(32); 660 | this.sodium.crypto_sign_ed25519_sk_to_curve25519(xsk, sk.getBuffer()); 661 | return xsk; 662 | } 663 | 664 | /** 665 | * @param {Ed25519PublicKey} pk 666 | * @return {Promise} 667 | */ 668 | async crypto_sign_ed25519_pk_to_curve25519(pk) { 669 | const xpk = Buffer.alloc(32); 670 | this.sodium.crypto_sign_ed25519_pk_to_curve25519(xpk, pk.getBuffer()); 671 | return xpk; 672 | } 673 | 674 | /** 675 | * @param {number} length 676 | * @param {Buffer} nonce 677 | * @param {CryptographyKey} key 678 | * @return {Promise} 679 | */ 680 | async crypto_stream(length, nonce, key) { 681 | const output = Buffer.alloc(length); 682 | this.sodium.crypto_stream( 683 | output, 684 | await Util.toBuffer(nonce), 685 | key.getBuffer() 686 | ); 687 | return output; 688 | } 689 | 690 | /** 691 | * @param {string|Buffer} plaintext 692 | * @param {Buffer} nonce 693 | * @param {CryptographyKey} key 694 | * @return {Promise} 695 | */ 696 | async crypto_stream_xor(plaintext, nonce, key) { 697 | const output = Buffer.alloc(plaintext.length); 698 | this.sodium.crypto_stream_xor( 699 | output, 700 | await Util.toBuffer(plaintext), 701 | await Util.toBuffer(nonce), 702 | key.getBuffer() 703 | ); 704 | return output; 705 | } 706 | 707 | /** 708 | * @param {number} number 709 | * @return {Promise} 710 | */ 711 | async randombytes_buf(number) { 712 | let buf = Buffer.alloc(number); 713 | this.sodium.randombytes_buf(buf); 714 | return buf; 715 | } 716 | 717 | /** 718 | * @param {number} upperBound 719 | * @return {Promise} 720 | */ 721 | async randombytes_uniform(upperBound) { 722 | return this.sodium.randombytes_uniform(upperBound); 723 | } 724 | 725 | /** 726 | * @param {Uint8Array} val 727 | * @param {Uint8Array} addv 728 | * @return {Promise} 729 | */ 730 | async sodium_add(val, addv) { 731 | const buf = await Util.cloneBuffer(val); 732 | this.sodium.sodium_add(buf, addv); 733 | return buf; 734 | } 735 | 736 | /** 737 | * @param {Buffer} input 738 | * @return {Promise} 739 | */ 740 | async sodium_bin2hex(input) { 741 | let str = "", b, c, x; 742 | for (let i = 0; i < input.length; i++) { 743 | c = input[i] & 0xf; 744 | b = input[i] >>> 4; 745 | x = 746 | ((87 + c + (((c - 10) >> 8) & ~38)) << 8) | 747 | (87 + b + (((b - 10) >> 8) & ~38)); 748 | str += String.fromCharCode(x & 0xff) + String.fromCharCode(x >>> 8); 749 | } 750 | return str; 751 | } 752 | 753 | /** 754 | * @param {Buffer} b1 755 | * @param {Buffer} b2 756 | * @return {Promise} 757 | */ 758 | async sodium_compare(b1, b2) { 759 | return this.sodium.sodium_compare(b1, b2); 760 | } 761 | 762 | /** 763 | * @param {Buffer|string} hex 764 | * @param {string|null} ignore 765 | * @return {Promise} 766 | */ 767 | async sodium_hex2bin(hex, ignore = null) { 768 | let bin_pos = 0, 769 | hex_pos = 0, 770 | c = 0, 771 | c_acc = 0, 772 | c_alpha0 = 0, 773 | c_alpha = 0, 774 | c_num0 = 0, 775 | c_num = 0, 776 | c_val = 0, 777 | state = 0; 778 | const bin = Buffer.alloc(hex.length >> 1, 0); 779 | 780 | while (hex_pos < hex.length) { 781 | c = hex.charCodeAt(hex_pos); 782 | c_num = c ^ 48; 783 | c_num0 = (c_num - 10) >> 8; 784 | c_alpha = (c & ~32) - 55; 785 | c_alpha0 = ((c_alpha - 10) ^ (c_alpha - 16)) >> 8; 786 | if ((c_num0 | c_alpha0) === 0) { 787 | if (ignore && state === 0 && ignore.indexOf(c) >= 0) { 788 | hex_pos++; 789 | continue; 790 | } 791 | break; 792 | } 793 | c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha); 794 | if (state === 0) { 795 | c_acc = c_val * 16; 796 | } else { 797 | bin[bin_pos++] = c_acc | c_val; 798 | } 799 | state = ~state; 800 | hex_pos++; 801 | } 802 | return bin; 803 | } 804 | 805 | /** 806 | * @param {Buffer} buf 807 | * @return {Promise} 808 | */ 809 | async sodium_increment(buf) { 810 | return this.sodium.sodium_increment(buf); 811 | } 812 | 813 | /** 814 | * @param {Buffer} buf 815 | * @param {number} len 816 | * @return {Promise} 817 | */ 818 | async sodium_is_zero(buf, len) { 819 | return this.sodium.sodium_is_zero(buf, len); 820 | } 821 | 822 | /** 823 | * @param {Buffer} b1 824 | * @param {Buffer} b2 825 | * @return {Promise} 826 | */ 827 | async sodium_memcmp(b1, b2) { 828 | return this.sodium.sodium_memcmp(b1, b2); 829 | } 830 | 831 | /** 832 | * @param {Buffer} buf 833 | * @return {Promise} 834 | */ 835 | async sodium_memzero(buf) { 836 | this.sodium.sodium_memzero(buf); 837 | } 838 | 839 | /** 840 | * @param {string|Buffer} buf 841 | * @param {number} blockSize 842 | * @return {Promise} 843 | */ 844 | async sodium_pad(buf, blockSize) { 845 | buf = await Util.toBuffer(buf); 846 | let length = buf.length + (buf.length % blockSize); 847 | if (length < blockSize) { 848 | length += blockSize; 849 | } 850 | const padded = Buffer.alloc(length + 100); 851 | buf.copy(padded, 0, 0); 852 | const sliceto = this.sodium.sodium_pad(padded, buf.length, blockSize); 853 | return padded.slice(0, sliceto); 854 | } 855 | 856 | /** 857 | * 858 | * @param {string|Buffer} buf 859 | * @param {number} blockSize 860 | * @return {Promise} 861 | */ 862 | async sodium_unpad(buf, blockSize) { 863 | const outlen = this.sodium.sodium_unpad(buf, buf.length, blockSize); 864 | return buf.slice(0, outlen); 865 | } 866 | }; 867 | -------------------------------------------------------------------------------- /test/sodiumplus-test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const expectError = require('./async-helper'); 3 | const fsp = require('fs').promises; 4 | const path = require('path'); 5 | const { describe, it } = require('mocha'); 6 | const { expect } = require('chai'); 7 | const { CryptographyKey, SodiumPlus, X25519PublicKey, X25519SecretKey } = require('../index'); 8 | const Util = require('../lib/util'); 9 | const VERBOSE = false; 10 | 11 | let sodium; 12 | 13 | (async () => { 14 | if (!sodium) sodium = await SodiumPlus.auto(); 15 | if (VERBOSE) { 16 | console.log({ 17 | 'libsodium-wrappers': sodium.isLibsodiumWrappers(), 18 | 'sodium-native': sodium.isSodiumNative() 19 | }); 20 | } 21 | })(); 22 | 23 | describe('SodiumPlus', () => { 24 | it('ensureLoaded', async () => { 25 | if (!sodium) sodium = await SodiumPlus.auto(); 26 | await sodium.ensureLoaded(); 27 | expect('string').to.be.equal(typeof sodium.getBackendName()); 28 | expect('boolean').to.be.equal(typeof sodium.isSodiumNative()); 29 | expect('boolean').to.be.equal(typeof sodium.isLibsodiumWrappers()); 30 | }); 31 | 32 | it('index.js', async () => { 33 | const indexFile = require('../index'); 34 | expect(typeof indexFile.getBackendObject()).to.be.equal('function'); 35 | expect(typeof indexFile.getBackendObject('SodiumNative')).to.be.equal('function'); 36 | expect(typeof indexFile.getBackendObject('LibsodiumWrappers')).to.be.equal('function'); 37 | expect(() => { 38 | indexFile.getBackendObject('Sodium') 39 | }).to.throw('Unrecognized backend type: Sodium'); 40 | }); 41 | 42 | it('SodiumPlus.CONSTANTS', async () => { 43 | if (!sodium) sodium = await SodiumPlus.auto(); 44 | let dummy = Util.populateConstants({}); 45 | for (let val in dummy) { 46 | expect(sodium.backend[val]).to.be.equals(dummy[val]); 47 | expect(sodium[val]).to.be.equals(dummy[val]); 48 | } 49 | }); 50 | 51 | it('SodiumPlus.crypto_aead_xchacha20poly1305_ietf_*', async() => { 52 | if (!sodium) sodium = await SodiumPlus.auto(); 53 | let plaintext = Buffer.from( 54 | '4c616469657320616e642047656e746c656d656e206f662074686520636c6173' + 55 | '73206f66202739393a204966204920636f756c64206f6666657220796f75206f' + 56 | '6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73' + 57 | '637265656e20776f756c642062652069742e', 58 | 'hex' 59 | ); 60 | let assocData = Buffer.from('50515253c0c1c2c3c4c5c6c7', 'hex'); 61 | let nonce = Buffer.from('404142434445464748494a4b4c4d4e4f5051525354555657', 'hex'); 62 | let key = CryptographyKey.from('808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f', 'hex'); 63 | 64 | let ciphertext = await sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, nonce, key, assocData); 65 | 66 | let expected = 'bd6d179d3e83d43b9576579493c0e939572a1700252bfaccbed2902c21396cbb' + 67 | '731c7f1b0b4aa6440bf3a82f4eda7e39ae64c6708c54c216cb96b72e1213b452' + 68 | '2f8c9ba40db5d945b11b69b982c1bb9e3f3fac2bc369488f76b2383565d3fff9' + 69 | '21f9664c97637da9768812f615c68b13b52e' + 70 | 'c0875924c1c7987947deafd8780acf49'; 71 | expect(ciphertext.toString('hex')).to.be.equals(expected); 72 | 73 | let decrypted = await sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(ciphertext, nonce, key, assocData); 74 | expect(decrypted.toString('hex')).to.be.equals(plaintext.toString('hex')); 75 | 76 | let randomKey = await sodium.crypto_aead_xchacha20poly1305_ietf_keygen(); 77 | assert(randomKey instanceof CryptographyKey); 78 | 79 | let ciphertext2 = await sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, nonce, randomKey); 80 | decrypted = await sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(ciphertext2, nonce, randomKey); 81 | expect(decrypted.toString('hex')).to.be.equals(plaintext.toString('hex')); 82 | expect(ciphertext.toString('hex')).to.not.equals(ciphertext2.toString('hex')); 83 | 84 | await expectError( 85 | sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(plaintext, nonce.slice(1), randomKey), 86 | 'Argument 2 must be 24 bytes' 87 | ); 88 | await expectError( 89 | sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(plaintext, nonce, Buffer.alloc(32)), 90 | 'Argument 3 must be an instance of CryptographyKey' 91 | ); 92 | 93 | await expectError( 94 | sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, nonce.slice(1), randomKey), 95 | 'Argument 2 must be 24 bytes' 96 | ); 97 | await expectError( 98 | sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, nonce, Buffer.alloc(32)), 99 | 'Argument 3 must be an instance of CryptographyKey' 100 | ); 101 | }); 102 | 103 | it('SodiumPlus.crypto_auth', async() => { 104 | if (!sodium) sodium = await SodiumPlus.auto(); 105 | let key = await sodium.crypto_auth_keygen(); 106 | let message = 'Science, math, technology, engineering, and compassion for others.'; 107 | let mac = await sodium.crypto_auth(message, key); 108 | assert(await sodium.crypto_auth_verify(message, key, mac) === true); 109 | 110 | await expectError( 111 | sodium.crypto_auth(message, Buffer.alloc(32)), 112 | 'Argument 2 must be an instance of CryptographyKey' 113 | ); 114 | await expectError( 115 | sodium.crypto_auth_verify(message, Buffer.alloc(32), mac), 116 | 'Argument 2 must be an instance of CryptographyKey' 117 | ); 118 | }); 119 | 120 | it('SodiumPlus.crypto_box', async() => { 121 | if (!sodium) sodium = await SodiumPlus.auto(); 122 | let plaintext = 'Science, math, technology, engineering, and compassion for others.'; 123 | 124 | let aliceKeypair = await sodium.crypto_box_keypair(); 125 | let aliceSecret = await sodium.crypto_box_secretkey(aliceKeypair); 126 | let alicePublic = await sodium.crypto_box_publickey(aliceKeypair); 127 | 128 | let bobKeypair = await sodium.crypto_box_keypair(); 129 | let bobSecret = await sodium.crypto_box_secretkey(bobKeypair); 130 | let bobPublic = await sodium.crypto_box_publickey(bobKeypair); 131 | 132 | let nonce = await sodium.randombytes_buf(24); 133 | 134 | let ciphertext = await sodium.crypto_box(plaintext, nonce, aliceSecret, bobPublic); 135 | let decrypted = await sodium.crypto_box_open(ciphertext, nonce, bobSecret, alicePublic); 136 | expect(decrypted.toString('hex')).to.be.equals(Buffer.from(plaintext).toString('hex')); 137 | 138 | let derived = await sodium.crypto_box_publickey_from_secretkey(aliceSecret); 139 | expect(alicePublic.getBuffer().toString('hex')) 140 | .to.be.equal(derived.getBuffer().toString('hex')); 141 | 142 | /* Unhappy path: */ 143 | await expectError( 144 | sodium.crypto_box(plaintext, nonce, alicePublic, bobPublic), 145 | 'Argument 3 must be an instance of X25519SecretKey' 146 | ); 147 | await expectError( 148 | sodium.crypto_box(plaintext, nonce, bobSecret, aliceSecret), 149 | 'Argument 4 must be an instance of X25519PublicKey' 150 | ); 151 | await expectError( 152 | sodium.crypto_box(plaintext, nonce.slice(1), bobSecret, alicePublic), 153 | 'Nonce must be a buffer of exactly 24 bytes' 154 | ); 155 | await expectError( 156 | sodium.crypto_box_open(ciphertext, nonce, alicePublic, bobPublic), 157 | 'Argument 3 must be an instance of X25519SecretKey' 158 | ); 159 | await expectError( 160 | sodium.crypto_box_open(ciphertext, nonce, bobSecret, aliceSecret), 161 | 'Argument 4 must be an instance of X25519PublicKey' 162 | ); 163 | await expectError( 164 | sodium.crypto_box_open(ciphertext.slice(0, 14), nonce, bobSecret, alicePublic), 165 | 'Ciphertext must be a buffer of at least 16 bytes' 166 | ); 167 | await expectError( 168 | sodium.crypto_box_open(ciphertext, nonce.slice(1), bobSecret, alicePublic), 169 | 'Nonce must be a buffer of exactly 24 bytes' 170 | ); 171 | await expectError( 172 | sodium.crypto_box_keypair_from_secretkey_and_publickey(alicePublic, alicePublic), 173 | 'Argument 1 must be an instance of X25519SecretKey' 174 | ); 175 | await expectError( 176 | sodium.crypto_box_keypair_from_secretkey_and_publickey(aliceSecret, aliceSecret), 177 | 'Argument 2 must be an instance of X25519PublicKey' 178 | ); 179 | await expectError( 180 | sodium.crypto_box_secretkey(derived), 181 | 'Keypair must be 64 bytes' 182 | ); 183 | await expectError( 184 | sodium.crypto_box_publickey(derived), 185 | 'Keypair must be 64 bytes' 186 | ); 187 | await expectError( 188 | sodium.crypto_box_publickey_from_secretkey(derived), 189 | 'Argument 1 must be an instance of X25519SecretKey' 190 | ); 191 | }); 192 | 193 | it('SodiumPlus.crypto_box_seal', async() => { 194 | if (!sodium) sodium = await SodiumPlus.auto(); 195 | let plaintext = 'Science, math, technology, engineering, and compassion for others.'; 196 | 197 | let aliceKeypair = await sodium.crypto_box_keypair(); 198 | let aliceSecret = await sodium.crypto_box_secretkey(aliceKeypair); 199 | let alicePublic = await sodium.crypto_box_publickey(aliceKeypair); 200 | assert(aliceSecret instanceof X25519SecretKey); 201 | assert(alicePublic instanceof X25519PublicKey); 202 | 203 | let ciphertext = await sodium.crypto_box_seal(plaintext, alicePublic); 204 | let decrypted = await sodium.crypto_box_seal_open(ciphertext, alicePublic, aliceSecret); 205 | expect(decrypted.toString('hex')).to.be.equals(Buffer.from(plaintext).toString('hex')); 206 | 207 | await expectError( 208 | sodium.crypto_box_seal(plaintext, aliceSecret), 209 | 'Argument 2 must be an instance of X25519PublicKey' 210 | ); 211 | await expectError( 212 | sodium.crypto_box_seal_open(plaintext, aliceSecret, aliceSecret), 213 | 'Argument 2 must be an instance of X25519PublicKey' 214 | ); 215 | await expectError( 216 | sodium.crypto_box_seal_open(plaintext, alicePublic, alicePublic), 217 | 'Argument 3 must be an instance of X25519SecretKey' 218 | ); 219 | }); 220 | 221 | it('SodiumPlus.crypto_generichash', async() => { 222 | let message = 'Science, math, technology, engineering, and compassion for others.'; 223 | let piece1 = message.slice(0, 16); 224 | let piece2 = message.slice(16); 225 | 226 | let hash1 = await sodium.crypto_generichash(message); 227 | expect(hash1.toString('hex')).to.be.equals('47c1fdbde32b30b9c54dd47cf88ba92d2d05df1265e342c9563ed56aee84ab02'); 228 | 229 | let state = await sodium.crypto_generichash_init(); 230 | await sodium.crypto_generichash_update(state, piece1); 231 | await sodium.crypto_generichash_update(state, piece2); 232 | let hash2 = await sodium.crypto_generichash_final(state); 233 | expect(hash1.toString('hex')).to.be.equals(hash2.toString('hex')); 234 | 235 | let key = await sodium.crypto_generichash_keygen(); 236 | hash1 = await sodium.crypto_generichash(message, key); 237 | state = await sodium.crypto_generichash_init(key); 238 | await sodium.crypto_generichash_update(state, piece1); 239 | await sodium.crypto_generichash_update(state, piece2); 240 | hash2 = await sodium.crypto_generichash_final(state); 241 | expect(hash1.toString('hex')).to.be.equals(hash2.toString('hex')); 242 | }); 243 | 244 | it('SodiumPlus.crypto_kdf', async function() { 245 | if (!sodium) sodium = await SodiumPlus.auto(); 246 | let subkey, expected; 247 | let key = CryptographyKey.from('808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f', 'hex'); 248 | let context = 'NaClTest'; 249 | subkey = await sodium.crypto_kdf_derive_from_key(32, 1, context, key); 250 | expected = 'bce6fcf118cac2691bb23975a63dfac02282c1cd5de6ab9febcbb0ec4348181b'; 251 | expect(subkey.toString('hex')).to.be.equals(expected); 252 | 253 | subkey = await sodium.crypto_kdf_derive_from_key(32, 2, context, key); 254 | expected = '877cf1c1a2da9b900c79464acebc3731ed4ebe326a7951911639821d09dc6dda'; 255 | expect(subkey.toString('hex')).to.be.equals(expected); 256 | 257 | let key2 = await sodium.crypto_kdf_keygen(); 258 | let subkey2 = await sodium.crypto_kdf_derive_from_key(32, 1, context, key2); 259 | expect(subkey2.toString('hex')).to.not.equals(key2.toString('hex')); 260 | expect(subkey2.toString('hex')).to.not.equals(subkey.toString('hex')); 261 | 262 | await expectError( 263 | sodium.crypto_kdf_derive_from_key(-32, 1, context, key2), 264 | 'Length must be a positive integer.' 265 | ); 266 | await expectError( 267 | sodium.crypto_kdf_derive_from_key(32, -1, context, key2), 268 | 'Key ID must be an unsigned integer' 269 | ); 270 | }); 271 | 272 | it('SodiumPlus.crypto_kx', async function() { 273 | if (!sodium) sodium = await SodiumPlus.auto(); 274 | let clientKeys = await sodium.crypto_kx_keypair(); 275 | let clientSecret = await sodium.crypto_box_secretkey(clientKeys); 276 | let clientPublic = await sodium.crypto_box_publickey(clientKeys); 277 | let seed = 'Unit test static key seed goes here. Nothing too complicated. No randomness needed, really.'; 278 | let serverKeys = await sodium.crypto_kx_seed_keypair(seed); 279 | let serverSecret = await sodium.crypto_box_secretkey(serverKeys); 280 | let serverPublic = await sodium.crypto_box_publickey(serverKeys); 281 | let clientRx, clientTx, serverRx, serverTx; 282 | 283 | [clientRx, clientTx] = await sodium.crypto_kx_client_session_keys(clientPublic, clientSecret, serverPublic); 284 | [serverRx, serverTx] = await sodium.crypto_kx_server_session_keys(serverPublic, serverSecret, clientPublic); 285 | 286 | expect(clientRx.toString('hex')).to.be.equals(serverTx.toString('hex')); 287 | expect(clientTx.toString('hex')).to.be.equals(serverRx.toString('hex')); 288 | 289 | await expectError( 290 | sodium.crypto_kx_client_session_keys(clientSecret, clientSecret, serverPublic), 291 | 'Argument 1 must be an instance of X25519PublicKey' 292 | ); 293 | await expectError( 294 | sodium.crypto_kx_client_session_keys(clientPublic, clientPublic, serverPublic), 295 | 'Argument 2 must be an instance of X25519SecretKey' 296 | ); 297 | await expectError( 298 | sodium.crypto_kx_client_session_keys(clientPublic, clientSecret, serverSecret), 299 | 'Argument 3 must be an instance of X25519PublicKey' 300 | ); 301 | 302 | await expectError( 303 | sodium.crypto_kx_server_session_keys(serverSecret, serverSecret, clientPublic), 304 | 'Argument 1 must be an instance of X25519PublicKey' 305 | ); 306 | await expectError( 307 | sodium.crypto_kx_server_session_keys(serverPublic, serverPublic, clientPublic), 308 | 'Argument 2 must be an instance of X25519SecretKey' 309 | ); 310 | await expectError( 311 | sodium.crypto_kx_server_session_keys(serverPublic, serverSecret, clientSecret), 312 | 'Argument 3 must be an instance of X25519PublicKey' 313 | ); 314 | }); 315 | 316 | it('SodiumPlus.crypto_onetimeauth', async() => { 317 | if (!sodium) sodium = await SodiumPlus.auto(); 318 | let key = await sodium.crypto_onetimeauth_keygen(); 319 | let plaintext = 'Science, math, technology, engineering, and compassion for others.'; 320 | let tag = await sodium.crypto_onetimeauth(plaintext, key); 321 | assert(await sodium.crypto_onetimeauth_verify(plaintext, key, tag)); 322 | assert((await sodium.crypto_onetimeauth_verify(plaintext + ' extra', key, tag)) === false); 323 | 324 | let msg = Buffer.alloc(32, 0); 325 | key = CryptographyKey.from('746869732069732033322d62797465206b657920666f7220506f6c7931333035', 'hex'); 326 | tag = await sodium.crypto_onetimeauth(msg, key); 327 | expect(tag.toString('hex')).to.be.equals('49ec78090e481ec6c26b33b91ccc0307'); 328 | assert(await sodium.crypto_onetimeauth_verify(msg, key, tag)); 329 | 330 | await expectError( 331 | sodium.crypto_onetimeauth(msg, Buffer.alloc(32)), 332 | 'Argument 2 must be an instance of CryptographyKey' 333 | ); 334 | 335 | await expectError( 336 | sodium.crypto_onetimeauth_verify(msg, Buffer.alloc(32), tag), 337 | 'Argument 2 must be an instance of CryptographyKey' 338 | ); 339 | }); 340 | 341 | it('SodiumPlus.crypto_scalarmult', async() => { 342 | let aliceKeypair = await sodium.crypto_box_keypair(); 343 | let aliceSecret = await sodium.crypto_box_secretkey(aliceKeypair); 344 | let alicePublic = await sodium.crypto_box_publickey(aliceKeypair); 345 | assert(aliceSecret instanceof X25519SecretKey); 346 | assert(alicePublic instanceof X25519PublicKey); 347 | 348 | // crypto_scalarmult_base test: 349 | let testPublic = await sodium.crypto_scalarmult_base(aliceSecret); 350 | expect(testPublic.getBuffer().toString('hex')).to.be.equals(alicePublic.getBuffer().toString('hex')); 351 | 352 | // crypto_scalarmult test: 353 | let bobKeypair = await sodium.crypto_box_keypair(); 354 | let bobSecret = await sodium.crypto_box_secretkey(bobKeypair); 355 | let bobPublic = await sodium.crypto_box_publickey(bobKeypair); 356 | 357 | expect(alicePublic.getBuffer().toString('hex')).to.be.equals(alicePublic.getBuffer().toString('hex')); 358 | 359 | let ab = await sodium.crypto_scalarmult(aliceSecret, bobPublic); 360 | expect(ab.toString('hex')).to.not.equals('0000000000000000000000000000000000000000000000000000000000000000'); 361 | let ba = await sodium.crypto_scalarmult(bobSecret, alicePublic); 362 | expect(ba.toString('hex')).to.not.equals('0000000000000000000000000000000000000000000000000000000000000000'); 363 | expect(ab.toString('hex')).to.be.equals(ba.toString('hex')); 364 | 365 | await expectError( 366 | sodium.crypto_scalarmult(alicePublic, bobPublic), 367 | 'Argument 1 must be an instance of X25519SecretKey' 368 | ); 369 | await expectError( 370 | sodium.crypto_scalarmult(aliceSecret, bobSecret), 371 | 'Argument 2 must be an instance of X25519PublicKey' 372 | ); 373 | await expectError( 374 | sodium.crypto_scalarmult_base(alicePublic), 375 | 'Argument 1 must be an instance of X25519SecretKey' 376 | ); 377 | }); 378 | 379 | it('SodiumPlus.crypto_secretbox', async() => { 380 | if (!sodium) sodium = await SodiumPlus.auto(); 381 | let plaintext = 'Science, math, technology, engineering, and compassion for others.'; 382 | 383 | let key = await sodium.crypto_secretbox_keygen(); 384 | let nonce = await sodium.randombytes_buf(24); 385 | 386 | let ciphertext = await sodium.crypto_secretbox(plaintext, nonce, key); 387 | let decrypted = await sodium.crypto_secretbox_open(ciphertext, nonce, key); 388 | expect(decrypted.toString('hex')).to.be.equals(Buffer.from(plaintext).toString('hex')); 389 | 390 | // Unhappy path: 391 | let ed25519key = await sodium.crypto_sign_secretkey(await sodium.crypto_sign_keypair()); 392 | await expectError( 393 | sodium.crypto_secretbox(ciphertext.slice(0, 14), nonce, ed25519key), 394 | 'Argument 3 must not be an asymmetric key' 395 | ); 396 | await expectError( 397 | sodium.crypto_secretbox(ciphertext, nonce.slice(1), key), 398 | 'Nonce must be a buffer of exactly 24 bytes' 399 | ); 400 | await expectError( 401 | sodium.crypto_secretbox_open(ciphertext.slice(0, 14), nonce, ed25519key), 402 | 'Argument 3 must not be an asymmetric key' 403 | ); 404 | await expectError( 405 | sodium.crypto_secretbox_open(ciphertext.slice(0, 14), nonce, key), 406 | 'Ciphertext must be a buffer of at least 16 bytes' 407 | ); 408 | await expectError( 409 | sodium.crypto_secretbox_open(ciphertext, nonce.slice(1), key), 410 | 'Nonce must be a buffer of exactly 24 bytes' 411 | ); 412 | }); 413 | 414 | it('SodiumPlus.crypto_secretstream_xchacha20poly1305', async() => { 415 | if (!sodium) sodium = await SodiumPlus.auto(); 416 | 417 | let key = await sodium.crypto_secretstream_xchacha20poly1305_keygen(); 418 | let encryptor, decryptor; 419 | encryptor = await sodium.crypto_secretstream_xchacha20poly1305_init_push(key); 420 | decryptor = await sodium.crypto_secretstream_xchacha20poly1305_init_pull(key, encryptor.header); 421 | 422 | await expectError( 423 | sodium.crypto_secretstream_xchacha20poly1305_init_push(Buffer.alloc(31)), 424 | 'Key must be an instance of CryptographyKey' 425 | ); 426 | await expectError( 427 | sodium.crypto_secretstream_xchacha20poly1305_init_pull(Buffer.alloc(31), encryptor.header), 428 | 'Key must be an instance of CryptographyKey' 429 | ); 430 | 431 | let invalidKey = new CryptographyKey(Buffer.alloc(31)); 432 | await expectError( 433 | sodium.crypto_secretstream_xchacha20poly1305_init_push(invalidKey), 434 | 'crypto_secretstream keys must be 32 bytes long' 435 | ); 436 | await expectError( 437 | sodium.crypto_secretstream_xchacha20poly1305_init_pull(invalidKey, encryptor.header), 438 | 'crypto_secretstream keys must be 32 bytes long' 439 | ); 440 | await expectError( 441 | sodium.crypto_secretstream_xchacha20poly1305_init_pull(key, encryptor.header.slice(1)), 442 | 'crypto_secretstream headers must be 24 bytes long' 443 | ); 444 | 445 | // Get a test input from the text file. 446 | let longText = await fsp.readFile(path.join(__dirname, 'longtext.md')); 447 | let chunk, readUntil; 448 | let ciphertext = Buffer.concat([encryptor.header]); 449 | 450 | // How big are our chunks going to be? 451 | let PUSH_CHUNK_SIZE = await sodium.randombytes_uniform(longText.length - 32) + 32; 452 | let PULL_CHUNK_SIZE = PUSH_CHUNK_SIZE + sodium.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES; 453 | 454 | // Encrypt 455 | for (let i = 0; i < longText.length; i += PUSH_CHUNK_SIZE) { 456 | readUntil = (i + PUSH_CHUNK_SIZE) > longText.length ? longText.length : i + PUSH_CHUNK_SIZE; 457 | chunk = await encryptor.push( 458 | longText.slice(i, readUntil) 459 | ); 460 | ciphertext = Buffer.concat([ciphertext, chunk]); 461 | } 462 | expect(ciphertext.slice(0, 24).toString('hex')).to.be 463 | .equals(encryptor.header.toString('hex')); 464 | 465 | // Decrypt, starting at 24 (after the header, which we already have) 466 | let decrypted = Buffer.alloc(0); 467 | for (let i = 24; i < ciphertext.length; i += PULL_CHUNK_SIZE) { 468 | readUntil = (i + PULL_CHUNK_SIZE) > ciphertext.length ? ciphertext.length : i + PULL_CHUNK_SIZE; 469 | chunk = await decryptor.pull( 470 | ciphertext.slice(i, readUntil) 471 | ); 472 | decrypted = Buffer.concat([decrypted, chunk]); 473 | } 474 | expect(decrypted.toString('hex')).to.be.equals(longText.toString('hex')); 475 | await encryptor.rekey(); 476 | }); 477 | 478 | it('SodiumPlus.crypto_shorthash', async() => { 479 | if (!sodium) sodium = await SodiumPlus.auto(); 480 | let key = CryptographyKey.from('808182838485868788898a8b8c8d8e8f', 'hex'); 481 | let message; 482 | let hash; 483 | 484 | message = 'This is short input0'; 485 | hash = await sodium.crypto_shorthash(message, key); 486 | expect(hash.toString('hex')).to.be.equals('ef589fb9ef4196b3'); 487 | 488 | message = 'This is short input1'; 489 | hash = await sodium.crypto_shorthash(message, key); 490 | expect(hash.toString('hex')).to.be.equals('5e8f01039bc53eb7'); 491 | 492 | let random = await sodium.crypto_shorthash_keygen(); 493 | expect(sodium.CRYPTO_SHORTHASH_KEYBYTES).to.be.equal(random.getLength()); 494 | }); 495 | 496 | it('SodiumPlus.crypto_sign_seed_keypair', async() => { 497 | if (!sodium) sodium = await SodiumPlus.auto(); 498 | let aliceKeypair = await sodium.crypto_sign_seed_keypair( 499 | await sodium.crypto_generichash('sodium-plus') 500 | ); 501 | let alicePublic = await sodium.crypto_sign_publickey(aliceKeypair); 502 | expect(alicePublic.getBuffer().toString('hex')).to.be.equals( 503 | '292288efba3a33275d216f2e4d9014d330f3b2852d6b767de15e43839096d6e8' 504 | ); 505 | await expectError( 506 | sodium.crypto_sign_seed_keypair(Buffer.alloc(31)), 507 | 'Seed must be 32 bytes long; got 31' 508 | ); 509 | // Should not throw: 510 | await sodium.crypto_sign_seed_keypair( 511 | new CryptographyKey(await sodium.crypto_generichash('sodium-plus')) 512 | ); 513 | }); 514 | 515 | it('SodiumPlus.crypto_sign_{secret,public}key', async() => { 516 | await expectError( 517 | sodium.crypto_sign_secretkey(new CryptographyKey(Buffer.alloc(16))), 518 | 'Keypair must be 96 bytes' 519 | ); 520 | await expectError( 521 | sodium.crypto_sign_publickey(new CryptographyKey(Buffer.alloc(16))), 522 | 'Keypair must be 96 bytes' 523 | ); 524 | }); 525 | 526 | it('SodiumPlus.crypto_sign', async() => { 527 | if (!sodium) sodium = await SodiumPlus.auto(); 528 | let aliceKeypair = await sodium.crypto_sign_keypair(); 529 | let aliceSecret = await sodium.crypto_sign_secretkey(aliceKeypair); 530 | let alicePublic = await sodium.crypto_sign_publickey(aliceKeypair); 531 | 532 | let plaintext = 'Science, math, technology, engineering, and compassion for others.'; 533 | let signed = await sodium.crypto_sign(plaintext, aliceSecret); 534 | let opened = await sodium.crypto_sign_open(signed, alicePublic); 535 | expect(signed.slice(64).toString('hex')).to.be.equals(opened.toString('hex')); 536 | expect(opened.toString()).to.be.equals(plaintext); 537 | 538 | let signature = await sodium.crypto_sign_detached(plaintext, aliceSecret); 539 | let valid = await sodium.crypto_sign_verify_detached(plaintext, alicePublic, signature); 540 | expect(valid).to.be.equals(true); 541 | let invalid = await sodium.crypto_sign_verify_detached(plaintext + ' extra', alicePublic, signature); 542 | expect(invalid).to.be.equals(false); 543 | await expectError( 544 | sodium.crypto_sign(plaintext, alicePublic), 545 | 'Argument 2 must be an instance of Ed25519SecretKey' 546 | ); 547 | await expectError( 548 | sodium.crypto_sign_open(signed, aliceSecret), 549 | 'Argument 2 must be an instance of Ed25519PublicKey' 550 | ); 551 | await expectError( 552 | sodium.crypto_sign_detached(plaintext, alicePublic), 553 | 'Argument 2 must be an instance of Ed25519SecretKey' 554 | ); 555 | await expectError( 556 | sodium.crypto_sign_verify_detached(plaintext, aliceSecret, signature), 557 | 'Argument 2 must be an instance of Ed25519PublicKey' 558 | ); 559 | }); 560 | 561 | it('SodiumPlus.crypto_sign_ed25519_to_curve25519', async function () { 562 | this.timeout(0); 563 | if (!sodium) sodium = await SodiumPlus.auto(); 564 | 565 | let aliceKeypair = CryptographyKey.from( 566 | '411a2c2227d2a799ebae0ed94417d8e8ed1ca9b0a9d5f4cd743cc52d961e94e2' + 567 | 'da49154c9e700b754199df7974e9fa4ee4b6ebbc71f89d8d8938335ea4a1409d' + 568 | 'da49154c9e700b754199df7974e9fa4ee4b6ebbc71f89d8d8938335ea4a1409d', 'hex'); 569 | let aliceSecret = await sodium.crypto_sign_secretkey(aliceKeypair); 570 | let alicePublic = await sodium.crypto_sign_publickey(aliceKeypair); 571 | 572 | let ecdhSecret = await sodium.crypto_sign_ed25519_sk_to_curve25519(aliceSecret); 573 | expect(ecdhSecret.toString('hex')).to.be 574 | .equals('60c783b8d1674b7081b72a105b55872502825d4ec638028152e085b54705ad7e'); 575 | let ecdhPublic = await sodium.crypto_sign_ed25519_pk_to_curve25519(alicePublic); 576 | expect(ecdhPublic.toString('hex')).to.be 577 | .equals('5a791d07cfb39060c8e9b641b6a915a3126cd14ddc243a9928c490c8e1f59e7c'); 578 | }); 579 | 580 | it('SodiumPlus.crypto_stream', async function () { 581 | if (!sodium) sodium = await SodiumPlus.auto(); 582 | let key = CryptographyKey.from('8000000000000000000000000000000000000000000000000000000000000000', 'hex'); 583 | let iv = Buffer.alloc(24, 0); 584 | let output = await sodium.crypto_stream(256, iv, key); 585 | let testVector = '93D88C085B8433B1FBAD2221FAD718078D96119F727D27F0547F9F3D29DE1358' + 586 | 'F3FE3D9EEACF59E894FA76E6507F567B4A0796DD00D8BFC736344A9906CB1F5D'; 587 | expect(output.slice(0, 64).toString('hex').toUpperCase()).to.be.equals(testVector); 588 | testVector = '17FD2BD86D095016D8367E0DD47D3E4A18DAE7BB24F8B5E3E9F52C4A493BE982' + 589 | 'ECA8E89A4DEC78467E31087A1ACDA83754BEFB273AB27EB396EB4957F7166C25'; 590 | expect(output.slice(192, 256).toString('hex').toUpperCase()).to.be.equals(testVector); 591 | 592 | key = CryptographyKey.from('8080808080808080808080808080808080808080808080808080808080808080', 'hex'); 593 | output = await sodium.crypto_stream_xor('Test message', iv, key); 594 | expect(output.toString('hex')).to.be.equals('1071d0355cb22c4c4e00303f'); 595 | 596 | key = await sodium.crypto_stream_keygen(); 597 | iv = await sodium.randombytes_buf(24); 598 | let plaintext = 'This is a secret message'; 599 | let ciphertext = await sodium.crypto_stream_xor(plaintext, iv, key); 600 | let decrypted = await sodium.crypto_stream_xor(ciphertext, iv, key); 601 | expect(decrypted.toString()).to.be.equals(plaintext); 602 | }); 603 | 604 | it('SodiumPlus.randombytes_buf', async() => { 605 | if (!sodium) sodium = await SodiumPlus.auto(); 606 | let a, b; 607 | for (let i = 0; i < 100; i++) { 608 | a = await sodium.randombytes_buf(64); 609 | b = await sodium.randombytes_buf(64); 610 | expect(a.toString('hex')).to.not.equals(b.toString('hex')); 611 | } 612 | }); 613 | 614 | it('SodiumPlus.randombytes_uniform', async() => { 615 | if (!sodium) sodium = await SodiumPlus.auto(); 616 | let a, b; 617 | for (let i = 0; i < 100; i++) { 618 | a = await sodium.randombytes_uniform(0x3fffffff); 619 | b = await sodium.randombytes_uniform(0x3fffffff); 620 | expect(a).to.not.equals(b); 621 | } 622 | }); 623 | 624 | it('SodiumPlus.sodium_bin2hex', async () => { 625 | if (!sodium) sodium = await SodiumPlus.auto(); 626 | let buf = await sodium.randombytes_buf(32); 627 | 628 | expect(await sodium.sodium_bin2hex(buf)).to.be.equals(buf.toString('hex')); 629 | }); 630 | 631 | it('SodiumPlus.sodium_add', async () => { 632 | if (!sodium) sodium = await SodiumPlus.auto(); 633 | let foo = Buffer.from('ed000000', 'hex'); 634 | let bar = Buffer.from('01000000', 'hex'); 635 | let baz = await sodium.sodium_add(foo, bar); 636 | expect(baz.toString('hex')).to.be.equals('ee000000'); 637 | 638 | bar = Buffer.from('ff000000', 'hex'); 639 | baz = await sodium.sodium_add(baz, bar); 640 | expect(baz.toString('hex')).to.be.equals('ed010000'); 641 | 642 | foo = Buffer.from('ffffffff', 'hex'); 643 | bar = Buffer.from('01000000', 'hex'); 644 | baz = await sodium.sodium_add(foo, bar); 645 | expect(baz.toString('hex')).to.be.equals('00000000'); 646 | bar = Buffer.from('02000000', 'hex'); 647 | baz = await sodium.sodium_add(foo, bar); 648 | expect(baz.toString('hex')).to.be.equals('01000000'); 649 | }); 650 | 651 | it('SodiumPlus.sodium_compare', async() => { 652 | if (!sodium) sodium = await SodiumPlus.auto(); 653 | let a = Buffer.from('80808080', 'hex'); 654 | let b = Buffer.from('81808080', 'hex'); 655 | let c = Buffer.from('80808081', 'hex'); 656 | 657 | expect(await sodium.sodium_compare(a, a)).to.be.equals(0); 658 | expect(await sodium.sodium_compare(b, b)).to.be.equals(0); 659 | expect(await sodium.sodium_compare(c, c)).to.be.equals(0); 660 | expect(await sodium.sodium_compare(a, b)).to.be.below(0); 661 | expect(await sodium.sodium_compare(b, a)).to.be.above(0); 662 | expect(await sodium.sodium_compare(a, c)).to.be.below(0); 663 | expect(await sodium.sodium_compare(c, a)).to.be.above(0); 664 | expect(await sodium.sodium_compare(b, c)).to.be.below(0); 665 | expect(await sodium.sodium_compare(c, b)).to.be.above(0); 666 | }); 667 | 668 | it('SodiumPlus.sodium_hex2bin', async () => { 669 | if (!sodium) sodium = await SodiumPlus.auto(); 670 | let buf = await sodium.randombytes_buf(32); 671 | let hex = buf.toString('hex'); 672 | let bin = await sodium.sodium_hex2bin(hex); 673 | expect(Buffer.isBuffer(bin)).to.be.equals(true); 674 | expect(bin.toString('base64')).to.be.equals(buf.toString('base64')); 675 | }); 676 | 677 | it('SodiumPlus.sodium_increment', async() => { 678 | if (!sodium) sodium = await SodiumPlus.auto(); 679 | let a = Buffer.from('80808080', 'hex'); 680 | let b = Buffer.from('81808080', 'hex'); 681 | await sodium.sodium_increment(a); 682 | expect(await sodium.sodium_compare(b, a)).to.be.equals(0); 683 | 684 | a = Buffer.from('ffffffff', 'hex'); 685 | b = Buffer.from('00000000', 'hex'); 686 | await sodium.sodium_increment(a); 687 | expect(await sodium.sodium_compare(b, a)).to.be.equals(0); 688 | }); 689 | it('SodiumPlus.sodium_is_zero', async() => { 690 | if (!sodium) sodium = await SodiumPlus.auto(); 691 | let buf; 692 | buf = Buffer.from('00', 'hex'); 693 | expect(await sodium.sodium_is_zero(buf, 1)).to.be.equals(true); 694 | buf = Buffer.from('01', 'hex'); 695 | expect(await sodium.sodium_is_zero(buf, 1)).to.be.equals(false); 696 | }); 697 | 698 | it('SodiumPlus.sodium_memcmp', async() => { 699 | if (!sodium) sodium = await SodiumPlus.auto(); 700 | let a, b, c; 701 | a = await sodium.randombytes_buf(32); 702 | b = await sodium.randombytes_buf(32); 703 | c = await Util.cloneBuffer(b); 704 | 705 | expect(await sodium.sodium_memcmp(a, b)).to.be.equals(false); 706 | expect(await sodium.sodium_memcmp(a, c)).to.be.equals(false); 707 | expect(await sodium.sodium_memcmp(b, c)).to.be.equals(true); 708 | expect(await sodium.sodium_memcmp(c, b)).to.be.equals(true); 709 | }); 710 | 711 | it('SodiumPlus.sodium_memzero', async() => { 712 | if (!sodium) sodium = await SodiumPlus.auto(); 713 | let buf = await sodium.randombytes_buf(16); 714 | expect(buf.toString('hex')).to.not.equals('00000000000000000000000000000000'); 715 | await sodium.sodium_memzero(buf); 716 | expect(buf.toString('hex')).to.be.equals('00000000000000000000000000000000'); 717 | }); 718 | 719 | it('SodiumPlus.sodium_pad', async() => { 720 | if (!sodium) sodium = await SodiumPlus.auto(); 721 | let buf, size, padded, unpadded; 722 | for (let i = 0; i < 100; i++) { 723 | buf = await sodium.randombytes_buf( 724 | await sodium.randombytes_uniform(96) + 16 725 | ); 726 | size = await sodium.randombytes_uniform(96) + 5; 727 | padded = await sodium.sodium_pad(buf, size); 728 | unpadded = await sodium.sodium_unpad(padded, size); 729 | expect(unpadded.toString('hex')).to.be.equals(buf.toString('hex')); 730 | } 731 | }); 732 | }); 733 | --------------------------------------------------------------------------------