diff --git a/src/encoding/armor.js b/src/encoding/armor.js
index a18dce6d..713ead63 100644
--- a/src/encoding/armor.js
+++ b/src/encoding/armor.js
@@ -207,7 +207,6 @@ function dearmor(input) {
       const reEmptyLine = /^[ \f\r\t\u00a0\u2000-\u200a\u202f\u205f\u3000]*$/;
 
       const reader = stream.getReader(input);
-      let lineIndex = 0;
       let type;
       const headers = [];
       let lastHeaders = headers;
@@ -232,13 +231,13 @@ function dearmor(input) {
       });
       while (true) {
         let line = await reader.readLine();
-        if (!line) break;
-        if (lineIndex++ === 0) {
-          // trim string
-          line = line.trim();
+        if (line === undefined) {
+          controller.error('Misformed armored text');
+          break;
         }
         // remove trailing whitespace at end of lines
-        line = line.replace(/[\t\r\n ]+$/g, '');
+        // remove leading whitespace for compat with older versions of OpenPGP.js
+        line = line.trim();
         if (!type) {
           if (reSplit.test(line)) {
             type = getType(line);
@@ -257,7 +256,7 @@ function dearmor(input) {
         } else if (!textDone && type === 2) {
           if (!reSplit.test(line)) {
             // Reverse dash-escaping for msg
-            text.push(line.replace(/^- /mg, ''));
+            text.push(line.replace(/^- /, ''));
           } else {
             text = text.join('\r\n');
             textDone = true;
diff --git a/src/openpgp.js b/src/openpgp.js
index c0fde9f4..a330771e 100644
--- a/src/openpgp.js
+++ b/src/openpgp.js
@@ -127,15 +127,15 @@ export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpira
     return asyncProxy.delegate('generateKey', options);
   }
 
-  return generate(options).then(key => {
+  return generate(options).then(async key => {
     const revocationCertificate = key.getRevocationCertificate();
     key.revocationSignatures = [];
 
     return {
 
       key: key,
-      privateKeyArmored: key.armor(),
-      publicKeyArmored: key.toPublic().armor(),
+      privateKeyArmored: await stream.readToEnd(key.armor()),
+      publicKeyArmored: await stream.readToEnd(key.toPublic().armor())
       revocationCertificate: revocationCertificate
 
     };
@@ -453,6 +453,9 @@ export function verify({ message, publicKeys, signature=null, date=new Date() })
   return Promise.resolve().then(async function() {
     const result = {};
     result.data = message instanceof CleartextMessage ? message.getText() : message.getLiteralData();
+    if (!message.fromStream) {
+      result.data = await stream.readToEnd(result.data);
+    }
     result.signatures = signature ?
       await message.verifyDetached(signature, publicKeys, date) :
       await message.verify(publicKeys, date);
diff --git a/src/packet/packet.js b/src/packet/packet.js
index d7fd4f4a..b6fbcbf7 100644
--- a/src/packet/packet.js
+++ b/src/packet/packet.js
@@ -235,14 +235,15 @@ export default {
         if (bodydata === null) {
           bodydata = await reader.readBytes(packet_length);
 
+          const peekedByte = await reader.peekBytes(1);
           resolve({
             tag: tag,
             packet: bodydata,
-            done: !await reader.peekBytes(1)
+            done: !(peekedByte && peekedByte.length)
           });
         } else {
-          const { done } = await reader.read();
-          if (!done) {
+          const { done, value } = await reader.read();
+          if (!done && value.length) {
             throw new Error('Packets after a packet with partial lengths are not supported');
           } else {
             controller.close();
diff --git a/src/packet/sym_encrypted_aead_protected.js b/src/packet/sym_encrypted_aead_protected.js
index 2775ae5c..33e51fa1 100644
--- a/src/packet/sym_encrypted_aead_protected.js
+++ b/src/packet/sym_encrypted_aead_protected.js
@@ -165,7 +165,7 @@ SymEncryptedAEADProtected.prototype.crypt = async function (fn, key, data) {
         cryptedBytes += chunk.length - tagLengthIfDecrypting;
         queuedBytes += chunk.length - tagLengthIfDecrypting;
         latestPromise = latestPromise.then(() => cryptedPromise).then(crypted => {
-          if (crypted.length) controller.enqueue(crypted);
+          controller.enqueue(crypted);
           queuedBytes -= chunk.length;
         }).catch(err => controller.error(err));
         // console.log(fn, done, queuedBytes, controller.desiredSize);
@@ -181,6 +181,6 @@ SymEncryptedAEADProtected.prototype.crypt = async function (fn, key, data) {
       }
     });
   } else {
-    return modeInstance[fn](data, this.iv);
+    return modeInstance[fn](await stream.readToEnd(data), this.iv);
   }
 };
diff --git a/src/util.js b/src/util.js
index ba0052ec..2c951914 100644
--- a/src/util.js
+++ b/src/util.js
@@ -53,33 +53,38 @@ export default {
   /**
    * Get transferable objects to pass buffers with zero copy (similar to "pass by reference" in C++)
    *   See: https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage
+   * Also, convert ReadableStreams to Uint8Arrays
    * @param  {Object} obj           the options object to be passed to the web worker
    * @returns {Array<ArrayBuffer>}   an array of binary data to be passed
    */
-  getTransferables: function(obj) {
+  prepareBuffers: async function(obj) {
     // Internet Explorer does not support Transferable objects.
     if (isIE11) {
       return undefined;
     }
-    if (config.zero_copy && Object.prototype.isPrototypeOf(obj)) {
-      const transferables = [];
-      util.collectBuffers(obj, transferables);
-      return transferables.length ? transferables : undefined;
-    }
+    const transferables = [];
+    await util.collectBuffers(obj, transferables);
+    return transferables.length ? transferables : undefined;
   },
 
-  collectBuffers: function(obj, collection) {
+  collectBuffers: async function(obj, collection) {
     if (!obj) {
       return;
     }
+
     if (util.isUint8Array(obj) && collection.indexOf(obj.buffer) === -1) {
-      collection.push(obj.buffer);
+      if (config.zero_copy) {
+        collection.push(obj.buffer);
+      }
       return;
     }
     if (Object.prototype.isPrototypeOf(obj)) {
-      Object.values(obj).forEach(value => { // recursively search all children
-        util.collectBuffers(value, collection);
-      });
+      await Promise.all(Object.entries(obj).map(async ([key, value]) => { // recursively search all children
+        if (util.isStream(value)) {
+          obj[key] = value = await stream.readToEnd(value);
+        }
+        await util.collectBuffers(value, collection);
+      }));
     }
   },
 
diff --git a/src/worker/async_proxy.js b/src/worker/async_proxy.js
index f1c767d7..9b82d07b 100644
--- a/src/worker/async_proxy.js
+++ b/src/worker/async_proxy.js
@@ -112,7 +112,7 @@ AsyncProxy.prototype.getID = function() {
  */
 AsyncProxy.prototype.seedRandom = async function(workerId, size) {
   const buf = await crypto.random.getRandomBytes(size);
-  this.workers[workerId].postMessage({ event:'seed-random', buf }, util.getTransferables(buf));
+  this.workers[workerId].postMessage({ event:'seed-random', buf }, await util.prepareBuffers(buf));
 };
 
 /**
@@ -143,9 +143,10 @@ AsyncProxy.prototype.delegate = function(method, options) {
     }
   }
 
-  return new Promise((resolve, reject) => {
+  return new Promise(async (resolve, reject) => {
     // clone packets (for web worker structured cloning algorithm)
-    this.workers[workerId].postMessage({ id:id, event:method, options:packet.clone.clonePackets(options) }, util.getTransferables(options));
+    const transferables = await util.prepareBuffers(options);
+    this.workers[workerId].postMessage({ id:id, event:method, options:packet.clone.clonePackets(options) }, transferables);
     this.workers[workerId].requests++;
 
     // remember to handle parsing cloned packets from worker
diff --git a/test/general/armor.js b/test/general/armor.js
index 518292ff..f2b6c5a6 100644
--- a/test/general/armor.js
+++ b/test/general/armor.js
@@ -112,18 +112,18 @@ describe("ASCII armor", function() {
     await expect(msg).to.be.rejectedWith(Error, /Improperly formatted armor header/);
   });
 
-  it('Exception if improperly formatted armor header - signature section', function () {
-    [' Space: leading', 'Space : trailing', 'Space :switched', ': empty', 'none', 'Space:missing'].forEach(function (invalidHeader) {
-      expect(openpgp.cleartext.readArmored(getArmor(['Hash: SHA1'], [invalidHeader]))).to.be.rejectedWith(Error, /Improperly formatted armor header/);
-    });
+  it('Exception if improperly formatted armor header - signature section', async function () {
+    await Promise.all(['Space : trailing', 'Space :switched', ': empty', 'none', 'Space:missing'].map(async function (invalidHeader) {
+      await expect(openpgp.cleartext.readArmored(getArmor(['Hash: SHA1'], [invalidHeader]))).to.be.rejectedWith(Error, /Improperly formatted armor header/);
+    }));
   });
 
   it('Ignore unknown armor header - signature section', async function () {
     const validHeaders = ['Version: BCPG C# v1.7.4114.6375', 'Independent Reserve Pty. Ltd. 2017: 1.0.0.0'];
     expect(await openpgp.cleartext.readArmored(getArmor(['Hash: SHA1'], validHeaders))).to.be.an.instanceof(openpgp.cleartext.CleartextMessage);
-    ['A: Hello', 'Ab: 1.2.3', 'Abcd: #!/yah', 'Acd 123 5.6.$.8: Hello', '_: Hello', '*: Hello', '* & ## ?? ()(): Hello', '( ): Weird'].forEach(async function (validHeader) {
+    await Promise.all(['A: Hello', 'Ab: 1.2.3', 'Abcd: #!/yah', 'Acd 123 5.6.$.8: Hello', '_: Hello', '*: Hello', '* & ## ?? ()(): Hello', '( ): Weird'].map(async function (validHeader) {
       expect(await openpgp.cleartext.readArmored(getArmor(['Hash: SHA1'], [validHeader]))).to.be.an.instanceof(openpgp.cleartext.CleartextMessage);
-    });
+    }));
   });
 
   it('Exception if wrong armor header type', async function () {
diff --git a/test/general/key.js b/test/general/key.js
index 88b7e97f..7d2cbf05 100644
--- a/test/general/key.js
+++ b/test/general/key.js
@@ -346,7 +346,7 @@ zoGJ6s48HcP591pN93uAitCcYcinY2ZslmdiCXw+zbeoX4spNrV4T4CYxBjNQdIa
       'BIDZSFjrJY/gm2kgQX2Pn9hGqDdGhxiALjxhA0+OJQNw4v11y0zVGdofh0IHjkcZ',
       'onCOcv4DKguN2w==',
       '=OqO3',
-      '-----END PGP PUBLIC KEY BLOCK----'].join('\n');
+      '-----END PGP PUBLIC KEY BLOCK-----'].join('\n');
 
   const pub_v3 =
       ['-----BEGIN PGP PUBLIC KEY BLOCK-----',
@@ -1731,7 +1731,7 @@ t/ia1kMpSEiOVLlX5dfHZzhR3WNtBqU=
     };
     const opt = {numBits: 512, userIds: 'test <a@b.com>', passphrase: 'hello'};
     if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
-    return openpgp.generateKey(opt).then(function(key) {
+    return openpgp.generateKey(opt).then(async function(key) {
       testPref(key.key);
       testPref((await openpgp.key.readArmored(key.publicKeyArmored)).keys[0]);
     });
@@ -1877,8 +1877,8 @@ VYGdb3eNlV8CfoEC
     const opt = {numBits: 512, userIds: userId, passphrase: 'passphrase'};
     if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
     const key = (await openpgp.generateKey(opt)).key;
-    const armor1 = key.armor();
-    const armor2 = key.armor();
+    const armor1 = await openpgp.stream.readToEnd(key.armor());
+    const armor2 = await openpgp.stream.readToEnd(key.armor());
     expect(armor1).to.equal(armor2);
     expect(await key.decrypt('passphrase')).to.be.true;
     expect(key.isDecrypted()).to.be.true;
@@ -1888,7 +1888,7 @@ VYGdb3eNlV8CfoEC
     expect(key.isDecrypted()).to.be.false;
     expect(await key.decrypt('new_passphrase')).to.be.true;
     expect(key.isDecrypted()).to.be.true;
-    const armor3 = key.armor();
+    const armor3 = await openpgp.stream.readToEnd(key.armor());
     expect(armor3).to.not.equal(armor1);
   });
 
diff --git a/test/general/packet.js b/test/general/packet.js
index 3516af72..6459dd01 100644
--- a/test/general/packet.js
+++ b/test/general/packet.js
@@ -249,10 +249,10 @@ describe("Packet", function() {
 
     return parsed[0].decrypt('test').then(() => {
       const key = parsed[0].sessionKey;
-      return parsed[1].decrypt(parsed[0].sessionKeyAlgorithm, key).then(() => {
+      return parsed[1].decrypt(parsed[0].sessionKeyAlgorithm, key).then(async () => {
         const compressed = parsed[1].packets[0];
 
-        const result = stringify(compressed.packets[0].data);
+        const result = await stringify(compressed.packets[0].data);
 
         expect(result).to.equal('Hello world!\n');
       });
@@ -393,7 +393,7 @@ describe("Packet", function() {
     return msg[0].decrypt(key).then(async () => {
       await msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey);
 
-      const text = stringify(msg[1].packets[0].packets[0].data);
+      const text = await stringify(msg[1].packets[0].packets[0].data);
 
       expect(text).to.equal('Hello world!');
     });
@@ -654,7 +654,7 @@ describe("Packet", function() {
     return msg[0].decrypt(key).then(async () => {
       await msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey);
 
-      const text = stringify(msg[1].packets[0].packets[0].data);
+      const text = await stringify(msg[1].packets[0].packets[0].data);
 
       expect(text).to.equal('Hello world!');
     });
diff --git a/test/general/signature.js b/test/general/signature.js
index d58c2d05..51b5c7fe 100644
--- a/test/general/signature.js
+++ b/test/general/signature.js
@@ -523,7 +523,7 @@ describe("Signature", function() {
     expect(pubKey2.getKeys(keyids[1])).to.not.be.empty;
     expect(pubKey3.getKeys(keyids[0])).to.not.be.empty;
 
-    expect(sMsg.getText()).to.equal(plaintext);
+    expect(await openpgp.stream.readToEnd(sMsg.getText())).to.equal(plaintext);
 
     return sMsg.verify([pubKey2, pubKey3]).then(verifiedSig => {
       expect(verifiedSig).to.exist;
@@ -720,7 +720,7 @@ yYDnCgA=
       const csMsg = await openpgp.message.readArmored(signed.data);
       return openpgp.verify({ publicKeys:[pubKey], message:csMsg });
 
-    }).then(function(cleartextSig) {
+    }).then(async function(cleartextSig) {
       expect(cleartextSig).to.exist;
       expect(cleartextSig.data).to.deep.equal(plaintext);
       expect(cleartextSig.signatures).to.have.length(1);
diff --git a/test/general/util.js b/test/general/util.js
index a611590a..67e36647 100644
--- a/test/general/util.js
+++ b/test/general/util.js
@@ -116,7 +116,7 @@ describe('Util unit tests', function() {
     });
   });
 
-  describe('getTransferables', function() {
+  describe('prepareBuffers', function() {
     let zero_copyVal;
     const buf1 = new Uint8Array(1);
     const buf2 = new Uint8Array(1);
@@ -137,18 +137,18 @@ describe('Util unit tests', function() {
       openpgp.config.zero_copy = zero_copyVal;
     });
 
-    it('should return undefined when zero_copy is false', function() {
+    it('should return undefined when zero_copy is false', async function() {
       openpgp.config.zero_copy = false;
-      expect(openpgp.util.getTransferables(obj)).to.be.undefined;
+      expect(await openpgp.util.prepareBuffers(obj)).to.be.undefined;
     });
-    it('should return undefined for no input', function() {
-      expect(openpgp.util.getTransferables()).to.be.undefined;
+    it('should return undefined for no input', async function() {
+      expect(await openpgp.util.prepareBuffers()).to.be.undefined;
     });
-    it('should return undefined for an empty oject', function() {
-      expect(openpgp.util.getTransferables({})).to.be.undefined;
+    it('should return undefined for an empty oject', async function() {
+      expect(await openpgp.util.prepareBuffers({})).to.be.undefined;
     });
-    it('should return two buffers', function() {
-      expect(openpgp.util.getTransferables(obj)).to.deep.equal([buf1.buffer, buf2.buffer]);
+    it('should return two buffers', async function() {
+      expect(await openpgp.util.prepareBuffers(obj)).to.deep.equal([buf1.buffer, buf2.buffer]);
     });
   });