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');
}
/**
* Proceed with symmetric encryption of `clearText` value.
*/
async function encrypt(
iv: Buffer,
export async function encrypt(
iv: Buffer | Uint8Array,
key: Buffer,
clearText: Buffer
): Promise<Buffer> {
@ -34,10 +31,7 @@ async function encrypt(
.then(Buffer.from);
}
/**
* Proceed with symmetric decryption of `cipherText` value.
*/
async function decrypt(
export async function decrypt(
iv: Buffer,
key: Buffer,
cipherText: Buffer
@ -45,21 +39,17 @@ async function decrypt(
return subtle
.importKey('raw', key, Algorithm, false, ['decrypt'])
.then((cryptoKey) =>
subtle.encrypt({ iv, ...Algorithm }, cryptoKey, cipherText)
subtle.decrypt({ iv, ...Algorithm }, cryptoKey, cipherText)
)
.then(Buffer.from);
}
/**
* Generate a new private key for Symmetric encryption purposes.
*/
function generateKeyForSymmetricEnc(): Buffer {
export 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));
export function generateIv(): Uint8Array {
const iv = new Uint8Array(IvSize);
crypto.getRandomValues(iv);
return iv;
}

View File

@ -3,17 +3,33 @@ 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;
/**
* 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;
generateIv: () => Buffer;
/**
* Generate an Initialization Vector (iv) for for Symmetric encryption purposes.
*/
generateIv: () => Uint8Array;
}
export let symmetric: Symmetric = {} as unknown as Symmetric;
import('./browser')
.then((mod) => {
symmetric = mod as unknown as Symmetric;
symmetric = mod;
})
.catch((eBrowser) => {
import('./node')

View File

@ -1,13 +1,14 @@
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
import { IvSize, SymmetricKeySize } from './index';
import { IvSize, SymmetricKeySize, TagSize } from './index';
const Algorithm = 'aes-256-gcm';
/**
* Proceed with symmetric encryption of `clearText` value.
*/
export function encrypt(iv: Buffer, key: Buffer, clearText: Buffer): Buffer {
export async function encrypt(
iv: Buffer | Uint8Array,
key: Buffer,
clearText: Buffer
): Promise<Buffer> {
const cipher = createCipheriv(Algorithm, key, iv);
const a = cipher.update(clearText);
const b = cipher.final();
@ -15,15 +16,14 @@ export function encrypt(iv: Buffer, key: Buffer, clearText: Buffer): Buffer {
return Buffer.concat([a, b, tag]);
}
/**
* Proceed with symmetric decryption of `cipherText` value.
*/
export function decrypt(
export async function decrypt(
iv: Buffer,
tag: Buffer,
key: Buffer,
cipherText: Buffer
): Buffer {
data: 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);
decipher.setAuthTag(tag);
const a = decipher.update(cipherText);
@ -31,16 +31,10 @@ export function decrypt(
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

@ -7,7 +7,7 @@ import * as secp256k1 from 'secp256k1';
import { hexToBuf } from '../utils';
import { IvSize, symmetric, TagSize } from './symmetric';
import { IvSize, symmetric } from './symmetric';
const FlagsLength = 1;
const FlagMask = 3; // 0011
@ -146,7 +146,7 @@ export async function encryptSymmetric(
const iv = symmetric.generateIv();
// 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]);
}
@ -164,13 +164,11 @@ export async function decryptSymmetric(
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 ivStart = data.length - IvSize;
const cipher = data.slice(0, 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,
clearEncode,
decryptAsymmetric,
decryptSymmetric,
encryptAsymmetric,
encryptSymmetric,
getPublicKey,
} 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);
}
)
);
});
});