Skip to content

Commit

Permalink
Use ML-KEM instead of Kyber, implement message decryption
Browse files Browse the repository at this point in the history
  • Loading branch information
larabr committed Jun 28, 2024
1 parent 39f8d01 commit 9c33183
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 62 deletions.
64 changes: 22 additions & 42 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"@noble/ed25519": "^1.7.3",
"@noble/hashes": "^1.4.0",
"@openpgp/asmcrypto.js": "^3.1.0",
"@openpgp/crystals-kyber-js": "^1.1.1",
"@openpgp/jsdoc": "^3.6.11",
"@openpgp/seek-bzip": "^1.0.5-git",
"@openpgp/tweetnacl": "^1.0.4-1",
Expand All @@ -86,7 +87,6 @@
"chai": "^4.4.1",
"chai-as-promised": "^7.1.2",
"eckey-utils": "^0.7.14",
"crystals-kyber-js": "^1.0.0",
"eslint": "^8.57.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-base": "^15.0.0",
Expand Down
7 changes: 4 additions & 3 deletions src/crypto/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ export async function publicKeyEncrypt(keyAlgo, symmetricAlgo, publicParams, pri
case enums.publicKey.pqc_mlkem_x25519: {
const { eccPublicKey, mlkemPublicKey } = publicParams;
const { eccCipherText, mlkemCipherText, wrappedKey } = await publicKey.postQuantum.kem.encrypt(keyAlgo, eccPublicKey, mlkemPublicKey, data);
return { eccCipherText, mlkemCipherText, C: new ShortByteString(wrappedKey) };
const C = ECDHXSymmetricKey.fromObject({ algorithm: symmetricAlgo, wrappedKey });
return { eccCipherText, mlkemCipherText, C };
}
default:
return [];
Expand Down Expand Up @@ -168,7 +169,7 @@ export async function publicKeyDecrypt(keyAlgo, publicKeyParams, privateKeyParam
const { eccSecretKey, mlkemSecretKey } = privateKeyParams;
const { eccPublicKey } = publicKeyParams;
const { eccCipherText, mlkemCipherText, C } = sessionKeyParams;
return publicKey.postQuantum.kem.decrypt(keyAlgo, eccCipherText, mlkemCipherText, eccSecretKey, eccPublicKey, mlkemSecretKey, C.data);
return publicKey.postQuantum.kem.decrypt(keyAlgo, eccCipherText, mlkemCipherText, eccSecretKey, eccPublicKey, mlkemSecretKey, C.wrappedKey);
}
default:
throw new Error('Unknown public key encryption algorithm.');
Expand Down Expand Up @@ -382,7 +383,7 @@ export function parseEncSessionKeyParams(algo, bytes) {
case enums.publicKey.pqc_mlkem_x25519: {
const eccCipherText = util.readExactSubarray(bytes, read, read + getCurvePayloadSize(enums.publicKey.x25519)); read += eccCipherText.length;
const mlkemCipherText = util.readExactSubarray(bytes, read, read + 1088); read += mlkemCipherText.length;
const C = new ShortByteString(); read += C.read(bytes.subarray(read));
const C = new ECDHXSymmetricKey(); C.read(bytes.subarray(read));
return { eccCipherText, mlkemCipherText, C }; // eccCipherText || mlkemCipherText || len(C) || C
}
default:
Expand Down
7 changes: 5 additions & 2 deletions src/crypto/public_key/post_quantum/kem/kem.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,17 @@ async function multiKeyCombine(eccKeyShare, eccCipherText, mlkemKeyShare, mlkemC
// "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');
if (outputBits !== 256) {
throw new Error('Unsupported output size');
}
const { kmac256 } = await import('@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]),
new Uint8Array([0, 0, 0, 1]),
eccData,
mlkemData,
fixedInfo
Expand Down
12 changes: 6 additions & 6 deletions src/crypto/public_key/post_quantum/kem/ml_kem.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import enums from '../../../../enums';
export async function generate(algo) {
switch (algo) {
case enums.publicKey.pqc_mlkem_x25519: {
const { Kyber768 } = await import('crystals-kyber-js');
const kyberInstance = new Kyber768();
const { MlKem768 } = await import('@openpgp/crystals-kyber-js');
const kyberInstance = new MlKem768();
const [encapsulationKey, decapsulationKey] = await kyberInstance.generateKeyPair();

return { mlkemPublicKey: encapsulationKey, mlkemSecretKey: decapsulationKey };
Expand All @@ -17,8 +17,8 @@ export async function generate(algo) {
export async function encaps(algo, mlkemRecipientPublicKey) {
switch (algo) {
case enums.publicKey.pqc_mlkem_x25519: {
const { Kyber768 } = await import('crystals-kyber-js');
const kyberInstance = new Kyber768();
const { MlKem768 } = await import('@openpgp/crystals-kyber-js');
const kyberInstance = new MlKem768();
const [mlkemCipherText, mlkemKeyShare] = await kyberInstance.encap(mlkemRecipientPublicKey);

return { mlkemCipherText, mlkemKeyShare };
Expand All @@ -31,8 +31,8 @@ export async function encaps(algo, mlkemRecipientPublicKey) {
export async function decaps(algo, mlkemCipherText, mlkemSecretKey) {
switch (algo) {
case enums.publicKey.pqc_mlkem_x25519: {
const { Kyber768 } = await import('crystals-kyber-js');
const kyberInstance = new Kyber768();
const { MlKem768 } = await import('@openpgp/crystals-kyber-js');
const kyberInstance = new MlKem768();
const mlkemKeyShare = await kyberInstance.decap(mlkemCipherText, mlkemSecretKey);

return mlkemKeyShare;
Expand Down
3 changes: 1 addition & 2 deletions src/enums.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,7 @@ export default {
/** Ed448 (Sign only) */
ed448: 28,
/** Post-quantum ML-KEM-768 + X25519 (Encrypt only) */
pqc_mlkem_x25519: 29,

pqc_mlkem_x25519: 105,
/** Persistent symmetric keys: encryption algorithm */
aead: 100,
/** Persistent symmetric keys: authentication algorithm */
Expand Down
3 changes: 2 additions & 1 deletion src/key/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,8 @@ export function validateDecryptionKeyPacket(keyPacket, signature, config) {
case enums.publicKey.elgamal:
case enums.publicKey.ecdh:
case enums.publicKey.x25519:
case enums.publicKey.x448: {
case enums.publicKey.x448:
case enums.publicKey.pqc_mlkem_x25519: {
const isValidSigningKeyPacket = !signature.keyFlags || (signature.keyFlags[0] & enums.keyFlags.signData) !== 0;
if (isValidSigningKeyPacket && config.allowInsecureDecryptionWithSigningKeys) {
// This is only relevant for RSA keys, all other signing algorithms cannot decrypt
Expand Down
15 changes: 10 additions & 5 deletions src/packet/public_key_encrypted_session_key.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ import enums from '../enums';
import util from '../util';
import { UnsupportedError } from './packet';

const algosWithV3CleartextSessionKeyAlgorithm = new Set([
enums.publicKey.x25519,
enums.publicKey.x448,
enums.publicKey.pqc_mlkem_x25519
]);

/**
* Public-Key Encrypted Session Key Packets (Tag 1)
*
Expand Down Expand Up @@ -128,7 +134,7 @@ class PublicKeyEncryptedSessionKeyPacket {
}
this.publicKeyAlgorithm = bytes[offset++];
this.encrypted = crypto.parseEncSessionKeyParams(this.publicKeyAlgorithm, bytes.subarray(offset));
if (this.publicKeyAlgorithm === enums.publicKey.x25519 || this.publicKeyAlgorithm === enums.publicKey.x448) {
if (algosWithV3CleartextSessionKeyAlgorithm.has(this.publicKeyAlgorithm)) {
if (this.version === 3) {
this.sessionKeyAlgorithm = enums.write(enums.symmetric, this.encrypted.C.algorithm);
} else if (this.encrypted.C.algorithm !== null) {
Expand Down Expand Up @@ -207,10 +213,7 @@ class PublicKeyEncryptedSessionKeyPacket {

const { sessionKey, sessionKeyAlgorithm } = decodeSessionKey(this.version, this.publicKeyAlgorithm, decryptedData, randomSessionKey);

// v3 Montgomery curves have cleartext cipher algo
if (this.version === 3 && (
this.publicKeyAlgorithm !== enums.publicKey.x25519 && this.publicKeyAlgorithm !== enums.publicKey.x448)
) {
if (this.version === 3 && !algosWithV3CleartextSessionKeyAlgorithm.has(this.publicKeyAlgorithm)) {
this.sessionKeyAlgorithm = sessionKeyAlgorithm;
}
this.sessionKey = sessionKey;
Expand All @@ -235,6 +238,7 @@ function encodeSessionKey(version, keyAlgo, cipherAlgo, sessionKeyData) {
]);
case enums.publicKey.x25519:
case enums.publicKey.x448:
case enums.publicKey.pqc_mlkem_x25519:
return sessionKeyData;
default:
throw new Error('Unsupported public key algorithm');
Expand Down Expand Up @@ -283,6 +287,7 @@ function decodeSessionKey(version, keyAlgo, decryptedData, randomSessionKey) {
}
case enums.publicKey.x25519:
case enums.publicKey.x448:
case enums.publicKey.pqc_mlkem_x25519:
return {
sessionKeyAlgorithm: null,
sessionKey: decryptedData
Expand Down
Loading

0 comments on commit 9c33183

Please sign in to comment.