From 68c7c29bd140202591550c20d895af426d769e07 Mon Sep 17 00:00:00 2001 From: Justin Lovell Date: Fri, 7 Jan 2022 18:28:34 +1100 Subject: [PATCH 1/9] Illustration of Memory Explosion and Process Crash --- test/benchmarks/memory_usage.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/benchmarks/memory_usage.js b/test/benchmarks/memory_usage.js index d00aa17b7..201a6099d 100644 --- a/test/benchmarks/memory_usage.js +++ b/test/benchmarks/memory_usage.js @@ -213,6 +213,38 @@ class MemoryBenchamrkSuite { await openpgp.decrypt({ message: encryptedMessage, passwords, config }); }); + suite.add('openpgp.encrypt/decrypt (PGP, text @ 100MB, with streaming)', async () => { + await stream.loadStreamsPonyfill(); + + const userID = { name: 'test', email: 'a@b.com' }; + const passphrase = 'super long and hard to guess secret'; + const { privateKey: rawPrivateKey, publicKey: rawPublicKey } = + await openpgp.generateKey({ userIDs: [userID], format: 'armored', passphrase }); + + function* largeDataGenerator() { + const chunkSize = 1024 * 1024; /*1MB*/ + const numberOfChunks = 100; + + for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { + yield 'a'.repeat(chunkSize); + } + } + + const plaintextMessage = await openpgp.createMessage({ text: require('stream').Readable.from(largeDataGenerator()) }); + assert(plaintextMessage.fromStream); + + const publicKey = await openpgp.readKey({ armoredKey: rawPublicKey }); + const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, encryptionKeys: [publicKey] }); + + const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); + + const privateKey = await openpgp.decryptKey({ + privateKey: await openpgp.readKey({ armoredKey: rawPrivateKey }), + passphrase + }); + await openpgp.decrypt({ message: encryptedMessage, decryptionKeys: privateKey }); + }); + const stats = await suite.run(); // Print JSON stats to stdout console.log(JSON.stringify(stats, null, 4)); From 29c0c84e238d52c1db7bb148a398b9b0935e9004 Mon Sep 17 00:00:00 2001 From: Justin Lovell Date: Fri, 28 Jan 2022 14:07:13 +1100 Subject: [PATCH 2/9] Updated documentation for allowUnauthenticatedStream Helpful advice for processing very large streams. --- src/config/config.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/config/config.js b/src/config/config.js index 035243c9f..b05b169f1 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -92,8 +92,17 @@ export default { */ allowUnauthenticatedMessages: false, /** - * Allow streaming unauthenticated data before its integrity has been checked. + * Allow streaming unauthenticated data before its integrity has been checked. This would allow the application to + * process very large streams by deferring the checksum integrity check to be only verified at the end of the stream. + * * This setting is **insecure** if the partially decrypted message is processed further or displayed to the user. + * + * Therefore, the client application is advised to implement at least one of the following strategies when processing + * very large streams. + * - Perform a two-phase commit -- process the data in a staged area, and when the stream finalizes, commit if no error + * occurs. + * - Read the stream twice -- read the contents to the end of the stream, and if the data is verified to be correct, + * then perform a second pass on the stream to perform the real work. * @memberof module:config * @property {Boolean} allowUnauthenticatedStream */ From 42e402e22aa4f487603656e395f02c653299c799 Mon Sep 17 00:00:00 2001 From: Justin Lovell Date: Fri, 28 Jan 2022 14:21:50 +1100 Subject: [PATCH 3/9] allowUnauthenticatedStream implemented on demonstration test --- test/benchmarks/memory_usage.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/benchmarks/memory_usage.js b/test/benchmarks/memory_usage.js index 201a6099d..77af6bea5 100644 --- a/test/benchmarks/memory_usage.js +++ b/test/benchmarks/memory_usage.js @@ -213,7 +213,7 @@ class MemoryBenchamrkSuite { await openpgp.decrypt({ message: encryptedMessage, passwords, config }); }); - suite.add('openpgp.encrypt/decrypt (PGP, text @ 100MB, with streaming)', async () => { + suite.add('openpgp.encrypt/decrypt (PGP, text @ 6000MB, with streaming)', async () => { await stream.loadStreamsPonyfill(); const userID = { name: 'test', email: 'a@b.com' }; @@ -223,7 +223,7 @@ class MemoryBenchamrkSuite { function* largeDataGenerator() { const chunkSize = 1024 * 1024; /*1MB*/ - const numberOfChunks = 100; + const numberOfChunks = 6000; for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { yield 'a'.repeat(chunkSize); @@ -242,7 +242,11 @@ class MemoryBenchamrkSuite { privateKey: await openpgp.readKey({ armoredKey: rawPrivateKey }), passphrase }); - await openpgp.decrypt({ message: encryptedMessage, decryptionKeys: privateKey }); + await openpgp.decrypt({ + message: encryptedMessage, + decryptionKeys: privateKey, + config: { allowUnauthenticatedStream: true } + }); }); const stats = await suite.run(); From 358b07b7a2f322c0affc004345feae244ac3c589 Mon Sep 17 00:00:00 2001 From: larabr Date: Wed, 1 Jun 2022 18:22:53 +0200 Subject: [PATCH 4/9] Apply suggestions from code review --- src/config/config.js | 10 ++-------- test/benchmarks/memory_usage.js | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/config/config.js b/src/config/config.js index b05b169f1..2108e15cd 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -93,16 +93,10 @@ export default { allowUnauthenticatedMessages: false, /** * Allow streaming unauthenticated data before its integrity has been checked. This would allow the application to - * process very large streams by deferring the checksum integrity check to be only verified at the end of the stream. + * process large streams while limiting memory usage by releasing the decrypted chunks as soon as possible + * and deferring checking their integrity until the decrypted stream has been read in full. * * This setting is **insecure** if the partially decrypted message is processed further or displayed to the user. - * - * Therefore, the client application is advised to implement at least one of the following strategies when processing - * very large streams. - * - Perform a two-phase commit -- process the data in a staged area, and when the stream finalizes, commit if no error - * occurs. - * - Read the stream twice -- read the contents to the end of the stream, and if the data is verified to be correct, - * then perform a second pass on the stream to perform the real work. * @memberof module:config * @property {Boolean} allowUnauthenticatedStream */ diff --git a/test/benchmarks/memory_usage.js b/test/benchmarks/memory_usage.js index 77af6bea5..151501a67 100644 --- a/test/benchmarks/memory_usage.js +++ b/test/benchmarks/memory_usage.js @@ -213,7 +213,7 @@ class MemoryBenchamrkSuite { await openpgp.decrypt({ message: encryptedMessage, passwords, config }); }); - suite.add('openpgp.encrypt/decrypt (PGP, text @ 6000MB, with streaming)', async () => { + suite.add('openpgp.encrypt/decrypt (MDC, text @ 6000MB, with streaming)', async () => { await stream.loadStreamsPonyfill(); const userID = { name: 'test', email: 'a@b.com' }; From 8b17f124b51ef5ba05ac329dd8cab87a4b8f8221 Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Thu, 2 Jun 2022 12:08:42 +0200 Subject: [PATCH 5/9] Use password encryption and disable compression to reduce noise Also add comparison large text data tests for AEAD and authenticated CFB stream --- test/benchmarks/memory_usage.js | 75 ++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/test/benchmarks/memory_usage.js b/test/benchmarks/memory_usage.js index 151501a67..79bb2b7f5 100644 --- a/test/benchmarks/memory_usage.js +++ b/test/benchmarks/memory_usage.js @@ -213,42 +213,79 @@ class MemoryBenchamrkSuite { await openpgp.decrypt({ message: encryptedMessage, passwords, config }); }); - suite.add('openpgp.encrypt/decrypt (MDC, text @ 6000MB, with streaming)', async () => { + suite.add('openpgp.encrypt/decrypt (CFB, text @ 100MB, with streaming)', async () => { await stream.loadStreamsPonyfill(); - const userID = { name: 'test', email: 'a@b.com' }; - const passphrase = 'super long and hard to guess secret'; - const { privateKey: rawPrivateKey, publicKey: rawPublicKey } = - await openpgp.generateKey({ userIDs: [userID], format: 'armored', passphrase }); - - function* largeDataGenerator() { - const chunkSize = 1024 * 1024; /*1MB*/ - const numberOfChunks = 6000; + function* largeTextDataGenerator() { + const chunkSize = 1024 * 1024; // 1MB + const numberOfChunks = 100; for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { yield 'a'.repeat(chunkSize); } } - const plaintextMessage = await openpgp.createMessage({ text: require('stream').Readable.from(largeDataGenerator()) }); + const passwords = 'password'; + const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; + const plaintextMessage = await openpgp.createMessage({ text: require('stream').Readable.from(largeTextDataGenerator()) }); assert(plaintextMessage.fromStream); - const publicKey = await openpgp.readKey({ armoredKey: rawPublicKey }); - const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, encryptionKeys: [publicKey] }); - + const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); + assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket); + await openpgp.decrypt({ message: encryptedMessage, passwords, config }); + }); - const privateKey = await openpgp.decryptKey({ - privateKey: await openpgp.readKey({ armoredKey: rawPrivateKey }), - passphrase - }); + suite.add('openpgp.encrypt/decrypt (CFB, text @ 100MB, with unauthenticated streaming)', async () => { + await stream.loadStreamsPonyfill(); + + function* largeTextDataGenerator() { + const chunkSize = 1024 * 1024; // 1MB + const numberOfChunks = 100; + + for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { + yield 'a'.repeat(chunkSize); + } + } + + const passwords = 'password'; + const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; + const plaintextMessage = await openpgp.createMessage({ text: require('stream').Readable.from(largeTextDataGenerator()) }); + assert(plaintextMessage.fromStream); + + const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); + const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); + assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket); await openpgp.decrypt({ message: encryptedMessage, - decryptionKeys: privateKey, - config: { allowUnauthenticatedStream: true } + passwords, + config: { ...config, allowUnauthenticatedStream: true } }); }); + suite.add('openpgp.encrypt/decrypt (AEAD, text @ 100MB, with streaming)', async () => { + await stream.loadStreamsPonyfill(); + + function* largeTextDataGenerator() { + const chunkSize = 1024 * 1024; // 1MB + const numberOfChunks = 100; + + for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { + yield 'a'.repeat(chunkSize); + } + } + + const passwords = 'password'; + const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; + const plaintextMessage = await openpgp.createMessage({ text: require('stream').Readable.from(largeTextDataGenerator()) }); + assert(plaintextMessage.fromStream); + + const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); + const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); + assert.ok(encryptedMessage.packets[1] instanceof openpgp.AEADEncryptedDataPacket); + await openpgp.decrypt({ message: encryptedMessage, passwords, config }); + }); + const stats = await suite.run(); // Print JSON stats to stdout console.log(JSON.stringify(stats, null, 4)); From e8319f9315b5f6666b2f2e38c962ad0979c28c60 Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Thu, 2 Jun 2022 14:22:59 +0200 Subject: [PATCH 6/9] Trigger stream decryption in all streaming tests --- test/benchmarks/memory_usage.js | 80 ++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/test/benchmarks/memory_usage.js b/test/benchmarks/memory_usage.js index 79bb2b7f5..10440418d 100644 --- a/test/benchmarks/memory_usage.js +++ b/test/benchmarks/memory_usage.js @@ -1,6 +1,5 @@ /* eslint-disable no-console */ const assert = require('assert'); -const stream = require('@openpgp/web-stream-tools'); const path = require('path'); const { writeFileSync, unlinkSync } = require('fs'); const { fork } = require('child_process'); @@ -17,7 +16,6 @@ const benchmark = async function(fn) { // the code to execute must be written to a file writeFileSync(tmpFileName, ` const assert = require('assert'); -const stream = require('@openpgp/web-stream-tools'); const openpgp = require('../..'); let maxMemoryComsumption; let activeSampling = false; @@ -156,72 +154,81 @@ class MemoryBenchamrkSuite { // streaming tests suite.add('openpgp.encrypt/decrypt (CFB, binary, with streaming)', async () => { - await stream.loadStreamsPonyfill(); - const passwords = 'password'; const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const plaintextMessage = await openpgp.createMessage({ binary: stream.toStream(new Uint8Array(1000000).fill(1)) }); + const plaintextMessage = await openpgp.createMessage({ binary: require('stream').Readable.from([new Uint8Array(1000000).fill(1)]) }); assert(plaintextMessage.fromStream); const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket); const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config }); - await stream.readToEnd(decryptedData); + // read out output stream to trigger decryption + await new Promise(resolve => { + decryptedData.pipe(require('fs').createWriteStream('/dev/null')); + decryptedData.on('end', resolve); + }); }); suite.add('openpgp.encrypt/decrypt (CFB, text, with streaming)', async () => { - await stream.loadStreamsPonyfill(); - const passwords = 'password'; const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const plaintextMessage = await openpgp.createMessage({ text: stream.toStream('a'.repeat(10000000 / 2)) }); + const plaintextMessage = await openpgp.createMessage({ text: require('stream').Readable.from(['a'.repeat(10000000 / 2)]) }); assert(plaintextMessage.fromStream); const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket); const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config }); - await stream.readToEnd(decryptedData); + // read out output stream to trigger decryption + await new Promise(resolve => { + decryptedData.pipe(require('fs').createWriteStream('/dev/null')); + decryptedData.on('end', resolve); + }); }); suite.add('openpgp.encrypt/decrypt (AEAD, binary, with streaming)', async () => { - await stream.loadStreamsPonyfill(); - const passwords = 'password'; const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const plaintextMessage = await openpgp.createMessage({ binary: stream.toStream(new Uint8Array(1000000).fill(1)) }); + const plaintextMessage = await openpgp.createMessage({ binary: require('stream').Readable.from([new Uint8Array(1000000).fill(1)]) }); assert(plaintextMessage.fromStream); const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); assert.ok(encryptedMessage.packets[1] instanceof openpgp.AEADEncryptedDataPacket); - await openpgp.decrypt({ message: encryptedMessage, passwords, config }); + const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config }); + // read out output stream to trigger decryption + await new Promise(resolve => { + decryptedData.pipe(require('fs').createWriteStream('/dev/null')); + decryptedData.on('end', resolve); + }); }); suite.add('openpgp.encrypt/decrypt (AEAD, text, with streaming)', async () => { - await stream.loadStreamsPonyfill(); - const passwords = 'password'; const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const plaintextMessage = await openpgp.createMessage({ text: stream.toStream('a'.repeat(10000000 / 2)) }); + const plaintextMessage = await openpgp.createMessage({ text: require('stream').Readable.from(['a'.repeat(10000000 / 2)]) }); assert(plaintextMessage.fromStream); const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); assert.ok(encryptedMessage.packets[1] instanceof openpgp.AEADEncryptedDataPacket); - await openpgp.decrypt({ message: encryptedMessage, passwords, config }); + const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config }); + // read out output stream to trigger decryption + await new Promise(resolve => { + decryptedData.pipe(require('fs').createWriteStream('/dev/null')); + decryptedData.on('end', resolve); + }); }); suite.add('openpgp.encrypt/decrypt (CFB, text @ 100MB, with streaming)', async () => { - await stream.loadStreamsPonyfill(); - function* largeTextDataGenerator() { const chunkSize = 1024 * 1024; // 1MB const numberOfChunks = 100; + const chunk = 'a'.repeat(chunkSize); for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { - yield 'a'.repeat(chunkSize); + yield chunk; } } @@ -233,18 +240,22 @@ class MemoryBenchamrkSuite { const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket); - await openpgp.decrypt({ message: encryptedMessage, passwords, config }); + const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config }); + // read out output stream to trigger decryption + await new Promise(resolve => { + decryptedData.pipe(require('fs').createWriteStream('/dev/null')); + decryptedData.on('end', resolve); + }); }); suite.add('openpgp.encrypt/decrypt (CFB, text @ 100MB, with unauthenticated streaming)', async () => { - await stream.loadStreamsPonyfill(); - function* largeTextDataGenerator() { const chunkSize = 1024 * 1024; // 1MB const numberOfChunks = 100; + const chunk = 'a'.repeat(chunkSize); for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { - yield 'a'.repeat(chunkSize); + yield chunk; } } @@ -256,22 +267,26 @@ class MemoryBenchamrkSuite { const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); assert.ok(encryptedMessage.packets[1] instanceof openpgp.SymEncryptedIntegrityProtectedDataPacket); - await openpgp.decrypt({ + const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config: { ...config, allowUnauthenticatedStream: true } }); + // read out output stream to trigger decryption + await new Promise(resolve => { + decryptedData.pipe(require('fs').createWriteStream('/dev/null')); + decryptedData.on('end', resolve); + }); }); suite.add('openpgp.encrypt/decrypt (AEAD, text @ 100MB, with streaming)', async () => { - await stream.loadStreamsPonyfill(); - function* largeTextDataGenerator() { const chunkSize = 1024 * 1024; // 1MB const numberOfChunks = 100; + const chunk = 'a'.repeat(chunkSize); for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { - yield 'a'.repeat(chunkSize); + yield chunk; } } @@ -283,7 +298,12 @@ class MemoryBenchamrkSuite { const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); assert.ok(encryptedMessage.packets[1] instanceof openpgp.AEADEncryptedDataPacket); - await openpgp.decrypt({ message: encryptedMessage, passwords, config }); + const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config }); + // read out output stream to trigger decryption + await new Promise(resolve => { + decryptedData.pipe(require('fs').createWriteStream('/dev/null')); + decryptedData.on('end', resolve); + }); }); const stats = await suite.run(); From f3f0403ae349bd9dbb7d89b6aea4d6a7b583ee34 Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Thu, 2 Jun 2022 15:16:21 +0200 Subject: [PATCH 7/9] Uniform input stream generation across all tests --- test/benchmarks/memory_usage.js | 82 ++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/test/benchmarks/memory_usage.js b/test/benchmarks/memory_usage.js index 10440418d..55a242567 100644 --- a/test/benchmarks/memory_usage.js +++ b/test/benchmarks/memory_usage.js @@ -109,9 +109,10 @@ class MemoryBenchamrkSuite { suite.add('empty test (baseline)', () => {}); suite.add('openpgp.encrypt/decrypt (CFB, binary)', async () => { + const ONE_MEGABYTE = 1024 * 1024; const passwords = 'password'; const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const plaintextMessage = await openpgp.createMessage({ binary: new Uint8Array(1000000).fill(1) }); + const plaintextMessage = await openpgp.createMessage({ binary: new Uint8Array(ONE_MEGABYTE) }); const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); @@ -120,9 +121,10 @@ class MemoryBenchamrkSuite { }); suite.add('openpgp.encrypt/decrypt (CFB, text)', async () => { + const ONE_MEGABYTE = 1024 * 1024; const passwords = 'password'; const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const plaintextMessage = await openpgp.createMessage({ text: 'a'.repeat(10000000 / 2) }); // two bytes per character + const plaintextMessage = await openpgp.createMessage({ text: 'a'.repeat(ONE_MEGABYTE / 2) }); // two bytes per character const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); @@ -131,9 +133,10 @@ class MemoryBenchamrkSuite { }); suite.add('openpgp.encrypt/decrypt (AEAD, binary)', async () => { + const ONE_MEGABYTE = 1024 * 1024; const passwords = 'password'; const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const plaintextMessage = await openpgp.createMessage({ binary: new Uint8Array(1000000).fill(1) }); + const plaintextMessage = await openpgp.createMessage({ binary: new Uint8Array(ONE_MEGABYTE) }); const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); @@ -142,9 +145,10 @@ class MemoryBenchamrkSuite { }); suite.add('openpgp.encrypt/decrypt (AEAD, text)', async () => { + const ONE_MEGABYTE = 1024 * 1024; const passwords = 'password'; const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const plaintextMessage = await openpgp.createMessage({ text: 'a'.repeat(10000000 / 2) }); // two bytes per character + const plaintextMessage = await openpgp.createMessage({ text: 'a'.repeat(ONE_MEGABYTE / 2) }); // two bytes per character const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); const encryptedMessage = await openpgp.readMessage({ armoredMessage: armoredEncryptedMessage }); @@ -154,9 +158,17 @@ class MemoryBenchamrkSuite { // streaming tests suite.add('openpgp.encrypt/decrypt (CFB, binary, with streaming)', async () => { + const ONE_MEGABYTE = 1024 * 1024; + function* largeDataGenerator({ chunk, numberOfChunks }) { + for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { + yield chunk; + } + } + const passwords = 'password'; const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const plaintextMessage = await openpgp.createMessage({ binary: require('stream').Readable.from([new Uint8Array(1000000).fill(1)]) }); + const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: new Uint8Array(ONE_MEGABYTE), numberOfChunks: 1 })); + const plaintextMessage = await openpgp.createMessage({ binary: inputStream }); assert(plaintextMessage.fromStream); const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); @@ -171,9 +183,17 @@ class MemoryBenchamrkSuite { }); suite.add('openpgp.encrypt/decrypt (CFB, text, with streaming)', async () => { + const ONE_MEGABYTE = 1024 * 1024; + function* largeDataGenerator({ chunk, numberOfChunks }) { + for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { + yield chunk; + } + } + const passwords = 'password'; const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const plaintextMessage = await openpgp.createMessage({ text: require('stream').Readable.from(['a'.repeat(10000000 / 2)]) }); + const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 1 })); + const plaintextMessage = await openpgp.createMessage({ text: inputStream }); assert(plaintextMessage.fromStream); const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); @@ -188,9 +208,17 @@ class MemoryBenchamrkSuite { }); suite.add('openpgp.encrypt/decrypt (AEAD, binary, with streaming)', async () => { + const ONE_MEGABYTE = 1024 * 1024; + function* largeDataGenerator({ chunk, numberOfChunks }) { + for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { + yield chunk; + } + } + const passwords = 'password'; const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const plaintextMessage = await openpgp.createMessage({ binary: require('stream').Readable.from([new Uint8Array(1000000).fill(1)]) }); + const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: new Uint8Array(ONE_MEGABYTE), numberOfChunks: 1 })); + const plaintextMessage = await openpgp.createMessage({ binary:inputStream }); assert(plaintextMessage.fromStream); const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); @@ -205,9 +233,17 @@ class MemoryBenchamrkSuite { }); suite.add('openpgp.encrypt/decrypt (AEAD, text, with streaming)', async () => { + const ONE_MEGABYTE = 1024 * 1024; + function* largeDataGenerator({ chunk, numberOfChunks }) { + for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { + yield chunk; + } + } + const passwords = 'password'; const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const plaintextMessage = await openpgp.createMessage({ text: require('stream').Readable.from(['a'.repeat(10000000 / 2)]) }); + const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 1 })); + const plaintextMessage = await openpgp.createMessage({ text: inputStream }); assert(plaintextMessage.fromStream); const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); @@ -222,11 +258,8 @@ class MemoryBenchamrkSuite { }); suite.add('openpgp.encrypt/decrypt (CFB, text @ 100MB, with streaming)', async () => { - function* largeTextDataGenerator() { - const chunkSize = 1024 * 1024; // 1MB - const numberOfChunks = 100; - - const chunk = 'a'.repeat(chunkSize); + const ONE_MEGABYTE = 1024 * 1024; + function* largeDataGenerator({ chunk, numberOfChunks }) { for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { yield chunk; } @@ -234,7 +267,8 @@ class MemoryBenchamrkSuite { const passwords = 'password'; const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const plaintextMessage = await openpgp.createMessage({ text: require('stream').Readable.from(largeTextDataGenerator()) }); + const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE), numberOfChunks: 100 })); + const plaintextMessage = await openpgp.createMessage({ text: inputStream }); assert(plaintextMessage.fromStream); const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); @@ -249,11 +283,8 @@ class MemoryBenchamrkSuite { }); suite.add('openpgp.encrypt/decrypt (CFB, text @ 100MB, with unauthenticated streaming)', async () => { - function* largeTextDataGenerator() { - const chunkSize = 1024 * 1024; // 1MB - const numberOfChunks = 100; - - const chunk = 'a'.repeat(chunkSize); + const ONE_MEGABYTE = 1024 * 1024; + function* largeDataGenerator({ chunk, numberOfChunks }) { for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { yield chunk; } @@ -261,7 +292,8 @@ class MemoryBenchamrkSuite { const passwords = 'password'; const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const plaintextMessage = await openpgp.createMessage({ text: require('stream').Readable.from(largeTextDataGenerator()) }); + const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE), numberOfChunks: 100 })); + const plaintextMessage = await openpgp.createMessage({ text: inputStream }); assert(plaintextMessage.fromStream); const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); @@ -280,11 +312,8 @@ class MemoryBenchamrkSuite { }); suite.add('openpgp.encrypt/decrypt (AEAD, text @ 100MB, with streaming)', async () => { - function* largeTextDataGenerator() { - const chunkSize = 1024 * 1024; // 1MB - const numberOfChunks = 100; - - const chunk = 'a'.repeat(chunkSize); + const ONE_MEGABYTE = 1024 * 1024; + function* largeDataGenerator({ chunk, numberOfChunks }) { for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { yield chunk; } @@ -292,7 +321,8 @@ class MemoryBenchamrkSuite { const passwords = 'password'; const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const plaintextMessage = await openpgp.createMessage({ text: require('stream').Readable.from(largeTextDataGenerator()) }); + const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE), numberOfChunks: 100 })); + const plaintextMessage = await openpgp.createMessage({ text: inputStream }); assert(plaintextMessage.fromStream); const armoredEncryptedMessage = await openpgp.encrypt({ message: plaintextMessage, passwords, config }); From 3b48d7d7eea1d0fb5d3db9e9ff4f56494f8de6db Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Thu, 2 Jun 2022 15:40:29 +0200 Subject: [PATCH 8/9] Use 1MB instead of 1MiB to allow comparison with previous benchmarks --- test/benchmarks/memory_usage.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test/benchmarks/memory_usage.js b/test/benchmarks/memory_usage.js index 55a242567..200df07dd 100644 --- a/test/benchmarks/memory_usage.js +++ b/test/benchmarks/memory_usage.js @@ -109,7 +109,7 @@ class MemoryBenchamrkSuite { suite.add('empty test (baseline)', () => {}); suite.add('openpgp.encrypt/decrypt (CFB, binary)', async () => { - const ONE_MEGABYTE = 1024 * 1024; + const ONE_MEGABYTE = 1000000; const passwords = 'password'; const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; const plaintextMessage = await openpgp.createMessage({ binary: new Uint8Array(ONE_MEGABYTE) }); @@ -121,7 +121,7 @@ class MemoryBenchamrkSuite { }); suite.add('openpgp.encrypt/decrypt (CFB, text)', async () => { - const ONE_MEGABYTE = 1024 * 1024; + const ONE_MEGABYTE = 1000000; const passwords = 'password'; const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; const plaintextMessage = await openpgp.createMessage({ text: 'a'.repeat(ONE_MEGABYTE / 2) }); // two bytes per character @@ -133,7 +133,7 @@ class MemoryBenchamrkSuite { }); suite.add('openpgp.encrypt/decrypt (AEAD, binary)', async () => { - const ONE_MEGABYTE = 1024 * 1024; + const ONE_MEGABYTE = 1000000; const passwords = 'password'; const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; const plaintextMessage = await openpgp.createMessage({ binary: new Uint8Array(ONE_MEGABYTE) }); @@ -145,7 +145,7 @@ class MemoryBenchamrkSuite { }); suite.add('openpgp.encrypt/decrypt (AEAD, text)', async () => { - const ONE_MEGABYTE = 1024 * 1024; + const ONE_MEGABYTE = 1000000; const passwords = 'password'; const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; const plaintextMessage = await openpgp.createMessage({ text: 'a'.repeat(ONE_MEGABYTE / 2) }); // two bytes per character @@ -158,7 +158,7 @@ class MemoryBenchamrkSuite { // streaming tests suite.add('openpgp.encrypt/decrypt (CFB, binary, with streaming)', async () => { - const ONE_MEGABYTE = 1024 * 1024; + const ONE_MEGABYTE = 1000000; function* largeDataGenerator({ chunk, numberOfChunks }) { for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { yield chunk; @@ -183,7 +183,7 @@ class MemoryBenchamrkSuite { }); suite.add('openpgp.encrypt/decrypt (CFB, text, with streaming)', async () => { - const ONE_MEGABYTE = 1024 * 1024; + const ONE_MEGABYTE = 1000000; function* largeDataGenerator({ chunk, numberOfChunks }) { for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { yield chunk; @@ -208,7 +208,7 @@ class MemoryBenchamrkSuite { }); suite.add('openpgp.encrypt/decrypt (AEAD, binary, with streaming)', async () => { - const ONE_MEGABYTE = 1024 * 1024; + const ONE_MEGABYTE = 1000000; function* largeDataGenerator({ chunk, numberOfChunks }) { for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { yield chunk; @@ -233,7 +233,7 @@ class MemoryBenchamrkSuite { }); suite.add('openpgp.encrypt/decrypt (AEAD, text, with streaming)', async () => { - const ONE_MEGABYTE = 1024 * 1024; + const ONE_MEGABYTE = 1000000; function* largeDataGenerator({ chunk, numberOfChunks }) { for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { yield chunk; @@ -258,7 +258,7 @@ class MemoryBenchamrkSuite { }); suite.add('openpgp.encrypt/decrypt (CFB, text @ 100MB, with streaming)', async () => { - const ONE_MEGABYTE = 1024 * 1024; + const ONE_MEGABYTE = 1000000; function* largeDataGenerator({ chunk, numberOfChunks }) { for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { yield chunk; @@ -283,7 +283,7 @@ class MemoryBenchamrkSuite { }); suite.add('openpgp.encrypt/decrypt (CFB, text @ 100MB, with unauthenticated streaming)', async () => { - const ONE_MEGABYTE = 1024 * 1024; + const ONE_MEGABYTE = 1000000; function* largeDataGenerator({ chunk, numberOfChunks }) { for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { yield chunk; @@ -312,7 +312,7 @@ class MemoryBenchamrkSuite { }); suite.add('openpgp.encrypt/decrypt (AEAD, text @ 100MB, with streaming)', async () => { - const ONE_MEGABYTE = 1024 * 1024; + const ONE_MEGABYTE = 1000000; function* largeDataGenerator({ chunk, numberOfChunks }) { for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { yield chunk; From db05571024ea36a6961b1bf7423acc08b5eee1da Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Thu, 2 Jun 2022 17:14:15 +0200 Subject: [PATCH 9/9] Reduce size and number of chunks to reduce variability below warning threshold (2%) --- test/benchmarks/memory_usage.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/benchmarks/memory_usage.js b/test/benchmarks/memory_usage.js index 200df07dd..58166105e 100644 --- a/test/benchmarks/memory_usage.js +++ b/test/benchmarks/memory_usage.js @@ -257,7 +257,7 @@ class MemoryBenchamrkSuite { }); }); - suite.add('openpgp.encrypt/decrypt (CFB, text @ 100MB, with streaming)', async () => { + suite.add('openpgp.encrypt/decrypt (CFB, text @ 10MB, with streaming)', async () => { const ONE_MEGABYTE = 1000000; function* largeDataGenerator({ chunk, numberOfChunks }) { for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { @@ -267,7 +267,7 @@ class MemoryBenchamrkSuite { const passwords = 'password'; const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE), numberOfChunks: 100 })); + const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 20 })); const plaintextMessage = await openpgp.createMessage({ text: inputStream }); assert(plaintextMessage.fromStream); @@ -282,7 +282,7 @@ class MemoryBenchamrkSuite { }); }); - suite.add('openpgp.encrypt/decrypt (CFB, text @ 100MB, with unauthenticated streaming)', async () => { + suite.add('openpgp.encrypt/decrypt (CFB, text @ 10MB, with unauthenticated streaming)', async () => { const ONE_MEGABYTE = 1000000; function* largeDataGenerator({ chunk, numberOfChunks }) { for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { @@ -292,7 +292,7 @@ class MemoryBenchamrkSuite { const passwords = 'password'; const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE), numberOfChunks: 100 })); + const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 20 })); const plaintextMessage = await openpgp.createMessage({ text: inputStream }); assert(plaintextMessage.fromStream); @@ -311,7 +311,7 @@ class MemoryBenchamrkSuite { }); }); - suite.add('openpgp.encrypt/decrypt (AEAD, text @ 100MB, with streaming)', async () => { + suite.add('openpgp.encrypt/decrypt (AEAD, text @ 10MB, with streaming)', async () => { const ONE_MEGABYTE = 1000000; function* largeDataGenerator({ chunk, numberOfChunks }) { for (let chunkNumber = 0; chunkNumber < numberOfChunks; chunkNumber++) { @@ -321,7 +321,7 @@ class MemoryBenchamrkSuite { const passwords = 'password'; const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed }; - const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE), numberOfChunks: 100 })); + const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 20 })); const plaintextMessage = await openpgp.createMessage({ text: inputStream }); assert(plaintextMessage.fromStream);