Implement and test symmetric encryption in the browser

This commit is contained in:
Franck Royer 2021-07-14 16:31:11 +10:00
parent 8c66022a18
commit 57f65267c2
No known key found for this signature in database
GPG Key ID: A82ED75A8DFC50A4
5 changed files with 63 additions and 48 deletions

View File

@ -18,11 +18,8 @@ if (subtle === undefined) {
throw new Error('Failed to load Subtle CryptoAPI'); throw new Error('Failed to load Subtle CryptoAPI');
} }
/** export async function encrypt(
* Proceed with symmetric encryption of `clearText` value. iv: Buffer | Uint8Array,
*/
async function encrypt(
iv: Buffer,
key: Buffer, key: Buffer,
clearText: Buffer clearText: Buffer
): Promise<Buffer> { ): Promise<Buffer> {
@ -34,10 +31,7 @@ async function encrypt(
.then(Buffer.from); .then(Buffer.from);
} }
/** export async function decrypt(
* Proceed with symmetric decryption of `cipherText` value.
*/
async function decrypt(
iv: Buffer, iv: Buffer,
key: Buffer, key: Buffer,
cipherText: Buffer cipherText: Buffer
@ -45,21 +39,17 @@ async function decrypt(
return subtle return subtle
.importKey('raw', key, Algorithm, false, ['decrypt']) .importKey('raw', key, Algorithm, false, ['decrypt'])
.then((cryptoKey) => .then((cryptoKey) =>
subtle.encrypt({ iv, ...Algorithm }, cryptoKey, cipherText) subtle.decrypt({ iv, ...Algorithm }, cryptoKey, cipherText)
) )
.then(Buffer.from); .then(Buffer.from);
} }
/** export function generateKeyForSymmetricEnc(): Buffer {
* Generate a new private key for Symmetric encryption purposes.
*/
function generateKeyForSymmetricEnc(): Buffer {
return crypto.getRandomValues(Buffer.alloc(SymmetricKeySize)); return crypto.getRandomValues(Buffer.alloc(SymmetricKeySize));
} }
/** export function generateIv(): Uint8Array {
* Generate an Initialisation Vector (iv) for for Symmetric encryption purposes. const iv = new Uint8Array(IvSize);
*/ crypto.getRandomValues(iv);
function generateIv(): Buffer { return iv;
return crypto.getRandomValues(Buffer.alloc(IvSize));
} }

View File

@ -3,17 +3,33 @@ export const IvSize = 12;
export const TagSize = 16; export const TagSize = 16;
export interface Symmetric { export interface Symmetric {
encrypt: (iv: Buffer, key: Buffer, clearText: Buffer) => Buffer; /**
decrypt: (iv: Buffer, tag: Buffer, key: Buffer, cipherText: Buffer) => Buffer; * Proceed with symmetric encryption of `clearText` value.
*/
encrypt: (
iv: Buffer | Uint8Array,
key: Buffer,
clearText: Buffer
) => Promise<Buffer>;
/**
* Proceed with symmetric decryption of `cipherText` value.
*/
decrypt: (iv: Buffer, key: Buffer, cipherText: Buffer) => Promise<Buffer>;
/**
* Generate a new private key for Symmetric encryption purposes.
*/
generateKeyForSymmetricEnc: () => Buffer; generateKeyForSymmetricEnc: () => Buffer;
generateIv: () => Buffer; /**
* Generate an Initialization Vector (iv) for for Symmetric encryption purposes.
*/
generateIv: () => Uint8Array;
} }
export let symmetric: Symmetric = {} as unknown as Symmetric; export let symmetric: Symmetric = {} as unknown as Symmetric;
import('./browser') import('./browser')
.then((mod) => { .then((mod) => {
symmetric = mod as unknown as Symmetric; symmetric = mod;
}) })
.catch((eBrowser) => { .catch((eBrowser) => {
import('./node') import('./node')

View File

@ -1,13 +1,14 @@
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto'; import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
import { IvSize, SymmetricKeySize } from './index'; import { IvSize, SymmetricKeySize, TagSize } from './index';
const Algorithm = 'aes-256-gcm'; const Algorithm = 'aes-256-gcm';
/** export async function encrypt(
* Proceed with symmetric encryption of `clearText` value. iv: Buffer | Uint8Array,
*/ key: Buffer,
export function encrypt(iv: Buffer, key: Buffer, clearText: Buffer): Buffer { clearText: Buffer
): Promise<Buffer> {
const cipher = createCipheriv(Algorithm, key, iv); const cipher = createCipheriv(Algorithm, key, iv);
const a = cipher.update(clearText); const a = cipher.update(clearText);
const b = cipher.final(); const b = cipher.final();
@ -15,15 +16,14 @@ export function encrypt(iv: Buffer, key: Buffer, clearText: Buffer): Buffer {
return Buffer.concat([a, b, tag]); return Buffer.concat([a, b, tag]);
} }
/** export async function decrypt(
* Proceed with symmetric decryption of `cipherText` value.
*/
export function decrypt(
iv: Buffer, iv: Buffer,
tag: Buffer,
key: Buffer, key: Buffer,
cipherText: Buffer data: Buffer
): Buffer { ): Promise<Buffer> {
const tagStart = data.length - TagSize;
const cipherText = data.slice(0, tagStart);
const tag = data.slice(tagStart);
const decipher = createDecipheriv(Algorithm, key, iv); const decipher = createDecipheriv(Algorithm, key, iv);
decipher.setAuthTag(tag); decipher.setAuthTag(tag);
const a = decipher.update(cipherText); const a = decipher.update(cipherText);
@ -31,16 +31,10 @@ export function decrypt(
return Buffer.concat([a, b]); return Buffer.concat([a, b]);
} }
/**
* Generate a new private key for Symmetric encryption purposes.
*/
export function generateKeyForSymmetricEnc(): Buffer { export function generateKeyForSymmetricEnc(): Buffer {
return randomBytes(SymmetricKeySize); return randomBytes(SymmetricKeySize);
} }
/**
* Generate an Initialisation Vector (iv) for for Symmetric encryption purposes.
*/
export function generateIv(): Buffer { export function generateIv(): Buffer {
return randomBytes(IvSize); return randomBytes(IvSize);
} }

View File

@ -7,7 +7,7 @@ import * as secp256k1 from 'secp256k1';
import { hexToBuf } from '../utils'; import { hexToBuf } from '../utils';
import { IvSize, symmetric, TagSize } from './symmetric'; import { IvSize, symmetric } from './symmetric';
const FlagsLength = 1; const FlagsLength = 1;
const FlagMask = 3; // 0011 const FlagMask = 3; // 0011
@ -146,7 +146,7 @@ export async function encryptSymmetric(
const iv = symmetric.generateIv(); const iv = symmetric.generateIv();
// Returns `cipher | tag` // Returns `cipher | tag`
const cipher = symmetric.encrypt(iv, hexToBuf(key), Buffer.from(data)); const cipher = await symmetric.encrypt(iv, hexToBuf(key), Buffer.from(data));
return Buffer.concat([cipher, iv]); return Buffer.concat([cipher, iv]);
} }
@ -164,13 +164,11 @@ export async function decryptSymmetric(
key: Uint8Array | Buffer | string key: Uint8Array | Buffer | string
): Promise<Uint8Array> { ): Promise<Uint8Array> {
const data = Buffer.from(payload); const data = Buffer.from(payload);
const ivStart = payload.length - IvSize; const ivStart = data.length - IvSize;
const tagStart = ivStart - TagSize; const cipher = data.slice(0, ivStart);
const cipher = data.slice(0, tagStart);
const tag = data.slice(tagStart, ivStart);
const iv = data.slice(ivStart); const iv = data.slice(ivStart);
return symmetric.decrypt(iv, tag, hexToBuf(key), cipher); return symmetric.decrypt(iv, hexToBuf(key), cipher);
} }
/** /**

View File

@ -5,7 +5,9 @@ import {
clearDecode, clearDecode,
clearEncode, clearEncode,
decryptAsymmetric, decryptAsymmetric,
decryptSymmetric,
encryptAsymmetric, encryptAsymmetric,
encryptSymmetric,
getPublicKey, getPublicKey,
} from '../../lib/waku_message/version_1'; } from '../../lib/waku_message/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);
}
)
);
});
}); });