Symmetric encryption for node

This commit is contained in:
Franck Royer 2021-07-13 12:32:57 +10:00
parent 75fce5eed3
commit 06ee0ac1d8
No known key found for this signature in database
GPG Key ID: A82ED75A8DFC50A4
5 changed files with 207 additions and 2 deletions

View File

@ -0,0 +1,65 @@
import { IvSize, SymmetricKeySize } from './index';
declare global {
interface Window {
msCrypto?: Crypto;
}
interface Crypto {
webkitSubtle?: SubtleCrypto;
}
}
const crypto = window.crypto || window.msCrypto;
const subtle: SubtleCrypto = crypto.subtle || crypto.webkitSubtle;
const Algorithm = { name: 'AES-GCM', length: 128 };
if (subtle === undefined) {
throw new Error('Failed to load Subtle CryptoAPI');
}
/**
* Proceed with symmetric encryption of `clearText` value.
*/
async function encrypt(
iv: Buffer,
key: Buffer,
clearText: Buffer
): Promise<Buffer> {
return subtle
.importKey('raw', key, Algorithm, false, ['encrypt'])
.then((cryptoKey) =>
subtle.encrypt({ iv, ...Algorithm }, cryptoKey, clearText)
)
.then(Buffer.from);
}
/**
* Proceed with symmetric decryption of `cipherText` value.
*/
async function decrypt(
iv: Buffer,
key: Buffer,
cipherText: Buffer
): Promise<Buffer> {
return subtle
.importKey('raw', key, Algorithm, false, ['decrypt'])
.then((cryptoKey) =>
subtle.encrypt({ iv, ...Algorithm }, cryptoKey, cipherText)
)
.then(Buffer.from);
}
/**
* Generate a new private key for Symmetric encryption purposes.
*/
function generateKeyForSymmetricEnc(): Buffer {
return crypto.getRandomValues(Buffer.alloc(SymmetricKeySize));
}
/**
* Generate an Initialisation Vector (iv) for for Symmetric encryption purposes.
*/
function generateIv(): Buffer {
return crypto.getRandomValues(Buffer.alloc(IvSize));
}

View File

@ -0,0 +1,26 @@
export const SymmetricKeySize = 32;
export const IvSize = 12;
export const TagSize = 16;
export interface Symmetric {
encrypt: (iv: Buffer, key: Buffer, clearText: Buffer) => Buffer;
decrypt: (iv: Buffer, tag: Buffer, key: Buffer, cipherText: Buffer) => Buffer;
generateKeyForSymmetricEnc: () => Buffer;
generateIv: () => Buffer;
}
export let symmetric: Symmetric = {} as unknown as Symmetric;
import('./browser')
.then((mod) => {
symmetric = mod as unknown as Symmetric;
})
.catch((eBrowser) => {
import('./node')
.then((mod) => {
symmetric = mod;
})
.catch((eNode) => {
throw `Could not load any symmetric crypto modules: ${eBrowser}, ${eNode}`;
});
});

View File

@ -0,0 +1,46 @@
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
import { IvSize, SymmetricKeySize } from './index';
const Algorithm = 'aes-256-gcm';
/**
* Proceed with symmetric encryption of `clearText` value.
*/
export function encrypt(iv: Buffer, key: Buffer, clearText: Buffer): Buffer {
const cipher = createCipheriv(Algorithm, key, iv);
const a = cipher.update(clearText);
const b = cipher.final();
const tag = cipher.getAuthTag();
return Buffer.concat([a, b, tag]);
}
/**
* Proceed with symmetric decryption of `cipherText` value.
*/
export function decrypt(
iv: Buffer,
tag: Buffer,
key: Buffer,
cipherText: Buffer
): Buffer {
const decipher = createDecipheriv(Algorithm, key, iv);
decipher.setAuthTag(tag);
const a = decipher.update(cipherText);
const b = decipher.final();
return Buffer.concat([a, b]);
}
/**
* Generate a new private key for Symmetric encryption purposes.
*/
export function generateKeyForSymmetricEnc(): Buffer {
return randomBytes(SymmetricKeySize);
}
/**
* Generate an Initialisation Vector (iv) for for Symmetric encryption purposes.
*/
export function generateIv(): Buffer {
return randomBytes(IvSize);
}

View File

@ -5,7 +5,9 @@ import {
clearDecode,
clearEncode,
decryptAsymmetric,
decryptSymmetric,
encryptAsymmetric,
encryptSymmetric,
getPublicKey,
} from './version_1';
@ -54,4 +56,19 @@ describe('Waku Message Version 1', function () {
)
);
});
it('Symmetric encrypt & Decrypt', async function () {
await fc.assert(
fc.asyncProperty(
fc.uint8Array(),
fc.uint8Array({ minLength: 32, maxLength: 32 }),
async (message, key) => {
const enc = await encryptSymmetric(message, key);
const res = await decryptSymmetric(enc, key);
expect(res).deep.equal(message);
}
)
);
});
});

View File

@ -7,6 +7,8 @@ import * as secp256k1 from 'secp256k1';
import { hexToBuf } from '../utils';
import { IvSize, symmetric, TagSize } from './symmetric';
const FlagsLength = 1;
const FlagMask = 3; // 0011
const IsSignedMask = 4; // 0100
@ -102,7 +104,7 @@ export function clearDecode(
}
/**
* Proceed with Asymmetric encryption of the data as per [26/WAKU-PAYLOAD](rfc.vac.dev/spec/26/).
* Proceed with Asymmetric encryption of the data as per [26/WAKU-PAYLOAD](https://rfc.vac.dev/spec/26/).
* The data MUST be flags | payload-length | payload | [signature].
* The returned result can be set to `WakuMessage.payload`.
*
@ -115,6 +117,12 @@ export async function encryptAsymmetric(
return ecies.encrypt(hexToBuf(publicKey), Buffer.from(data));
}
/**
* Proceed with Asymmetric decryption of the data as per [26/WAKU-PAYLOAD](https://rfc.vac.dev/spec/26/).
* The return data is expect to be flags | payload-length | payload | [signature].
*
* @internal
*/
export async function decryptAsymmetric(
payload: Uint8Array | Buffer,
privKey: Uint8Array | Buffer
@ -122,6 +130,49 @@ export async function decryptAsymmetric(
return ecies.decrypt(Buffer.from(privKey), Buffer.from(payload));
}
/**
* Proceed with Symmetric encryption of the data as per [26/WAKU-PAYLOAD](https://rfc.vac.dev/spec/26/).
*
* @param data The data to encrypt, expected to be `flags | payload-length | payload | [signature]`.
* @param key The key to use for encryption.
* @returns The decrypted data, `cipherText | tag | iv` and can be set to `WakuMessage.payload`.
*
* @internal
*/
export async function encryptSymmetric(
data: Uint8Array | Buffer,
key: Uint8Array | Buffer | string
): Promise<Uint8Array> {
const iv = symmetric.generateIv();
// Returns `cipher | tag`
const cipher = symmetric.encrypt(iv, hexToBuf(key), Buffer.from(data));
return Buffer.concat([cipher, iv]);
}
/**
* Proceed with Symmetric decryption of the data as per [26/WAKU-PAYLOAD](https://rfc.vac.dev/spec/26/).
*
* @param payload The cipher data, it is expected to be `cipherText | tag | iv`.
* @param key The key to use for decryption.
* @returns The decrypted data, expected to be `flags | payload-length | payload | [signature]`.
*
* @internal
*/
export async function decryptSymmetric(
payload: Uint8Array | Buffer,
key: Uint8Array | Buffer | string
): Promise<Uint8Array> {
const data = Buffer.from(payload);
const ivStart = payload.length - IvSize;
const tagStart = ivStart - TagSize;
const cipher = data.slice(0, tagStart);
const tag = data.slice(tagStart, ivStart);
const iv = data.slice(ivStart);
return symmetric.decrypt(iv, tag, hexToBuf(key), cipher);
}
/**
* Generate a new private key
*/
@ -137,7 +188,7 @@ export function getPublicKey(privateKey: Uint8Array | Buffer): Uint8Array {
}
/**
* Computes the flags & auxiliary-field as per [26/WAKU-PAYLOAD](rfc.vac.dev/spec/26/).
* Computes the flags & auxiliary-field as per [26/WAKU-PAYLOAD](https://rfc.vac.dev/spec/26/).
*/
function addPayloadSizeField(msg: Buffer, payload: Uint8Array): Buffer {
const fieldSize = getSizeOfPayloadSizeField(payload);