From 811463b870c4abaa1a9a89a137817ab731a03a00 Mon Sep 17 00:00:00 2001
From: yflory <yann.flory@xwiki.com>
Date: Tue, 24 Apr 2018 17:22:33 +0200
Subject: [PATCH] Add support for version 2 hashes needed for
 password-protected pads

---
 www/assert/main.js                |  24 ++++
 www/common/common-hash.js         | 215 +++++++++++++++++++++++-------
 www/common/common-ui-elements.js  |   8 +-
 www/common/cryptget.js            |  14 +-
 www/common/cryptpad-common.js     |  10 +-
 www/common/outer/async-store.js   |  10 +-
 www/common/outer/local-store.js   |   2 +-
 www/common/outer/upload.js        |   9 +-
 www/common/sframe-common-outer.js |  30 +++--
 www/common/sframe-common.js       |   1 +
 www/drive/inner.js                |  22 +--
 www/file/inner.js                 |   1 +
 www/profile/main.js               |   6 +-
 www/todo/main.js                  |   2 +-
 14 files changed, 254 insertions(+), 100 deletions(-)

diff --git a/www/assert/main.js b/www/assert/main.js
index eb470eb35..c50360e53 100644
--- a/www/assert/main.js
+++ b/www/assert/main.js
@@ -223,6 +223,30 @@ define([
             hd.type === 'invite');
     }, "test support for invite urls");
 
+    // test support for V2
+    assert(function (cb) {
+        var secret = Hash.parsePadUrl('/pad/#/2/pad/edit/oRE0oLCtEXusRDyin7GyLGcS/');
+        return cb(secret.hashData.version === 2 &&
+            secret.hashData.mode === "edit" &&
+            secret.hashData.type === "pad" &&
+            secret.hashData.channel === "2NUbSuqGPz8FD0f4rSYXUw" &&
+            secret.hashData.key === "oRE0oLCtEXusRDyin7GyLGcS" &&
+            window.nacl.util.encodeBase64(secret.hashData.cryptKey) === "0Ts1M6VVEozErV2Nx/LTv6Im5SCD7io2LlhasyyBPQo=" &&
+            secret.hashData.validateKey === "f5A1FM9Gp55tnOcM75RyHD1oxBG9ZPh9WDA7qe2Fvps=" &&
+            !secret.hashData.present);
+    }, "test support for version 2 hash failed to parse");
+    assert(function (cb) {
+        var secret = Hash.parsePadUrl('/pad/#/2/pad/edit/HGu0tK2od-2BBnwAz2ZNS-t4/p/embed', 'pewpew');
+        return cb(secret.hashData.version === 2 &&
+            secret.hashData.mode === "edit" &&
+            secret.hashData.type === "pad" &&
+            secret.hashData.channel === "P7bck4B9kDr-OQtfeYySyQ" &&
+            secret.hashData.key === "HGu0tK2od-2BBnwAz2ZNS-t4" &&
+            window.nacl.util.encodeBase64(secret.hashData.cryptKey) === "EeCkGJra8eJgVu7v4Yl2Hc3yUjrgpKpxr0Lcc3bSWVs=" &&
+            secret.hashData.validateKey === "WGkBczJf2V6vQZfAScz8V1KY6jKdoxUCckrD+E75gGE=" &&
+            secret.hashData.embed);
+    }, "test support for password in version 2 hash failed to parse");
+
     assert(function (cb) {
         var url = '/pad/?utm_campaign=new_comment&utm_medium=email&utm_source=thread_mailer#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/';
         var secret = Hash.parsePadUrl(url);
diff --git a/www/common/common-hash.js b/www/common/common-hash.js
index 870b19dfe..852260acc 100644
--- a/www/common/common-hash.js
+++ b/www/common/common-hash.js
@@ -19,7 +19,50 @@ define([
             .decodeUTF8(JSON.stringify(list))));
     };
 
-    var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) {
+    var getEditHashFromKeys = Hash.getEditHashFromKeys = function (secret) {
+        var version = secret.version;
+        var data = secret.keys;
+        if (version === 0) {
+            return secret.channel + secret.key;
+        }
+        if (version === 1) {
+            if (!data.editKeyStr) { return; }
+            return '/1/edit/' + hexToBase64(secret.channel) +
+                   '/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/';
+        }
+        if (version === 2) {
+            if (!data.editKeyStr) { return; }
+            var pass = secret.password ? 'p/' : '';
+            return '/2/' + secret.type + '/edit/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/' + pass;
+        }
+    };
+    var getViewHashFromKeys = Hash.getViewHashFromKeys = function (secret) {
+        var version = secret.version;
+        var data = secret.keys;
+        if (version === 0) { return; }
+        if (version === 1) {
+            if (!data.viewKeyStr) { return; }
+            return '/1/view/' + hexToBase64(secret.channel) +
+                   '/'+Crypto.b64RemoveSlashes(data.viewKeyStr)+'/';
+        }
+        if (version === 2) {
+            if (!data.viewKeyStr) { return; }
+            var pass = secret.password ? 'p/' : '';
+            return '/2/' + secret.type + '/view/' + Crypto.b64RemoveSlashes(data.viewKeyStr) + '/' + pass;
+        }
+    };
+    var getFileHashFromKeys = Hash.getFileHashFromKeys = function (secret) {
+        var version = secret.version;
+        var data = secret.keys;
+        if (version === 0) { return; }
+        if (version === 1) {
+            return '/1/' + hexToBase64(secret.channel) + '/' +
+                   Crypto.b64RemoveSlashes(data.fileKeyStr) + '/';
+        }
+    };
+
+    // V1
+    /*var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) {
         if (typeof keys === 'string') {
             return chanKey + keys;
         }
@@ -34,7 +77,7 @@ define([
     };
     var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, cryptKey) {
         return '/1/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/';
-    };
+    };*/
     Hash.getUserHrefFromKeys = function (origin, username, pubkey) {
         return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-');
     };
@@ -43,6 +86,24 @@ define([
         return s.replace(/\/+/g, '/');
     };
 
+    Hash.createChannelId = function () {
+        var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16));
+        if (id.length !== 32 || /[^a-f0-9]/.test(id)) {
+            throw new Error('channel ids must consist of 32 hex characters');
+        }
+        return id;
+    };
+
+    Hash.createRandomHash = function (type, password) {
+        var cryptor = Crypto.createEditCryptor2(void 0, void 0, password);
+        return getEditHashFromKeys({
+            password: Boolean(password),
+            version: 2,
+            type: type,
+            keys: { editKeyStr: cryptor.editKeyStr }
+        });
+    };
+
 /*
 Version 0
     /pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy
@@ -50,25 +111,49 @@ Version 1
     /code/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI
 */
 
-    var parseTypeHash = Hash.parseTypeHash = function (type, hash) {
+    var parseTypeHash = Hash.parseTypeHash = function (type, hash, password) {
         if (!hash) { return; }
         var parsed = {};
         var hashArr = fixDuplicateSlashes(hash).split('/');
         if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) {
             parsed.type = 'pad';
-            if (hash.slice(0,1) !== '/' && hash.length >= 56) {
+            if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Version 0
                 // Old hash
                 parsed.channel = hash.slice(0, 32);
                 parsed.key = hash.slice(32, 56);
                 parsed.version = 0;
                 return parsed;
             }
-            if (hashArr[1] && hashArr[1] === '1') {
+            var options;
+            if (hashArr[1] && hashArr[1] === '1') { // Version 1
                 parsed.version = 1;
                 parsed.mode = hashArr[2];
                 parsed.channel = hashArr[3];
-                parsed.key = hashArr[4].replace(/-/g, '/');
-                var options = hashArr.slice(5);
+                parsed.key = Crypto.b64AddSlashes(hashArr[4]);
+
+                options = hashArr.slice(5);
+                parsed.present = options.indexOf('present') !== -1;
+                parsed.embed = options.indexOf('embed') !== -1;
+                return parsed;
+            }
+            if (hashArr[1] && hashArr[1] === '2') { // Version 2
+                parsed.version = 2;
+                parsed.app = hashArr[2];
+                parsed.mode = hashArr[3];
+                parsed.key = hashArr[4];
+
+                var cryptor;
+                if (parsed.mode === "edit") {
+                    cryptor = Crypto.createEditCryptor2(parsed.key, void 0, password);
+                } else if (parsed.mode === "view") {
+                    cryptor = Crypto.createViewCryptor2(parsed.key, password);
+                }
+                parsed.channel = cryptor.chanId;
+                parsed.cryptKey = cryptor.cryptKey;
+                parsed.validateKey = cryptor.validateKey;
+
+                options = hashArr.slice(5);
+                parsed.password = options.indexOf('p') !== -1;
                 parsed.present = options.indexOf('present') !== -1;
                 parsed.embed = options.indexOf('embed') !== -1;
                 return parsed;
@@ -107,7 +192,7 @@ Version 1
         }
         return;
     };
-    var parsePadUrl = Hash.parsePadUrl = function (href) {
+    var parsePadUrl = Hash.parsePadUrl = function (href, password) {
         var patt = /^https*:\/\/([^\/]*)\/(.*?)\//i;
 
         var ret = {};
@@ -125,17 +210,33 @@ Version 1
             url += ret.type + '/';
             if (!ret.hashData) { return url; }
             if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; }
-            if (ret.hashData.version !== 1) { return url + '#' + ret.hash; }
-            url += '#/' + ret.hashData.version +
-                   '/' + ret.hashData.mode +
-                   '/' + ret.hashData.channel.replace(/\//g, '-') +
-                   '/' + ret.hashData.key.replace(/\//g, '-') +'/';
-            if (options.embed) {
-                url += 'embed/';
-            }
-            if (options.present) {
-                url += 'present/';
+            if (ret.hashData.version === 0) { return url + '#' + ret.hash; }
+            var hash;
+            if (options.readOnly === true ||
+                    (typeof (options.readOnly === "undefined") && ret.hashData.mode === "view")) {
+                hash = getViewHashFromKeys({
+                    version: ret.hashData.version,
+                    type: ret.hashData.app,
+                    channel: base64ToHex(ret.hashData.channel || ''),
+                    password: ret.hashData.password,
+                    keys: {
+                        viewKeyStr: ret.hashData.key
+                    }
+                });
+            } else {
+                hash = getEditHashFromKeys({
+                    version: ret.hashData.version,
+                    type: ret.hashData.app,
+                    channel: base64ToHex(ret.hashData.channel || ''),
+                    password: ret.hashData.password,
+                    keys: {
+                        editKeyStr: ret.hashData.key
+                    }
+                });
             }
+            url += '#' + hash;
+            if (options.embed) { url += 'embed/'; }
+            if (options.present) { url += 'present/'; }
             return url;
         };
 
@@ -143,7 +244,7 @@ Version 1
             idx = href.indexOf('/#');
             ret.type = href.slice(1, idx);
             ret.hash = href.slice(idx + 2);
-            ret.hashData = parseTypeHash(ret.type, ret.hash);
+            ret.hashData = parseTypeHash(ret.type, ret.hash, password);
             return ret;
         }
 
@@ -154,7 +255,7 @@ Version 1
         });
         idx = href.indexOf('/#');
         ret.hash = href.slice(idx + 2);
-        ret.hashData = parseTypeHash(ret.type, ret.hash);
+        ret.hashData = parseTypeHash(ret.type, ret.hash, password);
         return ret;
     };
 
@@ -170,11 +271,13 @@ Version 1
      * - no argument: use the URL hash or create one if it doesn't exist
      * - secretHash provided: use secretHash to find the keys
      */
-    Hash.getSecrets = function (type, secretHash) {
+    Hash.getSecrets = function (type, secretHash, password) {
         var secret = {};
         var generate = function () {
-            secret.keys = Crypto.createEditCryptor();
-            secret.key = Crypto.createEditCryptor().editKeyStr;
+            secret.keys = Crypto.createEditCryptor2(void 0, void 0, password);
+            secret.channel = base64ToHex(secret.keys.chanId);
+            secret.version = 2;
+            secret.type = type;
         };
         if (!secretHash && !window.location.hash) { //!/#/.test(window.location.href)) {
             generate();
@@ -203,9 +306,10 @@ Version 1
                 // Old hash
                 secret.channel = parsed.channel;
                 secret.key = parsed.key;
-            }
-            else if (parsed.version === 1) {
+                secret.version = 0;
+            } else if (parsed.version === 1) {
                 // New hash
+                secret.version = 1;
                 if (parsed.type === "pad") {
                     secret.channel = base64ToHex(parsed.channel);
                     if (parsed.mode === 'edit') {
@@ -229,45 +333,60 @@ Version 1
                     // version 2 hashes are to be used for encrypted blobs
                     throw new Error("User hashes can't be opened (yet)");
                 }
+            } else if (parsed.version === 2) {
+                // New hash
+                secret.version = 2;
+                secret.type = type;
+                secret.password = Boolean(password);
+                if (parsed.type === "pad") {
+                    if (parsed.mode === 'edit') {
+                        secret.keys = Crypto.createEditCryptor2(parsed.key);
+                        secret.channel = base64ToHex(secret.keys.chanId);
+                        secret.key = secret.keys.editKeyStr;
+                        if (secret.channel.length !== 32 || secret.key.length !== 24) {
+                            throw new Error("The channel key and/or the encryption key is invalid");
+                        }
+                    }
+                    else if (parsed.mode === 'view') {
+                        secret.keys = Crypto.createViewCryptor2(parsed.key);
+                        secret.channel = base64ToHex(secret.keys.chanId);
+                        if (secret.channel.length !== 32) {
+                            throw new Error("The channel key is invalid");
+                        }
+                    }
+                } else if (parsed.type === "file") {
+                    throw new Error("File hashes should be version 1");
+                } else if (parsed.type === "user") {
+                    throw new Error("User hashes can't be opened (yet)");
+                }
             }
         }
         return secret;
     };
 
-    Hash.getHashes = function (channel, secret) {
+    Hash.getHashes = function (secret) {
         var hashes = {};
-        if (!secret.keys) {
+        secret = JSON.parse(JSON.stringify(secret));
+
+        if (!secret.keys && !secret.key) {
             console.error('e');
             return hashes;
+        } else if (!secret.keys) {
+            secret.keys = {};
         }
-        if (secret.keys.editKeyStr) {
-            hashes.editHash = getEditHashFromKeys(channel, secret.keys);
+
+        if (secret.keys.editKeyStr || (secret.version === 0 && secret.key)) {
+            hashes.editHash = getEditHashFromKeys(secret);
         }
         if (secret.keys.viewKeyStr) {
-            hashes.viewHash = getViewHashFromKeys(channel, secret.keys);
+            hashes.viewHash = getViewHashFromKeys(secret);
         }
         if (secret.keys.fileKeyStr) {
-            hashes.fileHash = getFileHashFromKeys(channel, secret.keys.fileKeyStr);
+            hashes.fileHash = getFileHashFromKeys(secret);
         }
         return hashes;
     };
 
-    var createChannelId = Hash.createChannelId = function () {
-        var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16));
-        if (id.length !== 32 || /[^a-f0-9]/.test(id)) {
-            throw new Error('channel ids must consist of 32 hex characters');
-        }
-        return id;
-    };
-
-    Hash.createRandomHash = function () {
-        // 16 byte channel Id
-        var channelId = Util.hexToBase64(createChannelId());
-        // 18 byte encryption key
-        var key = Crypto.b64RemoveSlashes(Crypto.rand64(18));
-        return '/1/edit/' + [channelId, key].join('/') + '/';
-    };
-
     // STORAGE
     Hash.findWeaker = function (href, recents) {
         var rHref = href || getRelativeHref(window.location.href);
@@ -336,7 +455,7 @@ Version 1
         parsed = parsed.hashData;
         if (parsed.version === 0) {
             return parsed.channel;
-        } else if (parsed.version !== 1 && parsed.version !== 2) {
+        } else if (!parsed.version) {
             console.error("parsed href had no version");
             console.error(parsed);
             return;
diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js
index 2b9487a73..2d09b2abe 100644
--- a/www/common/common-ui-elements.js
+++ b/www/common/common-ui-elements.js
@@ -60,6 +60,10 @@ define([
     var getPropertiesData = function (common, cb) {
         var data = {};
         NThen(function (waitFor) {
+            common.getPadAttribute('password', waitFor(function (err, val) {
+                data.password = val;
+            }));
+        }).nThen(function (waitFor) {
             common.getPadAttribute('href', waitFor(function (err, val) {
                 var base = common.getMetadataMgr().getPrivateData().origin;
 
@@ -75,9 +79,9 @@ define([
                 if (parsed.hashData.type !== "pad") { return; }
                 var i = data.href.indexOf('#') + 1;
                 var hBase = data.href.slice(0, i);
-                var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash);
+                var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash, data.password);
                 if (!hrefsecret.keys) { return; }
-                var viewHash = Hash.getViewHashFromKeys(hrefsecret.channel, hrefsecret.keys);
+                var viewHash = Hash.getViewHashFromKeys(hrefsecret);
                 data.roHref = hBase + viewHash;
             }));
             common.getPadAttribute('atime', waitFor(function (err, val) {
diff --git a/www/common/cryptget.js b/www/common/cryptget.js
index 686c69884..d5cb3edc1 100644
--- a/www/common/cryptget.js
+++ b/www/common/cryptget.js
@@ -20,9 +20,9 @@ define([
         }
     };
 
-    var makeConfig = function (hash) {
+    var makeConfig = function (hash, password) {
         // We can't use cryptget with a file or a user so we can use 'pad' as hash type
-        var secret = Hash.getSecrets('pad', hash);
+        var secret = Hash.getSecrets('pad', hash, password);
         if (!secret.keys) { secret.keys = secret.key; } // support old hashses
         var config = {
             websocketURL: NetConfig.getWebsocketURL(),
@@ -43,12 +43,15 @@ define([
         Object.keys(b).forEach(function (k) { a[k] = b[k]; });
     };
 
+    // XXX make sure we pass the password here in opt
     var get = function (hash, cb, opt) {
         if (typeof(cb) !== 'function') {
             throw new Error('Cryptget expects a callback');
         }
+        opt = opt || {};
+
+        var config = makeConfig(hash, opt.password);
         var Session = { cb: cb, };
-        var config = makeConfig(hash);
 
         config.onReady = function (info) {
             var rt = Session.session = info.realtime;
@@ -60,13 +63,16 @@ define([
         Session.realtime = CPNetflux.start(config);
     };
 
+    // XXX make sure we pass the password here in opt
     var put = function (hash, doc, cb, opt) {
         if (typeof(cb) !== 'function') {
             throw new Error('Cryptput expects a callback');
         }
+        opt = opt || {};
 
-        var config = makeConfig(hash);
+        var config = makeConfig(hash, opt.password);
         var Session = { cb: cb, };
+
         config.onReady = function (info) {
             var realtime = Session.session = info.realtime;
             Session.network = info.network;
diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js
index d4adb1ee0..e473b397c 100644
--- a/www/common/cryptpad-common.js
+++ b/www/common/cryptpad-common.js
@@ -260,8 +260,8 @@ define([
         });
     };
 
-    common.isNewChannel = function (href, cb) {
-        postMessage('IS_NEW_CHANNEL', {href: href}, function (obj) {
+    common.isNewChannel = function (href, password, cb) {
+        postMessage('IS_NEW_CHANNEL', {href: href, password: password}, function (obj) {
             if (obj.error) { return void cb(obj.error); }
             if (!obj) { return void cb('INVALID_RESPONSE'); }
             cb(undefined, obj.isNew);
@@ -395,7 +395,7 @@ define([
     common.saveAsTemplate = function (Cryptput, data, cb) {
         var p = Hash.parsePadUrl(window.location.href);
         if (!p.type) { return; }
-        var hash = Hash.createRandomHash();
+        var hash = Hash.createRandomHash(p.type);
         var href = '/' + p.type + '/#' + hash;
         Cryptput(hash, data.toSave, function (e) {
             if (e) { throw new Error(e); }
@@ -556,13 +556,13 @@ define([
     common.getShareHashes = function (secret, cb) {
         var hashes;
         if (!window.location.hash) {
-            hashes = Hash.getHashes(secret.channel, secret);
+            hashes = Hash.getHashes(secret);
             return void cb(null, hashes);
         }
         var parsed = Hash.parsePadUrl(window.location.href);
         if (!parsed.type || !parsed.hashData) { return void cb('E_INVALID_HREF'); }
         if (parsed.type === 'file') { secret.channel = Util.base64ToHex(secret.channel); }
-        hashes = Hash.getHashes(secret.channel, secret);
+        hashes = Hash.getHashes(secret);
 
         if (!hashes.editHash && !hashes.viewHash && parsed.hashData && !parsed.hashData.mode) {
             // It means we're using an old hash
diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js
index 0125e6018..676277475 100644
--- a/www/common/outer/async-store.js
+++ b/www/common/outer/async-store.js
@@ -316,7 +316,7 @@ define([
 
     Store.isNewChannel = function (data, cb) {
         if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
-        var channelId = Hash.hrefToHexChannelId(data.href);
+        var channelId = Hash.hrefToHexChannelId(data.href, data.password);
         store.anon_rpc.send("IS_NEW_CHANNEL", channelId, function (e, response) {
             if (e) { return void cb({error: e}); }
             if (response && response.length && typeof(response[0]) === 'boolean') {
@@ -524,7 +524,7 @@ define([
      */
     Store.createReadme = function (data, cb) {
         require(['/common/cryptget.js'], function (Crypt) {
-            var hash = Hash.createRandomHash();
+            var hash = Hash.createRandomHash('pad');
             Crypt.put(hash, data.driveReadme, function (e) {
                 if (e) {
                     return void cb({ error: "Error while creating the default pad:"+ e});
@@ -717,7 +717,7 @@ define([
 
             // If the hash is different but represents the same channel, check if weaker or stronger
             if (!shouldUpdate &&
-                h.version === 1 && h2.version === 1 &&
+                h.version === h2.version &&
                 h.channel === h2.channel) {
                 // We had view & now we have edit, update
                 if (h2.mode === 'view' && h.mode === 'edit') { shouldUpdate = true; }
@@ -1123,7 +1123,7 @@ define([
     };
 
     var connect = function (data, cb) {
-        var hash = data.userHash || data.anonHash || Hash.createRandomHash();
+        var hash = data.userHash || data.anonHash || Hash.createRandomHash('drive');
         storeHash = hash;
         if (!hash) {
             throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...');
@@ -1150,7 +1150,7 @@ define([
             store.realtime = info.realtime;
             store.network = info.network;
             if (!data.userHash) {
-                returned.anonHash = Hash.getEditHashFromKeys(info.channel, secret.keys);
+                returned.anonHash = Hash.getEditHashFromKeys(secret);
             }
         }).on('ready', function () {
             if (store.userObject) { return; } // the store is already ready, it is a reconnection
diff --git a/www/common/outer/local-store.js b/www/common/outer/local-store.js
index 7e93d34e7..a40a3e6f5 100644
--- a/www/common/outer/local-store.js
+++ b/www/common/outer/local-store.js
@@ -108,7 +108,7 @@ define([
         // Make sure we have an FS_hash in localStorage before reloading all the tabs
         // so that we don't end up with tabs using different anon hashes
         if (!LocalStore.getFSHash()) {
-            LocalStore.setFSHash(Hash.createRandomHash());
+            LocalStore.setFSHash(Hash.createRandomHash('drive'));
         }
         eraseTempSessionValues();
 
diff --git a/www/common/outer/upload.js b/www/common/outer/upload.js
index cce3e6b3c..ee9f4d77c 100644
--- a/www/common/outer/upload.js
+++ b/www/common/outer/upload.js
@@ -51,7 +51,14 @@ define([
 
                 var b64Key = Nacl.util.encodeBase64(key);
 
-                var hash = Hash.getFileHashFromKeys(id, b64Key);
+                var secret = {
+                    version: 1,
+                    channel: id,
+                    keys: {
+                        fileKeyStr: b64Key
+                    }
+                };
+                var hash = Hash.getFileHashFromKeys(secret);
                 var href = '/file/#' + hash;
 
                 var title = metadata.name;
diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js
index 014914b50..e6543bb30 100644
--- a/www/common/sframe-common-outer.js
+++ b/www/common/sframe-common-outer.js
@@ -121,11 +121,17 @@ define([
                     });
                 }));
             } else {
-                secret = Utils.Hash.getSecrets();
-                if (!secret.channel) {
+                var parsedType = Utils.Hash.parsePadUrl(window.location.href).type;
+                // XXX prompt the password here if we have a hash containing /p/
+                // OR get it from the pad attributes
+                secret = Utils.Hash.getSecrets(parsedType);
+
+                // TODO: New hashes V2 already contain a channel ID so we can probably remove the following lines
+                //if (!secret.channel) {
                     // New pad: create a new random channel id
-                    secret.channel = Utils.Hash.createChannelId();
-                }
+                    //secret.channel = Utils.Hash.createChannelId();
+                //}
+
                 Cryptpad.getShareHashes(secret, waitFor(function (err, h) { hashes = h; }));
             }
         }).nThen(function (waitFor) {
@@ -133,7 +139,9 @@ define([
             if (!window.location.hash) { isNewFile = true; return; }
 
             if (realtime) {
-                Cryptpad.isNewChannel(window.location.href, waitFor(function (e, isNew) {
+                // XXX get password
+                var password;
+                Cryptpad.isNewChannel(window.location.href, password, waitFor(function (e, isNew) {
                     if (e) { return console.error(e); }
                     isNewFile = Boolean(isNew);
                 }));
@@ -635,7 +643,7 @@ define([
                     isNewHash: isNewHash,
                     readOnly: readOnly,
                     crypto: Crypto.createEncryptor(secret.keys),
-                    onConnect: function (wc) {
+                    onConnect: function () {
                         if (window.location.hash && window.location.hash !== '#') {
                             window.location = parsed.getUrl({
                                 present: parsed.hashData.present,
@@ -644,7 +652,7 @@ define([
                             return;
                         }
                         if (readOnly || cfg.noHash) { return; }
-                        replaceHash(Utils.Hash.getEditHashFromKeys(wc, secret.keys));
+                        replaceHash(Utils.Hash.getEditHashFromKeys(secret));
                     }
                 };
 
@@ -671,8 +679,10 @@ define([
             sframeChan.on('Q_CREATE_PAD', function (data, cb) {
                 if (!isNewFile || rtStarted) { return; }
                 // Create a new hash
-                var newHash = Utils.Hash.createRandomHash();
-                secret = Utils.Hash.getSecrets(parsed.type, newHash);
+                // XXX add password here
+                var password = data.password;
+                var newHash = Utils.Hash.createRandomHash(parsed.type, password);
+                secret = Utils.Hash.getSecrets(parsed.type, newHash, password);
 
                 // Update the hash in the address bar
                 var ohc = window.onhashchange;
@@ -684,7 +694,7 @@ define([
                 // Update metadata values and send new metadata inside
                 parsed = Utils.Hash.parsePadUrl(window.location.href);
                 defaultTitle = Utils.Hash.getDefaultName(parsed);
-                hashes = Utils.Hash.getHashes(secret.channel, secret);
+                hashes = Utils.Hash.getHashes(secret);
                 readOnly = false;
                 updateMeta();
 
diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js
index ab3c2389d..a56afa382 100644
--- a/www/common/sframe-common.js
+++ b/www/common/sframe-common.js
@@ -114,6 +114,7 @@ define([
     };
     funcs.getMediatagFromHref = function (href) {
         var parsed = Hash.parsePadUrl(href);
+        // FILE_HASHES2
         var secret = Hash.getSecrets('file', parsed.hash);
         var data = ctx.metadataMgr.getPrivateData();
         if (secret.keys && secret.channel) {
diff --git a/www/drive/inner.js b/www/drive/inner.js
index df9708d97..08c067183 100644
--- a/www/drive/inner.js
+++ b/www/drive/inner.js
@@ -2653,9 +2653,9 @@ define([
             if (parsed.hashData.type !== "pad") { return; }
             var i = data.href.indexOf('#') + 1;
             var base = data.href.slice(0, i);
-            var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash);
+            var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash, data.password);
             if (!hrefsecret.keys) { return; }
-            var viewHash = Hash.getViewHashFromKeys(hrefsecret.channel, hrefsecret.keys);
+            var viewHash = Hash.getViewHashFromKeys(hrefsecret);
             return base + viewHash;
         };
 
@@ -2720,24 +2720,6 @@ define([
                 $(window).focus();
                 if (!res) { return; }
                 filesOp.delete(pathsList, refresh);
-                /*
-                // Try to delete each selected pad from server, and delete from drive if no error
-                var n = nThen(function () {});
-                pathsList.forEach(function (p) {
-                    var el = filesOp.find(p);
-                    var data = filesOp.getFileData(el);
-                    var parsed = Hash.parsePadUrl(data.href);
-                    var channel = Util.base64ToHex(parsed.hashData.channel);
-                    n = n.nThen(function (waitFor) {
-                        sframeChan.query('Q_REMOVE_OWNED_CHANNEL', channel,
-                                         waitFor(function (e) {
-                            if (e) { return void console.error(e); }
-                            filesOp.delete([p], function () {}, false, true);
-                        }));
-                    });
-                });
-                n.nThen(function () { refresh(); });
-                */
             });
         };
         $contextMenu.on("click", "a", function(e) {
diff --git a/www/file/inner.js b/www/file/inner.js
index 8ad9532ad..98a77f647 100644
--- a/www/file/inner.js
+++ b/www/file/inner.js
@@ -61,6 +61,7 @@ define([
         if (!priv.filehash) {
             uploadMode = true;
         } else {
+            // FILE_HASHES2
             secret = Hash.getSecrets('file', priv.filehash);
             if (!secret.keys) { throw new Error("You need a hash"); }
             hexFileName = Util.base64ToHex(secret.channel);
diff --git a/www/profile/main.js b/www/profile/main.js
index c4b3847a4..90557d793 100644
--- a/www/profile/main.js
+++ b/www/profile/main.js
@@ -58,7 +58,7 @@ define([
                     window.location.href = '/drive';
                     return void cb();
                 }
-                var hash = Hash.createRandomHash();
+                var hash = Hash.createRandomHash('profile');
                 var secret = Hash.getSecrets('profile', hash);
                 Cryptpad.pinPads([secret.channel], function (e) {
                     if (e) {
@@ -69,8 +69,8 @@ define([
                         //return void UI.log(Messages._getKey('profile_error', [e])) // TODO
                     }
                     var profile = {};
-                    profile.edit = Utils.Hash.getEditHashFromKeys(secret.channel, secret.keys);
-                    profile.view = Utils.Hash.getViewHashFromKeys(secret.channel, secret.keys);
+                    profile.edit = Utils.Hash.getEditHashFromKeys(secret);
+                    profile.view = Utils.Hash.getViewHashFromKeys(secret);
                     Cryptpad.setNewProfile(profile);
                 });
                 cb(null, secret);
diff --git a/www/todo/main.js b/www/todo/main.js
index b86546bf6..3b1db1ef2 100644
--- a/www/todo/main.js
+++ b/www/todo/main.js
@@ -38,7 +38,7 @@ define([
     }).nThen(function (/*waitFor*/) {
         var getSecrets = function (Cryptpad, Utils, cb) {
             Cryptpad.getTodoHash(function (hash) {
-                var nHash = hash || Utils.Hash.createRandomHash();
+                var nHash = hash || Utils.Hash.createRandomHash('todo');
                 if (!hash) { Cryptpad.setTodoHash(nHash); }
                 cb(null, Utils.Hash.getSecrets('todo', nHash));
             });