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 Mar 18, 2024
1 parent eba0ffe commit cd4521b
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 60 deletions.
62 changes: 22 additions & 40 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 @@ -63,6 +63,7 @@
},
"devDependencies": {
"@openpgp/asmcrypto.js": "^3.1.0",
"@openpgp/crystals-kyber-js": "^1.1.1-0",
"@openpgp/jsdoc": "^3.6.11",
"@openpgp/noble-curves": "^1.3.0",
"@openpgp/noble-hashes": "^1.3.3",
Expand All @@ -82,7 +83,6 @@
"chai": "^4.4.1",
"chai-as-promised": "^7.1.1",
"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
5 changes: 4 additions & 1 deletion 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"
if (outputBits !== 256) {
throw new Error('Unsupported output size');
}
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]),
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
21 changes: 15 additions & 6 deletions src/packet/public_key_encrypted_session_key.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,7 @@ class PublicKeyEncryptedSessionKeyPacket {
}
this.publicKeyAlgorithm = bytes[offset++];
this.encrypted = crypto.parseEncSessionKeyParams(this.publicKeyAlgorithm, bytes.subarray(offset));
if (this.version === 3 && (
this.publicKeyAlgorithm === enums.publicKey.x25519 || this.publicKeyAlgorithm === enums.publicKey.x448)) {
if (hasCleartextCipherAlgo(this.version, this.publicKeyAlgorithm)) {
this.sessionKeyAlgorithm = enums.write(enums.symmetric, this.encrypted.C.algorithm);
}
}
Expand Down Expand Up @@ -201,10 +200,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 && !hasCleartextCipherAlgo(this.version, this.publicKeyAlgorithm)) {
this.sessionKeyAlgorithm = sessionKeyAlgorithm;
}
this.sessionKey = sessionKey;
Expand All @@ -229,6 +225,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 @@ -277,10 +274,22 @@ function decodeSessionKey(version, keyAlgo, decryptedData, randomSessionKey) {
}
case enums.publicKey.x25519:
case enums.publicKey.x448:
case enums.publicKey.pqc_mlkem_x25519:
return {
sessionKey: decryptedData
};
default:
throw new Error('Unsupported public key algorithm');
}
}

function hasCleartextCipherAlgo(version, publicKeyAlgo) {
switch (publicKeyAlgo) {
case enums.publicKey.x25519:
case enums.publicKey.x448:
case enums.publicKey.pqc_mlkem_x25519:
return version === 3;
default:
return false;
}
}
Loading

0 comments on commit cd4521b

Please sign in to comment.