You're Invited:Meet the Socket Team at BlackHat and DEF CON in Las Vegas, Aug 4-6.RSVP
Socket
Book a DemoInstallSign in
Socket

@digitalbazaar/minimal-cipher

Package Overview
Dependencies
Maintainers
5
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@digitalbazaar/minimal-cipher - npm Package Compare versions

Comparing version

to
5.1.0

lib/algorithms/c20p-browser.js

78

lib/algorithms/c20p.js
/*!
* Copyright (c) 2019-2020 Digital Bazaar, Inc. All rights reserved.
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
*/
import crypto from '../crypto.js';
import {ChaCha20Poly1305, KEY_LENGTH} from '@stablelib/chacha20poly1305';
import crypto from 'node:crypto';
import {default as webcrypto} from '../crypto.js';

@@ -17,3 +17,3 @@ export const JWE_ENC = 'C20P';

// generate content encryption key
return crypto.getRandomValues(new Uint8Array(KEY_LENGTH));
return webcrypto.getRandomValues(new Uint8Array(32));
}

@@ -40,22 +40,3 @@

}
const cipher = new ChaCha20Poly1305(cek);
// Note: Uses a random value here as a counter is not viable -- multiple
// recipients may be trying to update at the same time and use the same
// counter breaking security; using XChaCha20Poly1305 once available will
// further reduce chances of a collision as it has a 192-bit IV
const iv = crypto.getRandomValues(new Uint8Array(cipher.nonceLength));
// encrypt data
const encrypted = cipher.seal(iv, data, additionalData);
// split ciphertext and tag
const ciphertext = encrypted.subarray(0, encrypted.length - cipher.tagLength);
const tag = encrypted.subarray(encrypted.length - cipher.tagLength);
return {
ciphertext,
iv,
tag
};
return _encrypt({data, additionalData, cek});
}

@@ -91,8 +72,45 @@

// decrypt `ciphertext`
const cipher = new ChaCha20Poly1305(cek);
const encrypted = new Uint8Array(ciphertext.length + cipher.tagLength);
encrypted.set(ciphertext);
encrypted.set(tag, ciphertext.length);
return cipher.open(iv, encrypted, additionalData);
// decrypt `ciphertext` using node.js native implementation
const decipher = crypto.createDecipheriv(
'chacha20-poly1305', cek, iv, {authTagLength: 16});
decipher.setAuthTag(tag);
if(additionalData) {
decipher.setAAD(additionalData);
}
const decrypted = decipher.update(ciphertext);
const final = decipher.final();
return final.length > 0 ? Buffer.concat([decrypted, final]) : decrypted;
}
// internal function exported for reuse by XChaCha20Poly1305
export async function _encrypt({data, additionalData, cek, iv}) {
// Note: Use of a random value here as a counter is only viable for a
// limited set of messages; using XChaCha20Poly1305 instead
// probabilistically eliminates chances of a collision as it has a 192-bit IV
if(iv === undefined) {
iv = webcrypto.getRandomValues(new Uint8Array(12));
}
// encrypt `data` using node.js native implementation
const cipher = crypto.createCipheriv(
'chacha20-poly1305', cek, iv, {authTagLength: 16});
if(additionalData) {
cipher.setAAD(additionalData);
}
const encrypted = cipher.update(data);
const final = cipher.final();
const ciphertext = final.length > 0 ?
Buffer.concat([encrypted, final]) : encrypted;
const tag = cipher.getAuthTag();
return {ciphertext, iv, tag};
}
// internal function exported for reuse by XChaCha20Poly1305
export function _chacha20({key, nonce, src}) {
// use node.js implementation
const cipher = crypto.createCipheriv('chacha20', key, nonce);
const dst = cipher.update(src);
const final = cipher.final();
return final.length > 0 ? Buffer.concat([dst, final]) : dst;
}
/*!
* Copyright (c) 2019-2020 Digital Bazaar, Inc. All rights reserved.
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
*/
import crypto from '../crypto.js';
import {XChaCha20Poly1305, KEY_LENGTH} from '@stablelib/xchacha20poly1305';
import {_encrypt, decrypt as _decrypt, _chacha20} from './c20p.js';
// constants are based on the string: "expand 32-byte k"
const CHACHA20_CONSTANTS = [
0x61707865, // "expa" referred to as the "sigma" constant
0x3320646E, // "nd 3" keys used here must be 32-bytes
0x79622D32, // "2-by"
0x6B206574, // "te k"
];
const LE = true;
const NULL_DATA = new Uint8Array(64);
export const JWE_ENC = 'XC20P';

@@ -17,3 +27,3 @@

// generate content encryption key
return crypto.getRandomValues(new Uint8Array(KEY_LENGTH));
return crypto.getRandomValues(new Uint8Array(32));
}

@@ -42,21 +52,15 @@

const cipher = new XChaCha20Poly1305(cek);
// Note: Uses a random value here as a counter is not viable -- multiple
// recipients may be trying to update at the same time and use the same
// counter breaking security; using XChaCha20Poly1305 once available will
// further reduce chances of a collision as it has a 192-bit IV
const iv = crypto.getRandomValues(new Uint8Array(cipher.nonceLength));
// generate 24-byte (192-bit) XChaCha20Poly1305 IV and use it and `cek` to
// generate subkey and 12-byte (96-bit) IV for use with ChaCha20Poly1305
const nonce = crypto.getRandomValues(new Uint8Array(24));
const {subkey, iv} = await _generateSubkey({cek, nonce});
// encrypt data
const encrypted = cipher.seal(iv, data, additionalData);
// split ciphertext and tag
const ciphertext = encrypted.subarray(0, encrypted.length - cipher.tagLength);
const tag = encrypted.subarray(encrypted.length - cipher.tagLength);
return {
ciphertext,
iv,
tag
};
// run ChaCha20Poly1305
const result = await _encrypt({data, additionalData, cek: subkey, iv});
// return full XChaCha20Poly1305 nonce as IV
result.iv = nonce;
// wipe generated values
subkey.fill(0);
iv.fill(0);
return result;
}

@@ -93,8 +97,77 @@

// generate subkey and 12-byte (96-bit) IV for use with ChaCha20Poly1305 from
// `cek` and 24-byte (192-bit) XChaCha20Poly1305 IV
const {subkey, iv: newIV} = await _generateSubkey({cek, nonce: iv});
// decrypt `ciphertext`
const cipher = new XChaCha20Poly1305(cek);
const encrypted = new Uint8Array(ciphertext.length + cipher.tagLength);
encrypted.set(ciphertext);
encrypted.set(tag, ciphertext.length);
return cipher.open(iv, encrypted, additionalData);
const result = await _decrypt(
{ciphertext, iv: newIV, tag, additionalData, cek: subkey});
// wipe generated values
subkey.fill(0);
newIV.fill(0);
return result;
}
async function _generateSubkey({cek, nonce}) {
// generate subkey and 12-byte IV for ChaCha20Poly1305; first 4 bytes of
// IV are NULL bytes, last 8 are the last 8 bytes of the randomly generated
// 24-byte XChaCha20Poly1305 nonce
const subkey = await _hchacha20({key: cek, nonce: nonce.subarray(0, 16)});
const iv = new Uint8Array(12);
iv.set(nonce.subarray(16), 4);
return {subkey, iv};
}
async function _hchacha20({key, nonce}) {
/* HChaCha20's output is the first 16 bytes of internal state and last 16
bytes of ChaCha20 internal state after running its usual rounds. See:
https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha#section-2.2
ChaCha20's output is each 4 bytes of internal state (interpreted as a LE
uint32) after running the usual rounds added to the initial internal state
(again, with each 4 bytes interpreted as a LE uint32).
Therefore, we can implement HChaCha20 by:
1. Creating the ChaCha20 initial state by concatenating the ChaCha20
constants with the 32-byte key and the 16-byte nonce.
2. Running ChaCha20 with the 32-byte key, 16-byte nonce, and zero-filled
64-byte data to get 64 byte output. The zero-filled data makes the
internal XOR operations no-ops.
3. Read the first 16 bytes and last 16 bytes of ChaCha20 output as LE uint32s
and subtract each corresponding LE uint32 from the initial state.
4. Concatenate the resulting 32 bytes to produce the HChaCha20 output. */
// create initial ChaCha20 state as 16 LE uint32s (no need to convert to
// 64 bytes as the numbers will be used directly below)
const state = new Array(16);
for(let i = 0; i < 4; ++i) {
state[i] = CHACHA20_CONSTANTS[i];
}
const dvKey = new DataView(key.buffer, key.byteOffset, key.length);
for(let i = 0; i < 8; ++i) {
state[i + 4] = dvKey.getUint32(i * 4, LE);
}
const dvNonce = new DataView(nonce.buffer, nonce.byteOffset, nonce.length);
for(let i = 0; i < 4; ++i) {
state[i + 12] = dvNonce.getUint32(i * 4, LE);
}
// run ChaCha20
const dst = await _chacha20({key, nonce, src: NULL_DATA});
// generate HChaCha20 output
const out = new Uint8Array(32);
const dvOut = new DataView(out.buffer, out.byteOffset, out.length);
const dvDst = new DataView(dst.buffer, dst.byteOffset, dst.length);
for(let i = 0; i < 4; ++i) {
dvOut.setUint32(i * 4, (state[i] - dvDst.getUint32(i * 4, LE)) | 0);
}
for(let i = 0; i < 4; ++i) {
dvOut.setUint32(
i * 4 + 16, (state[i + 12] - dvDst.getUint32(i * 4 + 48, LE)) | 0);
}
return out;
}
{
"name": "@digitalbazaar/minimal-cipher",
"version": "5.0.0",
"version": "5.1.0",
"description": "Minimal encryption/decryption JWE/CWE library.",

@@ -9,2 +9,3 @@ "license": "BSD-3-Clause",

"browser": {
"./lib/algorithms/c20p.js": "./lib/algorithms/c20p-browser.js",
"./lib/algorithms/x25519-helper.js": "./lib/algorithms/x25519-helper-browser.js",

@@ -26,4 +27,4 @@ "./lib/crypto.js": "./lib/crypto-browser.js"

"dependencies": {
"@stablelib/chacha": "^1.0.1",
"@stablelib/chacha20poly1305": "^1.0.1",
"@stablelib/xchacha20poly1305": "^1.0.1",
"base58-universal": "^2.0.0",

@@ -30,0 +31,0 @@ "base64url-universal": "^2.0.0",