diff --git a/js/privatebin.js b/js/privatebin.js
index 5b3d9266..560d6e7f 100644
--- a/js/privatebin.js
+++ b/js/privatebin.js
@@ -701,17 +701,25 @@ jQuery.PrivateBin = (function($, RawDeflate) {
          * @private
          * @param  {string} key
          * @param  {string} password
-         * @param  {object} object cryptographic message
+         * @param  {array}  spec cryptographic specification
          * @return {CryptoKey} derived key
          */
-        async function deriveKey(key, password, object)
+        async function deriveKey(key, password, spec)
         {
             let keyArray = StrToArr(key);
             if ((password || '').trim().length > 0) {
-                keyArray += await window.crypto.subtle.digest(
+                let passwordBuffer = await window.crypto.subtle.digest(
                     {name: 'SHA-256'},
-                    StrToArr(password)
+                    StrToArr(utob(password))
                 );
+                let hexHash = Array.prototype.map.call(
+                    new Uint8Array(passwordBuffer), x => ('00' + x.toString(16)).slice(-2)
+                ).join('');
+                let passwordArray = StrToArr(hexHash),
+                    newKeyArray = new Uint8Array(keyArray.length + passwordArray.length);
+                newKeyArray.set(keyArray, 0);
+                newKeyArray.set(passwordArray, keyArray.length);
+                keyArray = newKeyArray;
             }
 
             // import raw key
@@ -724,39 +732,40 @@ jQuery.PrivateBin = (function($, RawDeflate) {
             );
 
             // derive a stronger key for use with AES
-            return await window.crypto.subtle.deriveKey(
+            return window.crypto.subtle.deriveKey(
                 {
                     name: 'PBKDF2', // we use PBKDF2 for key derivation
-                    salt: StrToArr(atob(object.salt)), // salt used in HMAC
-                    iterations: object.iter, // amount of iterations to apply
+                    salt: StrToArr(spec[1]), // salt used in HMAC
+                    iterations: spec[2], // amount of iterations to apply
                     hash: {name: 'SHA-256'} // can be "SHA-1", "SHA-256", "SHA-384" or "SHA-512"
                 },
                 importedKey,
                 {
-                    name: 'AES-' + object.mode.toUpperCase(), // can be any supported AES algorithm ("AES-CTR", "AES-CBC", "AES-CMAC", "AES-GCM", "AES-CFB", "AES-KW", "ECDH", "DH" or "HMAC")
-                    length: object.ks // can be 128, 192 or 256
+                    name: 'AES-' + spec[6].toUpperCase(), // can be any supported AES algorithm ("AES-CTR", "AES-CBC", "AES-CMAC", "AES-GCM", "AES-CFB", "AES-KW", "ECDH", "DH" or "HMAC")
+                    length: spec[3] // can be 128, 192 or 256
                 },
                 false, // the key may not be exported
-                ['encrypt'] // we may only use it for decryption
+                ['encrypt', 'decrypt'] // we use it for de- and encryption
             );
         }
 
         /**
-         * gets crypto settings from given object
+         * gets crypto settings from specification and authenticated data
          *
          * @name   CryptTool.cryptoSettings
          * @function
          * @private
-         * @param  {object} object cryptographic message
+         * @param  {string} adata authenticated data
+         * @param  {array}  spec cryptographic specification
          * @return {object} crypto settings
          */
-        function cryptoSettings(object)
+        function cryptoSettings(adata, spec)
         {
             return {
-                name: 'AES-' + object.mode.toUpperCase(), // can be any supported AES algorithm ("AES-CTR", "AES-CBC", "AES-CMAC", "AES-GCM", "AES-CFB", "AES-KW", "ECDH", "DH" or "HMAC")
-                iv: StrToArr(atob(object.iv)), // the initialization vector you used to encrypt
-                additionalData: StrToArr(atob(object.adata)), // the addtional data you used during encryption (if any)
-                tagLength: object.ts // the length of the tag you used to encrypt (if any)
+                name: 'AES-' + spec[6].toUpperCase(), // can be any supported AES algorithm ("AES-CTR", "AES-CBC", "AES-CMAC", "AES-GCM", "AES-CFB", "AES-KW", "ECDH", "DH" or "HMAC")
+                iv: StrToArr(spec[0]), // the initialization vector you used to encrypt
+                additionalData: StrToArr(adata), // the addtional data you used during encryption (if any)
+                tagLength: spec[4] // the length of the tag you used to encrypt (if any)
             };
         }
 
@@ -769,32 +778,53 @@ jQuery.PrivateBin = (function($, RawDeflate) {
          * @param  {string} key
          * @param  {string} password
          * @param  {string} message
-         * @return {string} data - JSON with encrypted data
+         * @param  {array}  adata
+         * @return {array}  encrypted message & adata containing encryption spec
          */
-        me.cipher = async function(key, password, message)
+        me.cipher = async function(key, password, message, adata)
         {
-            // AES in Galois Counter Mode, keysize 256 bit, authentication tag 128 bit, 10000 iterations in key derivation
-            const iv     = getRandomBytes(16);
-            let object   = {
-                    iv:     btoa(iv),
-                    v:      1,
-                    iter:   10000,
-                    ks:     256,
-                    ts:     128,
-                    mode:   'gcm',
-                    adata:  '', // if used, base64 encode it with btoa()
-                    cipher: 'aes',
-                    salt:   btoa(getRandomBytes(8))
-                };
+            // AES in Galois Counter Mode, keysize 256 bit,
+            // authentication tag 128 bit, 10000 iterations in key derivation
+            const spec = [
+                getRandomBytes(16), // initialization vector
+                getRandomBytes(8),  // salt
+                10000,              // iterations
+                256,                // key size
+                128,                // tag size
+                'aes',              // algorithm
+                'gcm',              // algorithm mode
+                'none'              // compression
+            ], encodedSpec = [
+                btoa(spec[0]),
+                btoa(spec[1]),
+                spec[2],
+                spec[3],
+                spec[4],
+                spec[5],
+                spec[6],
+                spec[7]
+            ];
+            if (adata.length === 0) {
+                // comment
+                adata = encodedSpec;
+            } else if (adata[0] === null) {
+                // paste
+                adata[0] = encodedSpec;
+            }
 
             // finally, encrypt message
-            const encrypted = await window.crypto.subtle.encrypt(
-                cryptoSettings(object),
-                await deriveKey(key, password, object),
-                StrToArr(compress(message)) // compressed plain text to encrypt
-            );
-            object.ct = btoa(ArrToStr(encrypted));
-            return JSON.stringify(object);
+            return [
+                btoa(
+                    ArrToStr(
+                        await window.crypto.subtle.encrypt(
+                            cryptoSettings(JSON.stringify(adata), spec),
+                            await deriveKey(key, password, spec),
+                            StrToArr(utob(message))
+                        )
+                    )
+                ),
+                adata
+            ];
         };
 
         /**
@@ -805,25 +835,57 @@ jQuery.PrivateBin = (function($, RawDeflate) {
          * @function
          * @param  {string} key
          * @param  {string} password
-         * @param  {string} data - JSON with encrypted data
+         * @param  {string|object} data encrypted message
          * @return {string} decrypted message, empty if decryption failed
          */
         me.decipher = async function(key, password, data)
         {
+            let adataString, encodedSpec, compression, cipherMessage;
+            if (data instanceof Array) {
+                // version 2
+                adataString = JSON.stringify(data[1]);
+                encodedSpec = (data[1][0] instanceof Array ? data[1][0] : data[1]);
+                cipherMessage = data[0];
+            } else if (typeof data === 'string') {
+                // version 1
+                let object = JSON.parse(data);
+                adataString = atob(object.adata);
+                encodedSpec = [
+                    object.iv,
+                    object.salt,
+                    object.iter,
+                    object.ks,
+                    object.ts,
+                    object.cipher,
+                    object.mode,
+                    'rawdeflate'
+                ];
+                cipherMessage = object.ct;
+            } else {
+                throw 'unsupported message format';
+            }
+            compression = encodedSpec[7];
+            let spec = encodedSpec, plainText = '';
+            spec[0] = atob(spec[0]);
+            spec[1] = atob(spec[1]);
             try {
-                const object = JSON.parse(data);
-                return decompress(
-                    ArrToStr(
-                        await window.crypto.subtle.decrypt(
-                            cryptoSettings(object),
-                            await deriveKey(key, password, object),
-                            StrToArr(atob(object.ct)) // cipher text to decrypt
-                        )
+                plainText = ArrToStr(
+                    await window.crypto.subtle.decrypt(
+                        cryptoSettings(adataString, spec),
+                        await deriveKey(key, password, spec),
+                        StrToArr(atob(cipherMessage))
                     )
                 );
             } catch(err) {
                 return '';
             }
+            if (compression === 'none') {
+                return btou(plainText);
+            } else if (compression === 'rawdeflate') {
+                return decompress(plainText);
+            } else {
+                throw 'unsupported compression format';
+            }
         };
 
         /**
@@ -906,25 +968,25 @@ jQuery.PrivateBin = (function($, RawDeflate) {
             }
 
             // reload data
-            Uploader.prepare();
-            Uploader.setUrl(Helper.baseUri() + '?' + me.getPasteId());
+            ServerInteraction.prepare();
+            ServerInteraction.setUrl(Helper.baseUri() + '?' + me.getPasteId());
 
-            Uploader.setFailure(function (status, data) {
+            ServerInteraction.setFailure(function (status, data) {
                 // revert loading status…
                 Alert.hideLoading();
                 TopNav.showViewButtons();
 
                 // show error message
-                Alert.showError(Uploader.parseUploadError(status, data, 'get paste data'));
+                Alert.showError(ServerInteraction.parseUploadError(status, data, 'get paste data'));
             });
-            Uploader.setSuccess(function (status, data) {
+            ServerInteraction.setSuccess(function (status, data) {
                 pasteData = data;
 
                 if (typeof callback === 'function') {
                     return callback(data);
                 }
             });
-            Uploader.run();
+            ServerInteraction.run();
         };
 
         /**
@@ -1290,7 +1352,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
          */
         me.showStatus = function(message, icon)
         {
-            console.info('status shown: ', message);
             handleNotification(1, $statusMessage, message, icon);
         };
 
@@ -1307,7 +1368,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
          */
         me.showError = function(message, icon)
         {
-            console.error('error message shown: ', message);
             handleNotification(3, $errorMessage, message, icon);
         };
 
@@ -1322,7 +1382,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
          */
         me.showRemaining = function(message)
         {
-            console.info('remaining message shown: ', message);
             handleNotification(1, $remainingTime, message);
         };
 
@@ -1338,10 +1397,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
          */
         me.showLoading = function(message, icon)
         {
-            if (typeof message !== 'undefined' && message !== null) {
-                console.info('status changed: ', message);
-            }
-
             // default message text
             if (typeof message === 'undefined') {
                 message = 'Loading…';
@@ -2132,7 +2187,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
         me.hide = function()
         {
             if (!isDisplayed) {
-                console.warn('PasteViewer was called to hide the parsed view, but it is already hidden.');
+                return;
             }
 
             $plainText.addClass('hidden');
@@ -3184,7 +3239,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
         me.showViewButtons = function()
         {
             if (viewButtonsDisplayed) {
-                console.warn('showViewButtons: view buttons are already displayed');
                 return;
             }
 
@@ -3205,7 +3259,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
         me.hideViewButtons = function()
         {
             if (!viewButtonsDisplayed) {
-                console.warn('hideViewButtons: view buttons are already hidden');
                 return;
             }
 
@@ -3238,7 +3291,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
         me.showCreateButtons = function()
         {
             if (createButtonsDisplayed) {
-                console.warn('showCreateButtons: create buttons are already displayed');
                 return;
             }
 
@@ -3263,7 +3315,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
         me.hideCreateButtons = function()
         {
             if (!createButtonsDisplayed) {
-                console.warn('hideCreateButtons: create buttons are already hidden');
                 return;
             }
 
@@ -3534,23 +3585,23 @@ jQuery.PrivateBin = (function($, RawDeflate) {
     /**
      * Responsible for AJAX requests, transparently handles encryption…
      *
-     * @name   Uploader
+     * @name   ServerInteraction
      * @class
      */
-    var Uploader = (function () {
+    var ServerInteraction = (function () {
         var me = {};
 
         var successFunc = null,
             failureFunc = null,
+            symmetricKey = null,
             url,
             data,
-            symmetricKey,
             password;
 
         /**
          * public variable ('constant') for errors to prevent magic numbers
          *
-         * @name   Uploader.error
+         * @name   ServerInteraction.error
          * @readonly
          * @enum   {Object}
          */
@@ -3564,7 +3615,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
         /**
          * ajaxHeaders to send in AJAX requests
          *
-         * @name   Uploader.ajaxHeaders
+         * @name   ServerInteraction.ajaxHeaders
          * @private
          * @readonly
          * @enum   {Object}
@@ -3574,40 +3625,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
         /**
          * called after successful upload
          *
-         * @name   Uploader.checkCryptParameters
-         * @private
-         * @function
-         * @throws {string}
-         */
-        function checkCryptParameters()
-        {
-            // workaround for this nasty 'bug' in ECMAScript
-            // see https://stackoverflow.com/questions/18808226/why-is-typeof-null-object
-            var typeOfKey = typeof symmetricKey;
-            if (symmetricKey === null) {
-                typeOfKey = 'null';
-            }
-
-            // in case of missing preparation, throw error
-            switch (typeOfKey) {
-                case 'string':
-                    // already set, all right
-                    return;
-                case 'null':
-                    // needs to be generated auto-generate
-                    symmetricKey = CryptTool.getSymmetricKey();
-                    break;
-                default:
-                    console.error('current invalid symmetricKey: ', symmetricKey);
-                    throw 'symmetricKey is invalid, probably the module was not prepared';
-            }
-            // password is optional
-        }
-
-        /**
-         * called after successful upload
-         *
-         * @name   Uploader.success
+         * @name   ServerInteraction.success
          * @private
          * @function
          * @param {int} status
@@ -3627,7 +3645,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
         /**
          * called after a upload failure
          *
-         * @name   Uploader.fail
+         * @name   ServerInteraction.fail
          * @private
          * @function
          * @param {int} status - internal code
@@ -3643,13 +3661,13 @@ jQuery.PrivateBin = (function($, RawDeflate) {
         /**
          * actually uploads the data
          *
-         * @name   Uploader.run
+         * @name   ServerInteraction.run
          * @function
          */
         me.run = function()
         {
             $.ajax({
-                type: 'POST',
+                type: data ? 'POST' : 'GET',
                 url: url,
                 data: data,
                 dataType: 'json',
@@ -3673,7 +3691,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
         /**
          * set success function
          *
-         * @name   Uploader.setUrl
+         * @name   ServerInteraction.setUrl
          * @function
          * @param {function} newUrl
          */
@@ -3684,11 +3702,11 @@ jQuery.PrivateBin = (function($, RawDeflate) {
 
         /**
          * sets the password to use (first value) and optionally also the
-         * encryption key (not recommend, it is automatically generated).
+         * encryption key (not recommended, it is automatically generated).
          *
          * Note: Call this after prepare() as prepare() resets these values.
          *
-         * @name   Uploader.setCryptValues
+         * @name   ServerInteraction.setCryptValues
          * @function
          * @param {string} newPassword
          * @param {string} newKey       - optional
@@ -3705,7 +3723,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
         /**
          * set success function
          *
-         * @name   Uploader.setSuccess
+         * @name   ServerInteraction.setSuccess
          * @function
          * @param {function} func
          */
@@ -3717,7 +3735,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
         /**
          * set failure function
          *
-         * @name   Uploader.setFailure
+         * @name   ServerInteraction.setFailure
          * @function
          * @param {function} func
          */
@@ -3733,7 +3751,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
          * previous uploads. Must be called before any other method of this
          * module.
          *
-         * @name   Uploader.prepare
+         * @name   ServerInteraction.prepare
          * @function
          * @return {object}
          */
@@ -3757,22 +3775,33 @@ jQuery.PrivateBin = (function($, RawDeflate) {
         /**
          * encrypts and sets the data
          *
-         * @name   Uploader.setData
+         * @name   ServerInteraction.setCipherMessage
          * @async
          * @function
-         * @param {string} index
-         * @param {mixed} element
+         * @param {object} cipherMessage
          */
-        me.setData = async function(index, element)
+        me.setCipherMessage = async function(cipherMessage)
         {
-            checkCryptParameters();
-            data[index] = await CryptTool.cipher(symmetricKey, password, element);
+            if (
+                symmetricKey === null ||
+                (typeof symmetricKey === 'string' && symmetricKey === '')
+            ) {
+                symmetricKey = CryptTool.getSymmetricKey();
+            }
+            if (!data.hasOwnProperty('adata')) {
+                data['adata'] = [];
+            }
+            let cipherResult = await CryptTool.cipher(symmetricKey, password, JSON.stringify(cipherMessage), data['adata']);
+            data['v'] = 2;
+            data['ct'] = cipherResult[0];
+            data['adata'] = cipherResult[1];
+
         };
 
         /**
          * set the additional metadata to send unencrypted
          *
-         * @name   Uploader.setUnencryptedData
+         * @name   ServerInteraction.setUnencryptedData
          * @function
          * @param {string} index
          * @param {mixed} element
@@ -3783,21 +3812,9 @@ jQuery.PrivateBin = (function($, RawDeflate) {
         };
 
         /**
-         * set the additional metadata to send unencrypted passed at once
+         * Helper, which parses shows a general error message based on the result of the ServerInteraction
          *
-         * @name   Uploader.setUnencryptedData
-         * @function
-         * @param {object} newData
-         */
-        me.setUnencryptedBulkData = function(newData)
-        {
-            $.extend(data, newData);
-        };
-
-        /**
-         * Helper, which parses shows a general error message based on the result of the Uploader
-         *
-         * @name    Uploader.parseUploadError
+         * @name    ServerInteraction.parseUploadError
          * @function
          * @param {int} status
          * @param {object} data
@@ -3825,24 +3842,13 @@ jQuery.PrivateBin = (function($, RawDeflate) {
             return errorArray;
         };
 
-        /**
-         * init Uploader
-         *
-         * @name   Uploader.init
-         * @function
-         */
-        me.init = function()
-        {
-            // nothing yet
-        };
-
         return me;
     })();
 
     /**
      * (controller) Responsible for encrypting paste and sending it to server.
      *
-     * Does upload, encryption is done transparently by Uploader.
+     * Does upload, encryption is done transparently by ServerInteraction.
      *
      * @name PasteEncrypter
      * @class
@@ -3906,43 +3912,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
             });
         }
 
-        /**
-         * adds attachments to the Uploader
-         *
-         * @name PasteEncrypter.encryptAttachments
-         * @private
-         * @function
-         * @param {function} callback - excuted when action is successful
-         */
-        function encryptAttachments(callback) {
-            var file = AttachmentViewer.getAttachmentData();
-
-            let encryptAttachmentPromise, encryptAttachmentNamePromise;
-            if (typeof file !== 'undefined' && file !== null) {
-                var fileName = AttachmentViewer.getFile().name;
-
-                // run concurrently to encrypt everything
-                encryptAttachmentPromise = Uploader.setData('attachment', file);
-                encryptAttachmentNamePromise = Uploader.setData('attachmentname', fileName);
-            } else if (AttachmentViewer.hasAttachment()) {
-                // fall back to cloned part
-                var attachment = AttachmentViewer.getAttachment();
-
-                encryptAttachmentPromise = Uploader.setData('attachment', attachment[0]);
-                encryptAttachmentNamePromise = Uploader.setData('attachmentname', attachment[1]);
-            } else {
-                // if there are no attachments, this is of course still successful
-                return callback();
-            }
-
-            // TODO: change this callback to also use Promises instead,
-            // this here just waits
-            return Promise.all([encryptAttachmentPromise, encryptAttachmentNamePromise]).then(() => {
-                // run callback
-                return callback();
-            });
-        }
-
         /**
          * send a reply in a discussion
          *
@@ -3973,20 +3942,20 @@ jQuery.PrivateBin = (function($, RawDeflate) {
                 return;
             }
 
-            // prepare Uploader
-            Uploader.prepare();
-            Uploader.setCryptParameters(Prompt.getPassword(), Model.getPasteKey());
+            // prepare server interaction
+            ServerInteraction.prepare();
+            ServerInteraction.setCryptParameters(Prompt.getPassword(), Model.getPasteKey());
 
             // set success/fail functions
-            Uploader.setSuccess(showUploadedComment);
-            Uploader.setFailure(function (status, data) {
+            ServerInteraction.setSuccess(showUploadedComment);
+            ServerInteraction.setFailure(function (status, data) {
                 // revert loading status…
                 Alert.hideLoading();
                 TopNav.showViewButtons();
 
                 // …show error message…
                 Alert.showError(
-                    Uploader.parseUploadError(status, data, 'post comment')
+                    ServerInteraction.parseUploadError(status, data, 'post comment')
                 );
 
                 // …and reset error handler
@@ -3994,28 +3963,24 @@ jQuery.PrivateBin = (function($, RawDeflate) {
             });
 
             // fill it with unencrypted params
-            Uploader.setUnencryptedData('pasteid', Model.getPasteId());
+            ServerInteraction.setUnencryptedData('pasteid', Model.getPasteId());
             if (typeof parentid === 'undefined') {
                 // if parent id is not set, this is the top-most comment, so use
                 // paste id as parent, as the root element of the discussion tree
-                Uploader.setUnencryptedData('parentid', Model.getPasteId());
+                ServerInteraction.setUnencryptedData('parentid', Model.getPasteId());
             } else {
-                Uploader.setUnencryptedData('parentid', parentid);
+                ServerInteraction.setUnencryptedData('parentid', parentid);
             }
 
-            // start promises to encrypt data…
-            let dataPromises = [];
-            dataPromises.push(Uploader.setData('data', plainText));
+            // prepare cypher message
+            let cipherMessage = {
+                'comment': plainText
+            };
             if (nickname.length > 0) {
-                dataPromises.push(Uploader.setData('nickname', nickname));
+                cipherMessage['nickname'] = nickname;
             }
 
-            // …and upload when they are all done
-            Promise.all(dataPromises).then(() => {
-                Uploader.run();
-            }).catch((e) => {
-                Alert.showError(e);
-            });
+            await ServerInteraction.setCipherMessage(cipherMessage).catch(Alert.showError);
         };
 
         /**
@@ -4049,60 +4014,55 @@ jQuery.PrivateBin = (function($, RawDeflate) {
                 return;
             }
 
-            // prepare Uploader
-            Uploader.prepare();
-            Uploader.setCryptParameters(TopNav.getPassword());
+            // prepare server interaction
+            ServerInteraction.prepare();
+            ServerInteraction.setCryptParameters(TopNav.getPassword());
 
             // set success/fail functions
-            Uploader.setSuccess(showCreatedPaste);
-            Uploader.setFailure(function (status, data) {
+            ServerInteraction.setSuccess(showCreatedPaste);
+            ServerInteraction.setFailure(function (status, data) {
                 // revert loading status…
                 Alert.hideLoading();
                 TopNav.showCreateButtons();
 
                 // show error message
                 Alert.showError(
-                    Uploader.parseUploadError(status, data, 'create paste')
+                    ServerInteraction.parseUploadError(status, data, 'create paste')
                 );
             });
 
             // fill it with unencrypted submitted options
-            Uploader.setUnencryptedBulkData({
-                expire:           TopNav.getExpiration(),
-                formatter:        format,
-                burnafterreading: TopNav.getBurnAfterReading() ? 1 : 0,
-                opendiscussion:   TopNav.getOpenDiscussion() ? 1 : 0
-            });
+            ServerInteraction.setUnencryptedData('adata', [
+                null, format,
+                TopNav.getOpenDiscussion() ? 1 : 0,
+                TopNav.getBurnAfterReading() ? 1 : 0
+            ]);
+            ServerInteraction.setUnencryptedData('meta', {'expire': TopNav.getExpiration()});
 
             // prepare PasteViewer for later preview
             PasteViewer.setText(plainText);
             PasteViewer.setFormat(format);
 
-            // encrypt attachments
-            const encryptAttachmentsPromise = encryptAttachments(
-                function () {
-                    // TODO: remove, is not needed anymore as we use Promises
-                }
-            );
+            // prepare cypher message
+            let file = AttachmentViewer.getAttachmentData(),
+                cipherMessage = {
+                    'paste': plainText
+                };
+            if (typeof file !== 'undefined' && file !== null) {
+                cipherMessage['attachment'] = file;
+                cipherMessage['attachment_name'] = AttachmentViewer.getFile().name;
+            } else if (AttachmentViewer.hasAttachment()) {
+                // fall back to cloned part
+                let attachment = AttachmentViewer.getAttachment();
+                cipherMessage['attachment'] = attachment[0];
+                cipherMessage['attachment_name'] = attachment[1];
+            }
 
-            // encrypt plain text
-            const encryptDataPromise = Uploader.setData('data', plainText);
-
-            await Promise.all([encryptAttachmentsPromise, encryptDataPromise]).catch(Alert.showError);
+            // encrypt message
+            await ServerInteraction.setCipherMessage(cipherMessage).catch(Alert.showError);
 
             // send data
-            Uploader.run();
-        };
-
-        /**
-         * initialize
-         *
-         * @name   PasteEncrypter.init
-         * @function
-         */
-        me.init = function()
-        {
-            // nothing yet
+            ServerInteraction.run();
         };
 
         return me;
@@ -4347,17 +4307,6 @@ jQuery.PrivateBin = (function($, RawDeflate) {
             });
         };
 
-        /**
-         * initialize
-         *
-         * @name   PasteDecrypter.init
-         * @function
-         */
-        me.init = function()
-        {
-            // nothing yet
-        };
-
         return me;
     })();
 
@@ -4457,20 +4406,20 @@ jQuery.PrivateBin = (function($, RawDeflate) {
             var orgPosition = $(window).scrollTop();
 
             Model.getPasteData(function (data) {
-                Uploader.prepare();
-                Uploader.setUrl(Helper.baseUri() + '?' + Model.getPasteId());
+                ServerInteraction.prepare();
+                ServerInteraction.setUrl(Helper.baseUri() + '?' + Model.getPasteId());
 
-                Uploader.setFailure(function (status, data) {
+                ServerInteraction.setFailure(function (status, data) {
                     // revert loading status…
                     Alert.hideLoading();
                     TopNav.showViewButtons();
 
                     // show error message
                     Alert.showError(
-                        Uploader.parseUploadError(status, data, 'refresh display')
+                        ServerInteraction.parseUploadError(status, data, 'refresh display')
                     );
                 });
-                Uploader.setSuccess(function (status, data) {
+                ServerInteraction.setSuccess(function (status, data) {
                     PasteDecrypter.run(data);
 
                     // restore position
@@ -4481,7 +4430,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
                     // password being entered
                     callback();
                 });
-                Uploader.run();
+                ServerInteraction.run();
             }, false); // this false is important as it circumvents the cache
         }
 
@@ -4551,14 +4500,11 @@ jQuery.PrivateBin = (function($, RawDeflate) {
             AttachmentViewer.init();
             DiscussionViewer.init();
             Editor.init();
-            PasteDecrypter.init();
-            PasteEncrypter.init();
             PasteStatus.init();
             PasteViewer.init();
             Prompt.init();
             TopNav.init();
             UiHelper.init();
-            Uploader.init();
 
             // check whether existing paste needs to be shown
             try {
@@ -4602,7 +4548,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
         AttachmentViewer: AttachmentViewer,
         DiscussionViewer: DiscussionViewer,
         TopNav: TopNav,
-        Uploader: Uploader,
+        ServerInteraction: ServerInteraction,
         PasteEncrypter: PasteEncrypter,
         PasteDecrypter: PasteDecrypter,
         Controller: Controller
diff --git a/js/test/CryptTool.js b/js/test/CryptTool.js
index 4ea70241..c36e7c90 100644
--- a/js/test/CryptTool.js
+++ b/js/test/CryptTool.js
@@ -2,6 +2,11 @@
 require('../common');
 
 describe('CryptTool', function () {
+    afterEach(async function () {
+        // pause to let async functions conclude
+        await new Promise(resolve => setTimeout(resolve, 1900));
+    });
+
     describe('cipher & decipher', function () {
         this.timeout(30000);
         it('can en- and decrypt any message', function () {
@@ -9,24 +14,22 @@ describe('CryptTool', function () {
                 'string',
                 'string',
                 'string',
-                function (key, password, message) {
-                    var clean = jsdom();
+                async function (key, password, message) {
+                    // pause to let async functions conclude
+                    await new Promise(resolve => setTimeout(resolve, 300));
+                    let clean = jsdom();
                     window.crypto = new WebCrypto();
                     message = message.trim();
-                    return $.PrivateBin.CryptTool.cipher(
-                        key, password, message
-                    ).then(function(ciphertext) {
-                        $.PrivateBin.CryptTool.decipher(
-                            key, password, ciphertext
-                        ).then(function(plaintext) {
-                            clean();
-                            return message === plaintext;
-                        });
-                    });
+                    let cipherMessage = await $.PrivateBin.CryptTool.cipher(
+                            key, password, message, []
+                        ),
+                        plaintext = await $.PrivateBin.CryptTool.decipher(
+                                key, password, cipherMessage
+                        );
+                    clean();
+                    return message === plaintext;
                 }
-            ),
-            // reducing amount of checks as running 100 async ones causes issues for later test scripts
-            {tests: 3});
+            ));
         });
 
         // The below static unit tests are included to ensure deciphering of "classic"
@@ -35,7 +38,7 @@ describe('CryptTool', function () {
             'supports PrivateBin v1 ciphertext (SJCL & browser atob)',
             function () {
                 delete global.Base64;
-                var clean = jsdom();
+                let clean = jsdom();
                 window.crypto = new WebCrypto();
 
                 // Of course you can easily decipher the following texts, if you like.
@@ -43,7 +46,7 @@ describe('CryptTool', function () {
                 return $.PrivateBin.CryptTool.decipher(
                     '6t2qsmLyfXIokNCL+3/yl15rfTUBQvm5SOnFPvNE7Q8=',
                     // -- "That's amazing. I've got the same combination on my luggage."
-                    Array.apply(0, Array(6)).map(function(_,b) { return b + 1; }).join(''),
+                    Array.apply(0, Array(6)).map((_,b) => b + 1).join(''),
                     '{"iv":"4HNFIl7eYbCh6HuShctTIA==","v":1,"iter":10000,"ks"' +
                     ':256,"ts":128,"mode":"gcm","adata":"","cipher":"aes","sa' +
                     'lt":"u0lQvePq6L0=","ct":"fGPUVrDyaVr1ZDGb+kqQ3CPEW8x4YKG' +
@@ -71,7 +74,7 @@ describe('CryptTool', function () {
                     'QUxMXI5htsn2rf0HxCFu7Po8DNYLxTS+67hYjDIYWYaEIc8LXWMLyDm9' +
                     'C5fARPJ4F2BIWgzgzkNj+dVjusft2XnziamWdbS5u3kuRlVuz5LQj+R5' +
                     'imnqQAincdZTkTT1nYx+DatlOLllCYIHffpI="}'
-                ).then(function(paste1) {
+                ).then(function (paste1) {
                     $.PrivateBin.CryptTool.decipher(
                         's9pmKZKOBN7EVvHpTA8jjLFH3Xlz/0l8lB4+ONPACrM=',
                         '', // no password
@@ -97,7 +100,7 @@ describe('CryptTool', function () {
                         'XhHvixZLcSjX2KQuHmEoWzmJcr3DavdoXZmAurGWLKjzEdJc5dSD/eNr' +
                         '99gjHX7wphJ6umKMM+fn6PcbYJkhDh2GlJL5COXjXfm/5aj/vuyaRRWZ' +
                         'MZtmnYpGAtAPg7AUG"}'
-                    ).then(function(paste2) {
+                    ).then(function (paste2) {
                         clean();
                         assert.ok(
                             paste1.includes('securely packed in iron') &&
@@ -120,7 +123,7 @@ describe('CryptTool', function () {
                 return $.PrivateBin.CryptTool.decipher(
                     '6t2qsmLyfXIokNCL+3/yl15rfTUBQvm5SOnFPvNE7Q8=',
                     // -- "That's amazing. I've got the same combination on my luggage."
-                    Array.apply(0, Array(6)).map(function(_,b) { return b + 1; }).join(''),
+                    Array.apply(0, Array(6)).map((_,b) => b + 1).join(''),
                     '{"iv":"aTnR2qBL1CAmLX8FdWe3VA==","v":1,"iter":10000,"ks"' +
                     ':256,"ts":128,"mode":"gcm","adata":"","cipher":"aes","sa' +
                     'lt":"u0lQvePq6L0=","ct":"A3nBTvICZtYy6xqbIJE0c8Veored5lM' +
@@ -140,7 +143,7 @@ describe('CryptTool', function () {
                     '7mNNo7xba/YT9KoPDaniqnYqb+q2pX1WNWE7dLS2wfroMAS3kh8P22DA' +
                     'V37AeiNoD2PcI6ZcHbRdPa+XRrRcJhSPPW7UQ0z4OvBfjdu/w390QxAx' +
                     'SxvZewoh49fKKB6hTsRnZb4tpHkjlww=="}'
-                ).then(function(paste1) {
+                ).then(function (paste1) {
                     $.PrivateBin.CryptTool.decipher(
                         's9pmKZKOBN7EVvHpTA8jjLFH3Xlz/0l8lB4+ONPACrM=',
                         '', // no password
@@ -159,7 +162,7 @@ describe('CryptTool', function () {
                         '7tmfcF73w9dufDFI3LNca2KxzBnWNPYvIZKBwWbq8ncxkb191dP6mjEi' +
                         '7NnhqVk5A6vIBbu4AC5PZf76l6yep4xsoy/QtdDxCMocCXeAML9MQ9uP' +
                         'QbuspOKrBvMfN5igA1kBqasnxI472KBNXsdZnaDddSVUuvhTcETM="}'
-                    ).then(function(paste2) {
+                    ).then(function (paste2) {
                         clean();
                         delete global.Base64;
                         assert.ok(
diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php
index 55ebed68..47f9f42b 100644
--- a/tpl/bootstrap.php
+++ b/tpl/bootstrap.php
@@ -71,7 +71,7 @@ if ($MARKDOWN):
 endif;
 ?>
 		
-		
+		
 		
diff --git a/tpl/page.php b/tpl/page.php
index d200794a..c7333fe5 100644
--- a/tpl/page.php
+++ b/tpl/page.php
@@ -49,7 +49,7 @@ if ($MARKDOWN):
 endif;
 ?>
 		
-		
+