MaxR
MaxR

Reputation: 27

How do I password protect a file (html) in a server-less environment?

Is it possible to password protect a file without the use of .htaccess, php, or localhost. Maybe with some type of encryption or an other method.

Upvotes: 1

Views: 3045

Answers (4)

Kamil Kiełczewski
Kamil Kiełczewski

Reputation: 92467

Yes you can encrypt any message using only JS without backend and without store password in your JS code. I will describe simple theoretical approach without technical details:

Encryption:

  1. Create password (but not store it anywhere)
  2. generate long complicated hash from that password (e.g bcrypt) (same password should produce always same hash not store it anywhere; )
  3. use that hash as key for symetric-key algorithm (e.g. ciphers AES) to code your message.

Decryption:

  1. ask user to type password
  2. generate hash from that password (in same way like for encryption)
  3. use that hash as key in symetric-key algorithm to decode message.

As you can see, you don't need to store password anywhere in this approach.

Upvotes: 0

Victor
Victor

Reputation: 5769

Inspired by @Kaiido I developed a JavaScript Bookmarklet. So, add it as a new bookmark in your browser and click on it to encrypt the current page. You will be asked to insert a password and choose a location where to save the encrypted HTML page.

javascript:(function (doc) {
    var password = prompt('Enter a password to encrypt this page'),
        js = doc.createElement('script');

    js.onload = function () {
        /* Get current page HTML and use a dirty workaround to convert relative paths to full URLs */
        var page = doc.documentElement.cloneNode(true);
        for (var attr of ['src', 'href']) {
            page.querySelectorAll('[' + attr + ']').forEach(function (node) {
                node[attr] = node[attr];
            });
        }

        /* All the magic belongs to openpgpjs.org */
        openpgp.encrypt({
            message: openpgp.message.fromText(page.outerHTML),
            passwords: [password]
        }).then(function (ciphertext) {
            var link = doc.createElement('a'),
                html = [
                    '<!DOCTYPE html>',
                    '<head>',
                    '  <meta charset="utf-8" />',
                    '</head>',
                    '<body>',
                    '  <textarea id="encryptedMessage" style="display:none">' + ciphertext.data + '</textarea>',
                    '  <script src="' + js.src + '"></script>',
                    '  <script>',
                    '    var field=document.getElementById("encryptedMessage");',
                    '    openpgp.message.readArmored(field.value).then(function(message){',
                    '      var decrypter=openpgp.decrypt({message:message,passwords:[prompt("Enter the password to decrypt this page")]});',
                    '      decrypter.then(function(plaintext){document.documentElement.innerHTML=plaintext.data});',
                    '      decrypter.catch(function(e){alert(String(e))});',
                    '    });',
                    '  </script>',
                    '</body>'
                ].join('\n');

            doc.body.appendChild(link);
            link.download = 'protected.html';
            link.href = 'data:text/html;,' + encodeURIComponent(html);
            link.click();
            doc.body.removeChild(link);
        });
    };

    /* This will fail if Content Security Policy prohibits embedded scripts */
    js.src = 'https://cdnjs.cloudflare.com/ajax/libs/openpgp/4.3.0/compat/openpgp.min.js';
    doc.body.appendChild(js);
})(document);

An example of encrypted page can be found here: http://fiddle.jshell.net/yjLwq0mx/show/light/

Upvotes: 0

Kaiido
Kaiido

Reputation: 136755

Yes, you can achieve pretty decent front-side encryption, thanks to the SubtleCrypto API.
There is even a js port of openpgp.

So yes, you could ultimately encode whatever data you wish as pgp message for instance, then require a password to decrypt it and use it.

If you need to encrypt your message:

(async function() {
  const cleartext = 'alert("an useless message")';
  const msg = openpgp.message.fromText(cleartext);
  const ciphertext = await openpgp.encrypt({
    message: msg,
    passwords: ["mypassword"],
    armor: true
  });
  // you'd be better saving as a text file though,
  // ecnoding and new line characters matter
  console.log(ciphertext.data);
})()
.catch(console.error);
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/openpgp.min.js"></script>

And then to decrypt it:

(async function() {
  const password = prompt('enter password ("mypassword")');
  // one of the results of previous snippet
  const encrypted = `-----BEGIN PGP MESSAGE-----
Version: OpenPGP.js v4.3.0
Comment: https://openpgpjs.org

wy4ECQMI61wIzRzOswzg/j6zhPvasbu97nt+XeD23m3UNnc8J3SqAGiogvn8
zqKD0lMB49BViJ8gQ7E/6If6vaCv9NBojjVgS9P2E7mROtZrbz5Z150ohcKV
kDncF//Io6sb/5L/5AcLXBxCJzhQKIYwtIdHu9paWGpEto1z5EzOGzpZgg==
=hMhM
-----END PGP MESSAGE-----`;
  const decrypted = await openpgp.decrypt({
    message: await openpgp.message.readArmored(encrypted),
    passwords: [password]
  })
  const cleartext = decrypted.data;
  console.log(cleartext);
  new Function(cleartext)();
})()
.catch(console.error);
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/openpgp.min.js"></script>

Upvotes: 4

Jay
Jay

Reputation: 373

No. Well, not anything that offers anything remotely secure. You could hide a password in the javascript, maybe encoded as base64 and then compare the value of an input field to the stored base64 code, but anyone that knows anything about "view source" or javascript would easily be able to circumvent this.

Password authentication and other sensitive information must be processed on the server side, where users can't get to it!

Upvotes: 1

Related Questions