Skip to content

Commit

Permalink
Fix symmetric encryption internal types (#726)
Browse files Browse the repository at this point in the history
  • Loading branch information
kigawas authored Jul 28, 2023
1 parent bb3fc61 commit dd0f131
Show file tree
Hide file tree
Showing 5 changed files with 392 additions and 332 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@

# Changelog

## 0.4.1 ~ 0.4.2
## 0.4.1 ~ 0.4.3

- Fix symmetric encryption internal types
- Add XChacha20 as an optional encryption backend
- Add configuration for more compatibility
- Bump dependencies
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"type": "git",
"url": "https://github.com/ecies/js.git"
},
"version": "0.4.2",
"version": "0.4.3",
"engines": {
"node": ">=16.0.0"
},
Expand Down
84 changes: 56 additions & 28 deletions src/utils/symmetric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,85 +8,113 @@ import { createCipheriv, createDecipheriv } from "crypto";
import { NonceLength, symmetricAlgorithm, symmetricNonceLength } from "../config";
import { AEAD_TAG_LENGTH, XCHACHA20_NONCE_LENGTH } from "../consts";

function _aesEncrypt(key: Buffer, plainText: Buffer, nonceLength: NonceLength): Buffer {
function _aesEncrypt(
key: Uint8Array,
plainText: Uint8Array,
nonceLength: NonceLength
): Uint8Array {
const nonce = randomBytes(nonceLength);
const cipher = createCipheriv("aes-256-gcm", key, nonce);
const encrypted = Buffer.concat([cipher.update(plainText), cipher.final()]);

const updated = cipher.update(plainText);
const finalized = cipher.final();
const tag = cipher.getAuthTag();
return Buffer.concat([nonce, tag, encrypted]);

const payload = new Uint8Array(
nonce.length + tag.length + updated.length + finalized.length
);
payload.set(nonce);
payload.set(tag, nonce.length);
payload.set(updated, nonce.length + tag.length);
payload.set(finalized, nonce.length + tag.length + updated.length);
return payload;
}

function _aesDecrypt(
key: Buffer,
cipherText: Buffer,
key: Uint8Array,
cipherText: Uint8Array,
nonceLength: NonceLength
): Buffer {
): Uint8Array {
const nonceTagLength = nonceLength + AEAD_TAG_LENGTH;
const nonce = cipherText.subarray(0, nonceLength);
const tag = cipherText.subarray(nonceLength, nonceTagLength);
const ciphered = cipherText.subarray(nonceTagLength);
const decipher = createDecipheriv("aes-256-gcm", key, nonce);
decipher.setAuthTag(tag);
return Buffer.concat([decipher.update(ciphered), decipher.final()]);

const updated = decipher.update(ciphered);
const finalized = decipher.final();

const payload = new Uint8Array(updated.length + finalized.length);
payload.set(updated);
payload.set(finalized, updated.length);
return payload;
}

function _encrypt(
func: (key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array) => Cipher,
key: Buffer,
plainText: Buffer,
key: Uint8Array,
plainText: Uint8Array,
nonceLength: number
) {
): Uint8Array {
const nonce = randomBytes(nonceLength);
const cipher = func(key, nonce);
const ciphered = cipher.encrypt(plainText);
const ciphered = cipher.encrypt(plainText); // TAG + encrypted

const encrypted = ciphered.subarray(0, ciphered.length - AEAD_TAG_LENGTH);
const tag = ciphered.subarray(-AEAD_TAG_LENGTH);
return Buffer.concat([nonce, tag, encrypted]);

const payload = new Uint8Array(nonce.length + tag.length + encrypted.length);
payload.set(nonce);
payload.set(tag, nonce.length);
payload.set(encrypted, nonce.length + tag.length);
return payload;
}

function _decrypt(
func: (key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array) => Cipher,
key: Buffer,
cipherText: Buffer,
key: Uint8Array,
cipherText: Uint8Array,
nonceLength: number
) {
): Uint8Array {
const nonceTagLength = nonceLength + AEAD_TAG_LENGTH;
const nonce = cipherText.subarray(0, nonceLength);
const tag = cipherText.subarray(nonceLength, nonceTagLength);
const ciphered = cipherText.subarray(nonceTagLength);
const encrypted = cipherText.subarray(nonceTagLength);

const decipher = func(key, Uint8Array.from(nonce)); // to reset byteOffset

const decipher = func(key, nonce);
const res = new Uint8Array(AEAD_TAG_LENGTH + ciphered.length);
res.set(ciphered);
res.set(tag, ciphered.length);
return Buffer.from(decipher.decrypt(res));
const ciphered = new Uint8Array(encrypted.length + AEAD_TAG_LENGTH);
ciphered.set(encrypted);
ciphered.set(tag, encrypted.length);
return decipher.decrypt(ciphered);
}

export function aesEncrypt(key: Buffer, plainText: Buffer): Buffer {
export function aesEncrypt(key: Uint8Array, plainText: Uint8Array): Buffer {
// TODO: Rename to symEncrypt
const algorithm = symmetricAlgorithm();
if (algorithm === "aes-256-gcm") {
return _aesEncrypt(key, plainText, symmetricNonceLength());
return Buffer.from(_aesEncrypt(key, plainText, symmetricNonceLength()));
} else if (algorithm === "xchacha20") {
return _encrypt(xchacha20, key, plainText, XCHACHA20_NONCE_LENGTH);
return Buffer.from(_encrypt(xchacha20, key, plainText, XCHACHA20_NONCE_LENGTH));
} else {
throw new Error("Not implemented");
}
}

export function aesDecrypt(key: Buffer, cipherText: Buffer): Buffer {
export function aesDecrypt(key: Uint8Array, cipherText: Uint8Array): Buffer {
// TODO: Rename to symDecrypt
const algorithm = symmetricAlgorithm();
if (algorithm === "aes-256-gcm") {
return _aesDecrypt(key, cipherText, symmetricNonceLength());
return Buffer.from(_aesDecrypt(key, cipherText, symmetricNonceLength()));
} else if (algorithm === "xchacha20") {
return _decrypt(xchacha20, key, cipherText, XCHACHA20_NONCE_LENGTH);
return Buffer.from(_decrypt(xchacha20, key, cipherText, XCHACHA20_NONCE_LENGTH));
} else {
throw new Error("Not implemented");
}
}

export function deriveKey(master: Buffer) {
export function deriveKey(master: Uint8Array): Buffer {
// 32 bytes shared secret for aes and chacha20
return Buffer.from(hkdf(sha256, master, undefined, undefined, 32));
}
55 changes: 46 additions & 9 deletions tests/crypt.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ECIES_CONFIG } from "../src/config";
import { decrypt, encrypt } from "../src/index";
import { PrivateKey, PublicKey } from "../src/keys";
import { decodeHex } from "../src/utils";

const TEXT = "helloworld🌍";

Expand All @@ -19,19 +20,13 @@ function checkHex(sk: PrivateKey) {
}

describe("test encrypt and decrypt", () => {
it("tests encrypt/decrypt buffer", () => {
it("tests encrypt/decrypt", () => {
const prv1 = new PrivateKey();
check(prv1);

const prv2 = new PrivateKey();
check(prv2, true);
});

it("tests encrypt/decrypt hex", () => {
const prv1 = new PrivateKey();
checkHex(prv1);

const prv2 = new PrivateKey();
check(prv2, true);
checkHex(prv2);
});

Expand All @@ -46,12 +41,54 @@ describe("test encrypt and decrypt", () => {
expect(decrypt(sk.toHex(), enc).toString()).toBe(TEXT);
});

it("tests ephemeral key config", () => {
it("tests config can be changed", () => {
ECIES_CONFIG.isEphemeralKeyCompressed = true;
ECIES_CONFIG.isHkdfKeyCompressed = true;

const prv1 = new PrivateKey();
check(prv1);

const prv2 = new PrivateKey();
checkHex(prv2);

ECIES_CONFIG.isEphemeralKeyCompressed = false;
ECIES_CONFIG.isHkdfKeyCompressed = false;
});

it("tests encrypt/decrypt chacha", () => {
ECIES_CONFIG.symmetricAlgorithm = "xchacha20";
ECIES_CONFIG.isEphemeralKeyCompressed = true;

const prv1 = new PrivateKey();
check(prv1);

const prv2 = new PrivateKey();
checkHex(prv2);

ECIES_CONFIG.symmetricAlgorithm = "aes-256-gcm";
ECIES_CONFIG.isEphemeralKeyCompressed = false;
});

it("tests known sk pk chacha", () => {
ECIES_CONFIG.symmetricAlgorithm = "xchacha20";

const sk = PrivateKey.fromHex(
"0000000000000000000000000000000000000000000000000000000000000002"
);
const pk = PublicKey.fromHex(
"02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"
);
const enc = encrypt(pk.toHex(), Buffer.from(TEXT));
expect(decrypt(sk.toHex(), enc).toString()).toBe(TEXT);

const known_enc = decodeHex(
"0x04e314abc14398e07974cd50221b682ed5f0629e977345fc03e2047208ee6e279f" +
"fb2a6942878d3798c968d89e59c999e082b0598d1b641968c48c8d47c570210d0a" +
"b1ade95eeca1080c45366562f9983faa423ee3fd3260757053d5843c5f453e1ee6" +
"bb955c8e5d4aee8572139357a091909357a8931b"
);
expect(decrypt(sk.toHex(), known_enc).toString()).toBe(TEXT);

ECIES_CONFIG.symmetricAlgorithm = "aes-256-gcm";
});
});
Loading

0 comments on commit dd0f131

Please sign in to comment.