Skip to content

Instantly share code, notes, and snippets.

@soatok

soatok/index.test.ts

Created Mar 12, 2021
Embed
What would you like to do?
XAES-256-GCM
import { expect } from 'chai';
import 'mocha';
import * as crypto from 'crypto';
import {xaesgcm_encrypt, xaesgcm_decrypt, KEY_BYTES, NONCE_BYTES, TAG_BYTES} from './index';
describe('XAES-GCM', () => {
it('encrypt/decrypt without AAD', async () => {
const key = crypto.randomBytes(KEY_BYTES);
const nonce = crypto.randomBytes(NONCE_BYTES);
const plaintext = Buffer.from('This is just a test message to ensure correctness.');
const [c, t] = xaesgcm_encrypt(plaintext, nonce, key);
const decrypt = xaesgcm_decrypt(c, t, nonce, key);
expect(plaintext.toString('utf-8'))
.to.be.equal(decrypt.toString('utf-8'));
});
it('encrypt/decrypt with AAD', async () => {
const key = crypto.randomBytes(KEY_BYTES);
const nonce = crypto.randomBytes(NONCE_BYTES);
const plaintext = Buffer.from('This is just a test message to ensure correctness.');
const aad = Buffer.from('soatok');
const [c, t] = xaesgcm_encrypt(plaintext, nonce, key, aad);
const decrypt = xaesgcm_decrypt(c, t, nonce, key, aad);
expect(plaintext.toString('utf-8'))
.to.be.equal(decrypt.toString('utf-8'));
expect(() =>{
// AAD omitted; throw
xaesgcm_decrypt(c, t, nonce, key);
}).to.throw('Unsupported state or unable to authenticate data');
});
});
import * as crypto from 'crypto';
export const NONCE_BYTES = 44;
export const KEY_BYTES = 32;
export const TAG_BYTES = 16;
function demand(condition: boolean, message?: string): void {
if (!condition) {
throw new Error(message ?? 'An unknown error occurred');
}
}
/**
* Returns a subkey and the remaining bytes of the nonce.
*
* @param {Buffer} key
* @param {Buffer} nonce (44 bytes)
*/
function xaesgcm_kdf(key: Buffer, nonce: Buffer): Buffer[] {
const sha512 = crypto.createHash('sha512');
sha512.update(key);
sha512.update(nonce.slice(0, 32));
return [sha512.digest().slice(0, 32), nonce.slice(32)];
}
export function xaesgcm_encrypt(
plaintext: Buffer,
nonce: Buffer,
key: Buffer,
aad?: Buffer
): Buffer[] {
demand(
key.length === KEY_BYTES,
`Key must be ${KEY_BYTES} long; ${key.length} given.`
);
demand(
nonce.length === NONCE_BYTES,
`Key must be ${NONCE_BYTES} long; ${nonce.length} given.`
);
const [subkey, remainder] = xaesgcm_kdf(key, nonce);
const aes = crypto.createCipheriv('aes-256-gcm', subkey, remainder);
if (aad) {
aes.setAAD(aad);
}
const ciphertext = Buffer.concat([
aes.update(plaintext),
aes.final()
]);
const tag = aes.getAuthTag();
return [ciphertext, tag];
}
export function xaesgcm_decrypt(
ciphertext: Buffer,
tag: Buffer,
nonce: Buffer,
key: Buffer,
aad?: Buffer
): Buffer {
demand(
tag.length === TAG_BYTES,
`Tag must be ${TAG_BYTES} long; ${tag.length} given.`
)
demand(
key.length === KEY_BYTES,
`Key must be ${KEY_BYTES} long; ${key.length} given.`
);
demand(
nonce.length === NONCE_BYTES,
`Key must be ${NONCE_BYTES} long; ${nonce.length} given.`
);
const [subkey, remainder] = xaesgcm_kdf(key, nonce);
const aes = crypto.createDecipheriv('aes-256-gcm', subkey, remainder);
aes.setAuthTag(tag);
if (aad) {
aes.setAAD(aad);
}
const plaintext = aes.update(ciphertext);
aes.final();
return plaintext;
}
{
"name": "@soatok/xaes-gcm",
"description": "Extended-Nonce construction using SHA-512 and AES-256-GCM",
"main": "index.ts",
"type": "module",
"scripts": {
"test": "mocha -r ts-node/register ./*.test.ts"
},
"dependencies": {
"@types/node": "^14.14.33",
"tslib": "^2.1.0"
},
"devDependencies": {
"@types/chai": "^4.2.15",
"@types/mocha": "^8.2.1",
"chai": "^4.3.3",
"chai-as-promised": "^7.1.1",
"mocha": "^8.3.1",
"ts-node": "^9.1.1",
"typescript": "^4.2.3"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment