From 9c331830b94b40e250a71547f4a7b40dcd42dae6 Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Tue, 27 Feb 2024 13:58:43 +0100 Subject: [PATCH] Use ML-KEM instead of Kyber, implement message decryption --- package-lock.json | 64 ++++------ package.json | 2 +- src/crypto/crypto.js | 7 +- src/crypto/public_key/post_quantum/kem/kem.js | 7 +- .../public_key/post_quantum/kem/ml_kem.js | 12 +- src/enums.js | 3 +- src/key/helper.js | 3 +- .../public_key_encrypted_session_key.js | 15 ++- test/crypto/postQuantum.js | 119 ++++++++++++++++++ 9 files changed, 170 insertions(+), 62 deletions(-) diff --git a/package-lock.json b/package-lock.json index 489060eaf1..6ca10fc209 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,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", @@ -32,7 +33,6 @@ "c8": "^8.0.1", "chai": "^4.4.1", "chai-as-promised": "^7.1.2", - "crystals-kyber-js": "^1.0.0", "eckey-utils": "^0.7.14", "eslint": "^8.57.0", "eslint-config-airbnb": "^19.0.4", @@ -880,6 +880,18 @@ "integrity": "sha512-LlQZE/Vtkx/KFnJxg7BB0iwD7oYKDeC8eRECHxKLhYyL2Ad0+xT137VZwv8SZTJB2euPqpx7xkj04ieV0Q665w==", "dev": true }, + "node_modules/@openpgp/crystals-kyber-js": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@openpgp/crystals-kyber-js/-/crystals-kyber-js-1.1.1.tgz", + "integrity": "sha512-Q4azKQpc2/SEPendXQs6IpnD2RNlPY2b7nwg5VNZ05FYYICnqYeH8R/NaTLJ2kVpitAWJpiNRC7bh7VlWz1T8g==", + "dev": true, + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@openpgp/jsdoc": { "version": "3.6.11", "resolved": "https://registry.npmjs.org/@openpgp/jsdoc/-/jsdoc-3.6.11.tgz", @@ -2615,30 +2627,6 @@ "node": ">= 8" } }, - "node_modules/crystals-kyber-js": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/crystals-kyber-js/-/crystals-kyber-js-1.1.1.tgz", - "integrity": "sha512-pLXIKrE3Bgs9cKJI6lp5Yao+4UywljicwhvhvLVm6QPH1GR5Wi9/RrsOmLKifNVF5DN8+MpmKkWq61Tifgu0/Q==", - "dev": true, - "dependencies": { - "@noble/hashes": "1.3.3" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/crystals-kyber-js/node_modules/@noble/hashes": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", - "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", - "dev": true, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", @@ -9132,6 +9120,15 @@ "integrity": "sha512-LlQZE/Vtkx/KFnJxg7BB0iwD7oYKDeC8eRECHxKLhYyL2Ad0+xT137VZwv8SZTJB2euPqpx7xkj04ieV0Q665w==", "dev": true }, + "@openpgp/crystals-kyber-js": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@openpgp/crystals-kyber-js/-/crystals-kyber-js-1.1.1.tgz", + "integrity": "sha512-Q4azKQpc2/SEPendXQs6IpnD2RNlPY2b7nwg5VNZ05FYYICnqYeH8R/NaTLJ2kVpitAWJpiNRC7bh7VlWz1T8g==", + "dev": true, + "requires": { + "@noble/hashes": "1.4.0" + } + }, "@openpgp/jsdoc": { "version": "3.6.11", "resolved": "https://registry.npmjs.org/@openpgp/jsdoc/-/jsdoc-3.6.11.tgz", @@ -10365,23 +10362,6 @@ "which": "^2.0.1" } }, - "crystals-kyber-js": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/crystals-kyber-js/-/crystals-kyber-js-1.1.1.tgz", - "integrity": "sha512-pLXIKrE3Bgs9cKJI6lp5Yao+4UywljicwhvhvLVm6QPH1GR5Wi9/RrsOmLKifNVF5DN8+MpmKkWq61Tifgu0/Q==", - "dev": true, - "requires": { - "@noble/hashes": "1.3.3" - }, - "dependencies": { - "@noble/hashes": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", - "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", - "dev": true - } - } - }, "custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", diff --git a/package.json b/package.json index d13fd6b3b8..41d8cdda28 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", diff --git a/src/crypto/crypto.js b/src/crypto/crypto.js index 7e0c336591..ba1a94255b 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -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 []; @@ -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.'); @@ -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: diff --git a/src/crypto/public_key/post_quantum/kem/kem.js b/src/crypto/public_key/post_quantum/kem/kem.js index 950c62cce6..6fec55df32 100644 --- a/src/crypto/public_key/post_quantum/kem/kem.js +++ b/src/crypto/public_key/post_quantum/kem/kem.js @@ -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 diff --git a/src/crypto/public_key/post_quantum/kem/ml_kem.js b/src/crypto/public_key/post_quantum/kem/ml_kem.js index 08af8616b0..a51d2fbf12 100644 --- a/src/crypto/public_key/post_quantum/kem/ml_kem.js +++ b/src/crypto/public_key/post_quantum/kem/ml_kem.js @@ -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 }; @@ -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 }; @@ -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; diff --git a/src/enums.js b/src/enums.js index 4794fe41ef..0872bcd4bf 100644 --- a/src/enums.js +++ b/src/enums.js @@ -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 */ diff --git a/src/key/helper.js b/src/key/helper.js index b8f910f0b7..ab58a69a19 100644 --- a/src/key/helper.js +++ b/src/key/helper.js @@ -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 diff --git a/src/packet/public_key_encrypted_session_key.js b/src/packet/public_key_encrypted_session_key.js index f6e8dd9b7f..cf75f8087c 100644 --- a/src/packet/public_key_encrypted_session_key.js +++ b/src/packet/public_key_encrypted_session_key.js @@ -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) * @@ -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) { @@ -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; @@ -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'); @@ -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 diff --git a/test/crypto/postQuantum.js b/test/crypto/postQuantum.js index ad5ae64cb6..4e95ad3f59 100644 --- a/test/crypto/postQuantum.js +++ b/test/crypto/postQuantum.js @@ -14,4 +14,123 @@ export default () => describe('PQC', function () { const decryptedSessionKey = await publicKeyDecrypt(openpgp.enums.publicKey.pqc_mlkem_x25519, publicParams, privateParams, encryptedSessionKeyParams); expect(decryptedSessionKey).to.deep.equal(sessionKey.data); }); + + it('ML-KEM + X25519 - Test vector', async function () { + const armoredMessage = `-----BEGIN PGP MESSAGE----- + +wcPUA/qyFfu1TBzoaRaET03uDIyjNeUBk84Hj31jS8SkuW7T/fY78wQZOA89 +MWj0RSoythNA9LJY7749Zlunxl896ws3tIfvy7FzMZ0/6kPEYvADZby9kwpv +X6Ia3DNAKlhiJfXM7oWXaPUQ/rZWnLm+eEnhmkXqE82Vz3nfml7h3k5LQJo6 +f43LX2+euqHDMhdxpffMD1INkABN1Ttzth/ezQ2/5Uob/YvxT4lROGmBTl9g +ua9xFN2Wo4cMxe5XxLYy8QUp+kH5Q+EdPmW9HYFu3YA1xFEALstmx9xM2oRg +KA84QZUVxcBmQekHbFiXkYpGVaZwqa/3oDqTW2tQvqDucShWkwXBryDYrPqm +cSOCdbOlyWxWHRTamgvBy0351dnEVTT+pm9DYDkpmdb0ToBpPebC8sy/WHWV +mRLLkvUPe+IGkgL/1ehNMPVvEACRScV5GviYvs26NruofJMQXZ3BbxARcZ++ +vOTVXl+kC7x+LbQOS4gAyN3RdfBSxrKPH2LBYI7Ccd1pUl/u0AnS6fksDT6n +ofECxwvkLks/CEmGyWaxNCfAVVAPz8S4g3f8jxhK4jtRWhD0PpY3MLCH3bug +JlYTzQm2J59NT9Knm+9PXSKny/5GVNOpfwFfWLgrK58y4Y5DfK0FVhEnLpwW +yqV49y4ujJEWp3Sd9NSCw+oIzi9XwHduj1JP7T+QZn3ED5uW596Q5+kHpiv1 +njg4JRJ7xJK9wC51I47kUmkppo0Er04DUx/1hfXxOmitVcmzDeo8CwVJsin5 +6ac9UPhYLHyTRy7do/cAfd5nxTNSUBI2DB903zbySaI3JGGgEzxOc0RmoHNF +Ku9ajrK9uBZrMMjT0tIbRD+oWiN4h1tYVrPJVZTLiWZGKaVr0PJCBN8rSI9x +Hx9/8Vapp9or0tN/XBTf4In0/gmO/Rr65/46Al8vkWrgcpY2TFuB8vdygUUc +rgboLHZejnWyUVO2+PbEhtbE2PM5sUBLCLK5U1xQphm72bwuwk2cxZihsk98 +L81bhl8z9yDNwei0uQ2fru+Gpp+6cKfBad0eXcC9K8WFwKpsrueDau3U8aG9 +gUqSFpAy9wVha/xagmdWYFSl1d4YtBanpGUhRrlD9iSRUhVJUhtwFAwqhpnE +/6KiJDpi43O9Mq1Re3YivAjR/PeuaWPzY9STqUZGnt9lQ4Wx9WlJw9FY8fU8 +QivWGW7inxhoN/T1or1Wx2iKdK6Oe6ZHBtKDypy9fwtam0SOyT31DFdq3pop +RXPWnbhzPZxgzGs79a42fFZbTD6iz50FeuJYGfHx8ahHPQlPbemS4JhJ0BcF +dtt9pzfJ3tAwxfAnhGJrJUJ/MOba+32uhjFYiwaU7o7ur2ak1K0eqqN3kAW4 +QwDQRpsJaMcO+JdpgSDVdYEkOMyFPLqf8sWUjjXUCVSrct9RI6T5QvUgsgdD +jE06bMep6ElLug6LonwnyjnaUbSgiKG1LoH1HN44ltX+sTiTcrQR0xyv4qDt +Jlswct6sjc0pCYRti9kpbvSKNkFIyao7JiXNo5lrJ5BSZLe9NlxLP6RP6yYl +KIuejZDSRAHCoPc7ma1tGPoVA8BaezF5gCdfaHla2hDaecWB83Ys0VzWCflp +q2MMqQ1h2UVunrMAQ/rYb8GQdkShXzPIF78mXeES +-----END PGP MESSAGE-----`; + const privateKey = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +=jowz +-----END PGP PRIVATE KEY BLOCK-----` }); + + const { data: decryptedData } = await openpgp.decrypt({ + message: await openpgp.readMessage({ armoredMessage }), + decryptionKeys: privateKey + }); + expect(decryptedData).to.equal('hello world\n'); + }); });