Skip to content

Commit

Permalink
Add Kyber-x25519 (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
larabr committed Nov 1, 2023
1 parent a71aa39 commit 388057e
Show file tree
Hide file tree
Showing 12 changed files with 292 additions and 31 deletions.
40 changes: 40 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@
},
"devDependencies": {
"@openpgp/asmcrypto.js": "^3.0.0",
"@openpgp/noble-curves": "^1.2.1-0",
"@openpgp/jsdoc": "^3.6.11",
"@openpgp/noble-curves": "^1.2.1-0",
"@openpgp/noble-hashes": "^1.3.3-0",
"@openpgp/seek-bzip": "^1.0.5-git",
"@openpgp/tweetnacl": "^1.0.4-1",
Expand All @@ -81,6 +81,7 @@
"bn.js": "^4.11.8",
"chai": "^4.3.7",
"chai-as-promised": "^7.1.1",
"crystals-kyber-js": "^1.0.0",
"eslint": "^8.34.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-base": "^15.0.0",
Expand Down
2 changes: 1 addition & 1 deletion rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const wasmOptions = {

const getChunkFileName = (chunkInfo, extension) => {
// index files result in chunks named simply 'index', so we rename them to include the package name
if (chunkInfo.name === 'index') {
if (chunkInfo.name === 'index' && chunkInfo.facadeModuleId) {
const packageName = chunkInfo.facadeModuleId.split('/').at(-2); // assume index file is under the root folder
return `${packageName}.${extension}`;
}
Expand Down
54 changes: 50 additions & 4 deletions src/crypto/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import util from '../util';
import OID from '../type/oid';
import { UnsupportedError } from '../packet/packet';
import ECDHXSymmetricKey from '../type/ecdh_x_symkey';
import * as aesKW from './aes_kw';

/**
* Encrypts data using specified algorithm and public key parameters.
Expand Down Expand Up @@ -96,6 +97,15 @@ export async function publicKeyEncrypt(keyAlgo, symmetricAlgo, publicParams, pri
const c = await modeInstance.encrypt(data, iv, new Uint8Array());
return { aeadMode: new AEADEnum(aeadMode), iv, c: new ShortByteString(c) };
}
case enums.publicKey.kem_x25519: {
const { eccPublicKey, mlkemPublicKey } = publicParams;
const { eccKeyShare, eccCipherText } = await publicKey.pqc.kem.ecdhX.encaps(keyAlgo, eccPublicKey);
const { mlkemKeyShare, mlkemCipherText } = await publicKey.pqc.kem.ml.encaps(keyAlgo, mlkemPublicKey);
const fixedInfo = new Uint8Array([keyAlgo]);
const kek = await publicKey.pqc.kem.multiKeyCombine(eccKeyShare, eccCipherText, mlkemKeyShare, mlkemCipherText, fixedInfo, 256);
const C = aesKW.wrap(kek, data);
return { eccCipherText, mlkemCipherText, C: new ShortByteString(C) }; // eccCipherText || mlkemCipherText || len(C) || C
}
default:
return [];
}
Expand All @@ -115,8 +125,8 @@ export async function publicKeyEncrypt(keyAlgo, symmetricAlgo, publicParams, pri
* @throws {Error} on sensitive decryption error, unless `randomPayload` is given
* @async
*/
export async function publicKeyDecrypt(algo, publicKeyParams, privateKeyParams, sessionKeyParams, fingerprint, randomPayload) {
switch (algo) {
export async function publicKeyDecrypt(keyAlgo, publicKeyParams, privateKeyParams, sessionKeyParams, fingerprint, randomPayload) {
switch (keyAlgo) {
case enums.publicKey.rsaEncryptSign:
case enums.publicKey.rsaEncrypt: {
const { c } = sessionKeyParams;
Expand Down Expand Up @@ -146,7 +156,7 @@ export async function publicKeyDecrypt(algo, publicKeyParams, privateKeyParams,
throw new Error('AES session key expected');
}
return publicKey.elliptic.ecdhX.decrypt(
algo, ephemeralPublicKey, C.wrappedKey, A, k);
keyAlgo, ephemeralPublicKey, C.wrappedKey, A, k);
}
case enums.publicKey.aead: {
const { cipher: algo } = publicKeyParams;
Expand All @@ -159,6 +169,17 @@ export async function publicKeyDecrypt(algo, publicKeyParams, privateKeyParams,
const modeInstance = await mode(algoValue, keyMaterial);
return modeInstance.decrypt(c.data, iv, new Uint8Array());
}
case enums.publicKey.kem_x25519: {
const { eccSecretKey, mlkemSecretKey } = privateKeyParams;
const { eccPublicKey } = publicKeyParams;
const { eccCipherText, mlkemCipherText, C } = sessionKeyParams;
const eccKeyShare = await publicKey.pqc.kem.ecdhX.decaps(keyAlgo, eccCipherText, eccSecretKey, eccPublicKey);
const mlkemKeyShare = await publicKey.pqc.kem.ml.decaps(keyAlgo, mlkemCipherText, mlkemSecretKey);
const fixedInfo = new Uint8Array([keyAlgo]);
const kek = await publicKey.pqc.kem.multiKeyCombine(eccKeyShare, eccCipherText, mlkemKeyShare, mlkemCipherText, fixedInfo, 256);
const sessionKey = aesKW.unwrap(kek, C.data);
return sessionKey;
}
default:
throw new Error('Unknown public key encryption algorithm.');
}
Expand Down Expand Up @@ -227,6 +248,11 @@ export function parsePublicKeyParams(algo, bytes) {
const digest = bytes.subarray(read, read + digestLength); read += digestLength;
return { read: read, publicParams: { cipher: algo, digest } };
}
case enums.publicKey.kem_x25519: {
const eccPublicKey = util.readExactSubarray(bytes, read, read + getCurvePayloadSize(enums.publicKey.x25519)); read += eccPublicKey.length;
const mlkemPublicKey = util.readExactSubarray(bytes, read, read + (1184 / 8)); read += mlkemPublicKey.length;
return { read, publicParams: { eccPublicKey, mlkemPublicKey } };
}
default:
throw new UnsupportedError('Unknown public key encryption algorithm.');
}
Expand Down Expand Up @@ -295,6 +321,11 @@ export function parsePrivateKeyParams(algo, bytes, publicParams) {
const keyMaterial = bytes.subarray(read, read + keySize); read += keySize;
return { read, privateParams: { hashSeed, keyMaterial } };
}
case enums.publicKey.kem_x25519: {
const eccSecretKey = util.readExactSubarray(bytes, read, read + getCurvePayloadSize(enums.publicKey.x25519)); read += eccSecretKey.length;
const mlkemSecretKey = util.readExactSubarray(bytes, read, read + (2400 / 8)); read += mlkemSecretKey.length;
return { read, privateParams: { eccSecretKey, mlkemSecretKey } };
}
default:
throw new UnsupportedError('Unknown public key encryption algorithm.');
}
Expand Down Expand Up @@ -358,6 +389,12 @@ export function parseEncSessionKeyParams(algo, bytes) {

return { aeadMode, iv, c };
}
case enums.publicKey.kem_x25519: {
const eccCipherText = util.readExactSubarray(bytes, read, read + getCurvePayloadSize(enums.publicKey.x25519)); read += eccCipherText.length;
const mlkemCipherText = util.readExactSubarray(bytes, read, read + (1088 / 8)); read += mlkemCipherText.length;
const C = new ShortByteString(); read += C.read(bytes.subarray(read));
return { eccCipherText, mlkemCipherText, C }; // eccCipherText || mlkemCipherText || len(C) || C
}
default:
throw new UnsupportedError('Unknown public key encryption algorithm.');
}
Expand All @@ -377,7 +414,8 @@ export function serializeParams(algo, params) {
enums.publicKey.ed448,
enums.publicKey.x448,
enums.publicKey.aead,
enums.publicKey.hmac
enums.publicKey.hmac,
enums.publicKey.kem_x25519
]);
const orderedParams = Object.keys(params).map(name => {
const param = params[name];
Expand Down Expand Up @@ -444,6 +482,14 @@ export async function generateParams(algo, bits, oid, symmetric) {
const keyMaterial = generateSessionKey(symmetric);
return createSymmetricParams(keyMaterial, new SymAlgoEnum(symmetric));
}
case enums.publicKey.kem_x25519: {
const eccKeyPair = await publicKey.elliptic.ecdhX.generate(enums.publicKey.x25519); // todo move into kem_echd_x?
const mlkemKeyPair = await publicKey.pqc.kem.ml.generate(algo);
return {
privateParams: { eccSecretKey: eccKeyPair.k, mlkemSecretKey: mlkemKeyPair.decapsulationKey },
publicParams: { eccPublicKey: eccKeyPair.A, mlkemPublicKey: mlkemKeyPair.encapsulationKey }
};
}
case enums.publicKey.dsa:
case enums.publicKey.elgamal:
throw new Error('Unsupported algorithm for key generation.');
Expand Down
42 changes: 35 additions & 7 deletions src/crypto/public_key/elliptic/ecdh_x.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,11 @@ export async function validateParams(algo, A, k) {
* @async
*/
export async function encrypt(algo, data, recipientA) {
const { ephemeralPublicKey } = await generateEphemeralKeyPair(algo);
const sharedSecret = await getSharedSecret(algo, recipientA);

switch (algo) {
case enums.publicKey.x25519: {
const ephemeralSecretKey = getRandomBytes(32);
const sharedSecret = x25519.scalarMult(ephemeralSecretKey, recipientA);
const { publicKey: ephemeralPublicKey } = x25519.box.keyPair.fromSecretKey(ephemeralSecretKey);
const hkdfInput = util.concatUint8Array([
ephemeralPublicKey,
recipientA,
Expand All @@ -103,10 +103,6 @@ export async function encrypt(algo, data, recipientA) {
return { ephemeralPublicKey, wrappedKey };
}
case enums.publicKey.x448: {
const x448 = await util.getNobleCurve(enums.publicKey.x448);
const ephemeralSecretKey = x448.utils.randomPrivateKey();
const sharedSecret = x448.getSharedSecret(ephemeralSecretKey, recipientA);
const ephemeralPublicKey = x448.getPublicKey(ephemeralSecretKey);
const hkdfInput = util.concatUint8Array([
ephemeralPublicKey,
recipientA,
Expand Down Expand Up @@ -176,3 +172,35 @@ export function getPayloadSize(algo) {
throw new Error('Unsupported ECDH algorithm');
}
}

export async function generateEphemeralKeyPair(algo) {
switch (algo) {
case enums.publicKey.x25519: {
const ephemeralSecretKey = getRandomBytes(getPayloadSize(algo));
const { publicKey: ephemeralPublicKey } = x25519.box.keyPair.fromSecretKey(ephemeralSecretKey);
return { ephemeralPublicKey, ephemeralSecretKey };
}
case enums.publicKey.x448: {
const x448 = await util.getNobleCurve(enums.publicKey.x448);
const ephemeralSecretKey = x448.utils.randomPrivateKey();
const ephemeralPublicKey = x448.getPublicKey(ephemeralSecretKey);
return { ephemeralSecretKey, ephemeralPublicKey };
}

default:
throw new Error('Unsupported ECDH algorithm');
}
}

export async function getSharedSecret(algo, secretKey, recipientPublicKey) {
switch (algo) {
case enums.publicKey.x25519:
return x25519.scalarMult(secretKey, recipientPublicKey);
case enums.publicKey.x448: {
const x448 = await util.getNobleCurve(enums.publicKey.x448);
return x448.getSharedSecret(secretKey, recipientPublicKey);
}
default:
throw new Error('Unsupported ECDH algorithm');
}
}
4 changes: 3 additions & 1 deletion src/crypto/public_key/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as elgamal from './elgamal';
import * as elliptic from './elliptic';
import * as dsa from './dsa';
import * as hmac from './hmac';
import * as pqc from './pqc';

export default {
/** @see module:crypto/public_key/rsa */
Expand All @@ -19,5 +20,6 @@ export default {
/** @see module:crypto/public_key/dsa */
dsa: dsa,
/** @see module:crypto/public_key/hmac */
hmac: hmac
hmac: hmac,
pqc
};
48 changes: 48 additions & 0 deletions src/crypto/public_key/pqc/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import util from '../../../util';
import * as ecdhX from './kem_ecdh_x';
import * as ml from './kem_ml';

const kem = { ecdhX, ml, multiKeyCombine };
export {
kem
};

async function multiKeyCombine(eccKeyShare, eccCipherText, mlkemKeyShare, mlkemCipherText, fixedInfo, outputBits) {
// multiKeyCombine(eccKeyShare, eccCipherText,
// mlkemKeyShare, mlkemCipherText,
// fixedInfo, oBits)
//
// Input:
// eccKeyShare - the ECC key share encoded as an octet string
// eccCipherText - the ECC ciphertext encoded as an octet string
// mlkemKeyShare - the ML-KEM key share encoded as an octet string
// mlkemCipherText - the ML-KEM ciphertext encoded as an octet string
// fixedInfo - the fixed information octet string
// oBits - the size of the output keying material in bits
//
// Constants:
// domSeparation - the UTF-8 encoding of the string
// "OpenPGPCompositeKeyDerivationFunction"
// counter - the fixed 4 byte value 0x00000001
// customizationString - the UTF-8 encoding of the string "KDF"
const { kmac256 } = await import('@openpgp/noble-hashes/sha3-addons');
// const { eccKeyShare, eccCiphertext } = await publicKey.pqc.kem.ecdhX(keyAlgo, publicParams.A);
// const { keyShare: mlkemKeyShare, cipherText: mlkemCipherText } = await publicKey.pqc.kem.ml(keyAlgo, publicParams.publicKey);
const eccData = util.concatUint8Array([eccKeyShare, eccCipherText]); // eccKeyShare || eccCipherText
const mlkemData = util.concatUint8Array([mlkemKeyShare, mlkemCipherText]); //mlkemKeyShare || mlkemCipherText
// const fixedInfo = new Uint8Array([keyAlgo]);
const encData = util.concatUint8Array([
new Uint8Array([1, 0, 0, 0]),
eccData,
mlkemData,
fixedInfo
]); // counter || eccData || mlkemData || fixedInfo

const mb = kmac256(
util.encodeUTF8('OpenPGPCompositeKeyDerivationFunction'),
encData,
{ personalization: util.encodeUTF8('KDF') }
);

return mb;
}
40 changes: 40 additions & 0 deletions src/crypto/public_key/pqc/kem_ecdh_x.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as ecdhX from '../elliptic/ecdh_x';
import hash from '../../hash';
import util from '../../../util';
import enums from '../../../enums';

export async function encaps(eccAlgo, eccRecipientPublicKey) {
switch (eccAlgo) {
case enums.publicKey.kem_x25519: {
const { ephemeralPublicKey: eccCipherText, ephemeralSecretKey } = await ecdhX.generateEphemeralKeyPair(enums.publicKey.x25519);
const X = await ecdhX.getSharedSecret(enums.publicKey.x25519, ephemeralSecretKey, eccRecipientPublicKey);
const eccKeyShare = await hash.sha3_256(util.concatUint8Array([
X,
eccCipherText,
eccRecipientPublicKey
]));
return {
eccCipherText,
eccKeyShare
};
}
default:
throw new Error('Unsupported KEM algorithm');
}
}

export async function decaps(eccAlgo, eccCipherText, eccSecretKey, eccPublicKey) {
switch (eccAlgo) {
case enums.publicKey.kem_x25519: {
const X = await ecdhX.getSharedSecret(enums.publicKey.x25519, eccSecretKey, eccCipherText);
const eccKeyShare = await hash.sha3_256(util.concatUint8Array([
X,
eccCipherText,
eccPublicKey
]));
return eccKeyShare;
}
default:
throw new Error('Unsupported KEM algorithm');
}
}
Loading

0 comments on commit 388057e

Please sign in to comment.