From a5e646243b93a8ecffee30d7e586cfe50c73abe5 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 | 42 ++----- package.json | 2 +- src/crypto/crypto.js | 7 +- src/crypto/public_key/post_quantum/kem/kem.js | 5 +- .../public_key/post_quantum/kem/ml_kem.js | 12 +- src/enums.js | 2 +- src/key/helper.js | 3 +- .../public_key_encrypted_session_key.js | 21 +++- test/crypto/postQuantum.js | 119 +++++++++++++++++- 9 files changed, 159 insertions(+), 54 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0533789a6f..58c844eb8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "bn.js": "^4.11.8", "chai": "^4.3.7", "chai-as-promised": "^7.1.1", - "crystals-kyber-js": "^1.0.0", + "crystals-kyber-js": "larabr/crystals-kyber-js#e87c7319cbc56246a67fe94ff63b11bb421854ff", "eslint": "^8.34.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-base": "^15.0.0", @@ -588,18 +588,6 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, - "node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "dev": true, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2072,16 +2060,11 @@ } }, "node_modules/crystals-kyber-js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crystals-kyber-js/-/crystals-kyber-js-1.0.0.tgz", - "integrity": "sha512-seFuh50/zZpd+N6XCg+4Ryu7G6f+I3bVajuEgBr9H0tGQpq9lbEe8FutXoJiS0HJhpwbmdNiQm0jH9nkw8n+Fg==", + "resolved": "git+ssh://git@github.com/larabr/crystals-kyber-js.git#e87c7319cbc56246a67fe94ff63b11bb421854ff", + "integrity": "sha512-tLXi0cfpa2mye0nqV5vNE6y/CM0WKLQ+UX44679ZBgOmhhKKVatlbyYULrmmcL180qNqePKzfqsC7koslCbYIw==", "dev": true, - "dependencies": { - "@noble/hashes": "1.3.2" - }, - "engines": { - "node": ">=16.0.0" - } + "hasInstallScript": true, + "license": "MIT" }, "node_modules/custom-event": { "version": "1.0.1", @@ -8306,12 +8289,6 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, - "@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "dev": true - }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -9432,13 +9409,10 @@ } }, "crystals-kyber-js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crystals-kyber-js/-/crystals-kyber-js-1.0.0.tgz", - "integrity": "sha512-seFuh50/zZpd+N6XCg+4Ryu7G6f+I3bVajuEgBr9H0tGQpq9lbEe8FutXoJiS0HJhpwbmdNiQm0jH9nkw8n+Fg==", + "version": "git+ssh://git@github.com/larabr/crystals-kyber-js.git#e87c7319cbc56246a67fe94ff63b11bb421854ff", + "integrity": "sha512-tLXi0cfpa2mye0nqV5vNE6y/CM0WKLQ+UX44679ZBgOmhhKKVatlbyYULrmmcL180qNqePKzfqsC7koslCbYIw==", "dev": true, - "requires": { - "@noble/hashes": "1.3.2" - } + "from": "crystals-kyber-js@larabr/crystals-kyber-js#e87c7319cbc56246a67fe94ff63b11bb421854ff" }, "custom-event": { "version": "1.0.1", diff --git a/package.json b/package.json index dd4a6b2f6a..2017728d65 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "bn.js": "^4.11.8", "chai": "^4.3.7", "chai-as-promised": "^7.1.1", - "crystals-kyber-js": "^1.0.0", + "crystals-kyber-js": "larabr/crystals-kyber-js#e87c7319cbc56246a67fe94ff63b11bb421854ff", "eslint": "^8.34.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 a056c5d892..44191f9676 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.'); @@ -392,7 +393,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 1984756bf4..31a1c16ff1 100644 --- a/src/crypto/public_key/post_quantum/kem/kem.js +++ b/src/crypto/public_key/post_quantum/kem/kem.js @@ -46,6 +46,9 @@ 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); @@ -53,7 +56,7 @@ async function multiKeyCombine(eccKeyShare, eccCipherText, mlkemKeyShare, mlkemC 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..71eaddb675 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('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('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('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 c52209a393..674c05070b 100644 --- a/src/enums.js +++ b/src/enums.js @@ -142,7 +142,7 @@ export default { /** Ed448 (Sign only) */ ed448: 28, /** Post-quantum ML-KEM-768 + X25519 (Encrypt only) */ - pqc_mlkem_x25519: 29, + pqc_mlkem_x25519: 105, /** Post-quantum ML-DSA-64 + Ed25519 (Sign only) */ pqc_mldsa_ed25519: 35, diff --git a/src/key/helper.js b/src/key/helper.js index e86a442498..a5122987d0 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 634290c253..9f3632f385 100644 --- a/src/packet/public_key_encrypted_session_key.js +++ b/src/packet/public_key_encrypted_session_key.js @@ -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); } } @@ -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; @@ -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'); @@ -277,6 +274,7 @@ function decodeSessionKey(version, keyAlgo, decryptedData, randomSessionKey) { } case enums.publicKey.x25519: case enums.publicKey.x448: + case enums.publicKey.pqc_mlkem_x25519: return { sessionKey: decryptedData }; @@ -284,3 +282,14 @@ function decodeSessionKey(version, keyAlgo, decryptedData, randomSessionKey) { 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; + } +} diff --git a/test/crypto/postQuantum.js b/test/crypto/postQuantum.js index 3f2893e97b..7db432dd8e 100644 --- a/test/crypto/postQuantum.js +++ b/test/crypto/postQuantum.js @@ -16,7 +16,124 @@ export default () => describe('PQC', function () { expect(decryptedSessionKey).to.deep.equal(sessionKey.data); }); - it('ML-DSA + Ed25519 - Generate/sign/verify', async function () { + 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----- + +xVgEZdXkZBYJKwYBBAHaRw8BAQdAIrXzAtBR4BFzHu1YNESGSShdX8TNGRhjR9BKDrbhasQAAP9K +GuOc6G1W53iCNBIbEcbEqTp3yIEbZc3sCc3oN2mGABAqzQ5TYW1wbGVWNFBRQ0tlecKPBBMWCAA3 +FiEEFWjtiwO7w3FeHVyqNmrNi0+1P+UFAmXV5GQFCQPCZwACGwMECwkIBwUVCAkKCwUWAgMBAAAK +CRA2as2LT7U/5W9aAQDPetWJoYh2e5z0wZCmz1IF6pJxcbBOClN9KLYzrM37wQEAs5MVR5cKKeRp +FbzD41l3wiYsP6by17H4Y9072BvY6gbHXQRl1eRkEgorBgEEAZdVAQUBAQdAj3yMDZuSFpNvsaWR +zsmX58goAtwyc1colj3PfZljihUDAQgHAAD/SzaAg4UENPFwImAnaNG9CRqRSxqwPpbBHEbM4eEd +RxgOCsJ+BBgWCAAmFiEEFWjtiwO7w3FeHVyqNmrNi0+1P+UFAmXV5GQFCQPCZwACGwwACgkQNmrN +i0+1P+V3ywEA46Tzha2DkUV1CbaTMVaCRur1wEQJ1dXRWkT6o509c+oBAOIDWzmCH8znxOIaFj8i +txt8xid2Yy3eS236sckDPwUMx82JBGXV5GRpFQVZj6p1+CgY/jNyxd/0xsqt7MXGJxW424tMXu8F +Hisi0kypuajINTX6NhI1MR8oFVejSCpzia0aulZeaI6/4cscp5c0oX3m4MvBFaPzNab4U350HF4Z +kGuUN8fMrCFUIHJH1lDqtF3c2gmqWKCsmTwqZIbZCoYx1QSsLGqcAwJ8LGNEYTz/Z2qfoAG7Ypx2 +F3E0wR+gUXeu7BN22JpgeCqhGJH1pgXgp2Itl6T3mk5PLIhOoMoFZKFs8rsm1yA0ExzaVlMD1VjR +opIh9kRwwRGmCCpNMzwN46c6BoyCuL7T82ooTFuaYDnisGkiWGTdqLEXFlx5Bm1mREZ89ci8Uw9/ +3CfgNS/jCb1zJQPYt2MrGVLfdhIJlFYuYGjs6ZIAwEAqCJ7c58MkKSMNx8DBcjRJmoCXZ79ScCoO +KsxndBh4lmzoYgzEOhwA07FZMauZyXsdSFRos1jAYMnQ03/dEHMHqZKDNrOGKTM7sxJoBcbO1COC +YnHvsBiqI1Qnc8qauCcTQg/a9xcFAALsRmzIU3gr4BiTBiPsZzeqU3fYE5oO94ZrKJYad5R7o3d8 +G2iGysZ4cJYZFj12yC2pYnsBVSrS8pqcsgmXMIvHVTM4RkRLkj6vnCYXpWEgNZlsaMO5+J/ee8ea +Bhv0mjiB3BB/qxuFOawlJasySU/SVFrXgoIZrAkTowrmYIDYWZh8F1kZQDpEsE11swr28WyOnBiX +EYK8qFzx0YRBGX3iFmYoScEZ5FyHUwLKOXHHSJ2UBrs3YFsUuyWcszB7oSjyBsB8l8RRFLQKEljr +WVGZNaT6kMoUgq7JAxCzpo44FKBKQVun/MXxkyN5uiM+1Slq8qDBFKgUXHfdvHInobc4GkOYMEXw +1mXahZZuWcNVU67wV1ERAxKFuhlaTKD6+HE4wlTsEF51ELv0EIe4rMobJ7VCgRzFecuex3EYiX4d +UYZRQMSa987fUhF7zJNIy3K6GonoEnWgUxxx57PucVCEEaBKPAhR4VSk8XakIzxBQQJkFRYTdEHL +w3jjvHazwxa45DZWg33KcHVxSL5ihCdXuw3xRUIRMxn5Iax4YF7f14ACmHmBUGl/gkhJ5JAl5pwY +A8lx2Jn0ULRzSygqU3v8Sp3taoVAxcUmJWs31KBWRIRvSymwmKwXCjsfp0/eOGEyDGyu47KBx49j +k5XKaJ+9urYu6sw1S3eSy1o8FXs5QMwxJYzDBC8LWHyvYou4ohdENws/Y3ks5Rja8IUwGD21gyd3 +o8lMoyBi9cIxoL2AfMlQsz7bbMjMY8+68mF0ebk+lQW2qFUyYJe95cTEW1/B8DpCIRGgW023Zpt6 +dRDrJ4TzlltuZc2PtDHHXIX1uI6BFcPu9kI0lQBWcky6aS2jtXl/EGpOQxkby55umxh1tV6x9bb4 +RFhzYR8gta2yQX1NWztZmzAbQVyHF6Ql5FeEMp5Gy6nsk26Ja30IURD6AgtGC02AXKMYNBTaiqou +yAaYJz6W5n1nBp/cCMACVbyqNRg6GFfSgA8p15v45gjgGiuBMYjzs1Fd8pTa0QhYmGfz5K86UgxA +tUYlHF60ghBooo4wGUrNUVu7u+sOY61YoUOsfZDWMp00z9uof//FxAcBlt0FqABA8rZiz7j/QGJm +CzP/BvNfOaCsYKpPIF0H8sJ1pcY/HHD6pzr7WdGoESSWvk58N8+LS/N7t8kpKEOGMPhiaKOrU1dx +kKTcw8RQrZaqNudRjSJzNPsaXNvBa72ae1t2PkHJV6tzXWjwlFgjpkspHw+7U/MSNh7yJDYXZYlG +c5lbqh8zKOsRPE5qbxtHeqH5RNIiYJQWPkJRnu+7rC/KUWAUelxhGjL2dQgyM/j0hx+FZyrpCR+y +eg2IljLjSfODFs+SaNCJT1cWLKrTevHLB6uATvDVPDxJelG6OJTQvWBYEavlrKaVTEwcRg5yhUSz +WFZTZBgjWky3fzvAUdj3RAWGg527DDn3vvwpKAE6gAQJXiAGrVHIsaL0IuxirKcRhLbqvay4KY7A +CxGSBjcmOnGSUiKqBTKKOc9pJIYIFbHSODLquyOZbU5DLFeAXONWuD1cUQDIGjjpLPhWNqz8nwgD +Q0KrU1T0emNZztvsiFG0bsGFIm3yl0JGiDggCAlkxsYbP+i5HhRzXTChyVEjyQBgn6ypgPV4uqz6 +E+yzqo/aKM9is891PaokroGQLx7Bq1C3eZJQJkSLNKr2hJZ2rNQ7JzeIG1h2SpoyhIESvyyyai2E +t23Kw/Fbmoqqhtd4cUa2Zc9jU6uaKV3MKvvRwtUSLznUM/fypIEHPpJJb+RzWx4FCguUVYKGeW+V +wzh1XXjagOPFt+tjYM8WYxYBUMVjb5ozwUCDT0F0z1fmPVCkYwP1dT20FylpfDukTzjZZKLcRLKD +pivytDLGJIS8OLmUVPHXkzF1yC6hus8Qhdf1GBLjFesGgci4WUUEYyMStJEZrkyaIUf8vWuanULI +UPHzT/uBPWi8mqOFNfrqo9cSitZSd3EAYoM7qcRDOJfwzl55jhu6lvcxUa9lmbxHXtSyux08nuLi +Y+aYYAaTLLuXAR8Yvh7ME0pbsrEUiG9pP1R4KsqJl8AIdurMOk0LDdsgHNtyTogDPQ/ab3oyakEq +d+Qpb8a0Ia0hCmu5bwrzSi3ZtPjmzvp4b2iSuLTZQCnkUkT3CwQwCCzHraHmzDlTm/6TMVkLK0PA +kHVVa0Rqc9gMRcFGlV9Jdm+wtyMZk4LWdTBVi/m0Yn8yIV2je8RMMekrR9dauwvaywC4ZNf2TQQq +YDw1Y366IhZwwhlVi9xxczxjuMmiy57KyNd7aiQyMEpaE6ayb5mbGWprPKdkxPbIhzW0FRdWp8AD +gFWsUJA1bvQqINrAGbJbwQQiN0roIskaMlH3iJOULBsxLeRTjXbXmRmZsv0xcXe4BZH8kR2ESmYy +TrhmFCdgNtJZnm1XnGIhkMkFXgj1DTVqz9wlcfXBvCrHfHQJFOSswFT2bmhSMFykqUgmMd5VaclD +jP4GgNlME85QuHKwcE55lVWgUoUwbcqgZDYTZ8MhnGnQKakse2zkxExcdh/hKJgFik8UsB/aKCSl +Ah9TTPlsIVPUfxZaNKMam0ZhtltIPncMsN4WNKW1IcxgclOJbUmGU/jxmDUzUKTqxYMzTjeGvBLF +wdOoNhhkSy3HeAOjoykjR2t0g2pmY4PQYwW3kKsJRXVFYiLSTKm5qMg1Nfo2EjUxHygVV6NIKnOJ +rRq6Vl5ojr/hyxynlzShfebgy8EVo/M1pvhTfnQcXhmQa5Q3x8ysIVQgckfWUOq0XdzaCapYoKyZ +PCpkhtkKhjHVBKwsapwDAnwsY0RhPP9nap+gAbtinHYXcTTBH6BRd67sE3bYmmB4KqEYkfWmBeCn +Yi2XpPeaTk8siE6gygVkoWzyuybXIDQTHNpWUwPVWNGikiH2RHDBEaYIKk0zPA3jpzoGjIK4vtPz +aihMW5pgOeKwaSJYZN2osRcWXHkGbWZERnz1yLxTD3/cJ+A1L+MJvXMlA9i3YysZUt92EgmUVi5g +aOzpkgDAQCoIntznwyQpIw3HwMFyNEmagJdnv1JwKg4qzGd0GHiWbOhiDMQ6HADTsVkxq5nJex1I +VGizWMBgydDTf90QcwepkoM2s4YpMzuzEmgFxs7UI4Jice+wGKojVCdzypq4JxNCD9r3FwUAAuxG +bMhTeCvgGJMGI+xnN6pTd9gTmg73hmsolhp3lHujd3wbaIbKxnhwlhkWPXbILaliewFVKtLympyy +CZcwi8dVMzhGREuSPq+cJhelYSA1mWxow7n4n957x5oGG/SaOIHcEH+rG4U5rCUlqzJJT9JUWteC +ghmsCROjCuZggNhZmHwXWRlAOkSwTXWzCvbxbI6cGJcRgryoXPHRhEEZfeIWZihJwRnkXIdTAso5 +ccdInZQGuzdgWxS7JZyzMHuhKPIGwHyXxFEUtAoSWOtZUZk1pPqQyhSCrskDELOmjjgUoEpBW6f8 +xfGTI3m6Iz7VKWryoMEUqBRcd928ciehtzgaQ5gwRfDWZdqFlm5Zw1VTrvBXUREDEoW6GVpMoPr4 +cTjCVOwQXnUQu/QQh7isyhsntUKBHMV5y57HcRiJfh1RhlFAxJr3zt9SEXvMk0jLcroaiegSdaBT +HHHns+5xUIQRoEo8CFHhVKTxdqQjPEFBAmQVFhN0QcvDeOO8drPDFrjkNlaDfcpwdXFIvmKEJ1e7 +DfFFQhEzGfkhrHhgXt/XgAKYeYFQaX+CSEnkkCXmnBgDyXHYmfRQtHNLKCpTe/xKne1qhUDFxSYl +azfUoFZEhG9LKbCYrBcKOx+nT944YTIMbK7jsoHHj2OTlcpon726ti7qzDVLd5LLWjwVezlAzDEl +jMMELwtYfK9ii7iiF0Q3Cz9jeSzlGNrwhTAYPbWDJ3ejyUyjIGL1wjGgvYB8yVCzPttsyMxjz7ry +YXR5uT6VBbaoVTJgl73lxMRbX8HwOkIhEaBbTbdmm3p1EOsnhPOWW25lzY+0McdchfW4joEVw+72 +QjSVAFZyTLppLaO1eX8Qak5DGRvLnm6bGHW1XrH1tvhEWHNhHyC1rbJBfU1bO1mbMBtBXIcXpCXk +V4QynkbLqeyTbolrfQhREPoCC0YLTYBcoxg0FNqKqi7IBpgnPpbmfWcGn9wIwAJVvKo1GDoYV9KA +DynXm/jmCOAaK4ExiPOzUV3ylNrRCFiYZ/PkrzpSDEC1RiUcXrSCEGiijjAZSs1RW7u76w5jrVih +Q6x9kNYynTTP26h//8XEBwGW3QWoaLcXUJNpP2JmC6cwd4ls1tP3ekewIkWaP94i9FVzNo2y8zIo +nZprJ43z0ED7FVkv16sS6iUPH356gFR4SBpjrGJswn4EGBYIACYWIQQVaO2LA7vDcV4dXKo2as2L +T7U/5QUCZdXkZAUJA8JnAAIbDAAKCRA2as2LT7U/5R8EAP9rVvp9knEAoaaIoslkfkVy18RNymWe +gxa3/Jm0tqLUHAEAyyV5473sqcilThIyTdMYnpu1TCFoX+IBvj3U4JML+As= +=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'); + }); const digest = new Uint8Array(32).fill(1); const hashAlgo = openpgp.enums.hash.sha256;