Simple Javascript Password Encryption & Decryption

Welcome to a tutorial on how to encrypt and decrypt passwords in Javascript. First, I would like to give you guys a piece of good news – Yes, Javascript does have a native web crypto API that we can use to protect the passwords, and there are plenty of free libraries all over the Internet as well.

But now for the bad news – Password encryption only makes sense if you are working on server-side Javascript (NodeJS), it pretty much does nothing good on the client-side. Just why is that so? Let us explore that in-depth by walking through a couple of examples on both the server and client-side Javascript. Read on to find out!

ⓘ I have included a zip file with all the example source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.

 

TABLE OF CONTENTS

Download & Notes Client-Side Passwords Server-Side Passwords
Useful Bits & Links The End

 

 

DOWNLOAD & NOTES

Firstly, here is the download link to the example code as promised.

 

EXAMPLE CODE DOWNLOAD

Click here to download the source code, I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

 

QUICK NOTES

If you spot a bug, please feel free to comment below. I try to answer questions too, but it is one person versus the entire world… If you need answers urgently, please check out my list of websites to get help with programming.

 

CLIENT-SIDE PASSWORDS

Let us start with how to do password encryption/decryption on client-side Javascript (that is on a web page or web app) – Also on why most web developers won’t bother doing this at all.

 

BASIC JAVASCRIPT CRYPTO

For the purpose of demonstrating that Javascript is capable of doing crypto stuff, here is an example that rides on top of a good old library called Crypto-JS.

1-basic-password.html
<!-- (A) LOAD CRYPTO JS LIBRARY -->
<!-- https://cryptojs.gitbook.io/docs/ -->
<!-- https://cdnjs.com/libraries/crypto-js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
<script>
// (B) ENCRYPT & DECRYPT FUNCTIONS
var crypt = {
  // (B1) THE SECRET KEY
  secret : "CIPHERKEY",
 
  // (B2) ENCRYPT
  encrypt : function (clear) {
    var cipher = CryptoJS.AES.encrypt(clear, crypt.secret);
    cipher = cipher.toString();
    return cipher;
  },
 
  // (B3) DECRYPT
  decrypt : function (cipher) {
    var decipher = CryptoJS.AES.decrypt(cipher, crypt.secret);
    decipher = decipher.toString(CryptoJS.enc.Utf8);
    return decipher;
  }
};
 
// (C) TEST
// (C1) ENCRYPT CLEAR TEXT
var cipher = crypt.encrypt("FOO BAR");
console.log(cipher);
 
// (C2) DECRYPT CIPHER TEXT
var decipher = crypt.decrypt(cipher);
console.log(decipher);
</script>

Yep, that’s all to it. Just plug-and-play the library to do all the heavy lifting.

 

 

CLIENT-SIDE PASSWORD ENCRYPTION – IT’S BAD

THE SIGN-UP PAGE EXAMPLE

With the baseline now in place, let us walk through why client-side password encryption is a waste of time and why you should never do it. A common example is that beginners think they can “secure” the password by encrypting it on the user registration page:

2a-signup.html
<!-- (A) LOAD CRYPTO JS LIBRARY -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js"></script>
<script>
// (B) ENCRYPT PASSWORD
function crypt(){
  // (B1) GET PASSWORD + ENCRYPT
  var password = document.getElementById("user-password");
  var cipher = CryptoJS.AES.encrypt(password.value, "CIPHERKEY");
  cipher = cipher.toString();

  // (B2) SET ENCRYPTED PASSWORD TO HIDDEN FIELD
  document.getElementById("user-real-password").value = cipher;

  // (B3) REMOVE CLEAR TEXT PASSWORD
  password.value = "";

  // (B4) SUBMIT FORM
  return true;
}
</script>
 
<!-- (C) REGISTRATION FORM -->
<form onsubmit="return crypt()">
  <input type="email" name="email" required value="john@doe.com"/>
  <input type="password" id="user-password" required value="Hello World 12345"/>
  <input type="hidden" id="user-real-password" name="password"/>
  <input type="submit" value="Register"/>
</form>

Hey, a pretty neat trick, right? Now the password is encrypted and secured – We don’t have to worry about it being “hijacked” by other people when the form is being submitted.

 

HOW ABOUT THE DECRYPTION DURING LOGIN?

Next, let us assume that the user data will be saved into the server’s database upon successful registration. Then comes the following question – How do we check and confirm the password? Some beginners will then pull the password data straight from the server and verify it using Javascript:

2b-login.html
<!-- (A) LOAD CRYPTO JS LIBRARY -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js"></script>
<script>
// (B) PROCESS LOGIN
function login(){
  // (B1) SET EMAIL
  var data = new FormData();
  data.append('email', document.getElementById("user-email").value);
 
  // (B2) AJAX REQUEST - GET + CHECK PASSWORD FROM SERVER
  var xhr = new XMLHttpRequest();
  xhr.open('POST', "get-user.txt");
  xhr.onload = function(){
    let response = JSON.parse(this.response),
        decipher = CryptoJS.AES.decrypt(response.password, "CIPHERKEY").toString(CryptoJS.enc.Utf8);
 
    // MATCH!
    if (decipher == document.getElementById("user-password").value) {
      alert("OK!");
      // location.href = "members.html";
    }
    // WRONG
    else { alert("Invalid password"); }
  };
 
  // (B3) GO!
  xhr.send(data);
  return false;
}
</script>
 
<!-- (C) LOGIN FORM -->
<form onsubmit="return login()">
  <input type="email" id="user-email" required value="john@doe.com"/>
  <input type="password" id="user-password" required value="Hello World 12345"/>
  <input type="submit" value="Sign in"/>
</form>

 

WHY THIS IS A WASTE OF TIME

While this may seem to work at the first impression, but there are a lot of security loopholes in this design – Let us go through them one-by-one. Firstly, remember the server-client model?

Javascript runs on the user’s computer and it is fully visible to the users. Meaning, all the code ninjas will know which algorithm you are using to encrypt/decrypt the passwords – They can easily reverse engineer and harvest all the passwords of all the users. No sweat.

If that is not already a critical flaw, then consider these – The secret key is also in plain sight. What good does that serve? Also, what is stopping people from directly entering the member’s page? How do you determine who is signed in or not? How do you track the users, and how do you kick unauthorized users off?

 

 

THE CORRECT WAY

So the only correct way to properly protect the password is to encrypt/verify on the server-side. This is the only way to keep the password crypto hidden away from the users. Oh, and if you are worried about the passwords being “hijacked” when the registration form is being submitted… There is a very easy solution.

That is called “just enforce HTTPS on your server”. Yep, once it’s HTTPS, all data exchange between client and server is automatically encrypted. No need for all the manual Javascript encryption gimmicks.

P.S. If you are using PHP on the server-side, I will leave links below to my other tutorials on how to create a proper registration and login page.

 

SERVER-SIDE PASSWORDS

Now let’s move on to examples of how we can do password encryption and decryption in NodeJS. Also on why 2-way encryption might not be the best.

 

GET CRYPTOJS

To get the CryptoJS library, simply navigate to your project folder in the command line and run npm i crypto-js.

D:\http\project>npm i crypto-js
npm WARN saveError ENOENT: no such file or directory, open 'D:\http\project\package.json'
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN enoent ENOENT: no such file or directory, open 'D:\http\project\package.json'
npm WARN project No description
npm WARN project No repository field.
npm WARN project No README data
npm WARN project No license field.

+ crypto-js@3.1.9-1
added 1 package from 1 contributor and audited 1 package in 1.445s
found 0 vulnerabilities

 

 

ENCRYPT-DECRYPT

3-encrypt-decrypt.js
// (A) LOAD ENCRYPT LIBRARY
const CryptoJS = require("crypto-js");

// (B) SECRET KEY
var key = "ASECRET";

// (C) ENCRYPT
var cipher = CryptoJS.AES.encrypt("PASSWORD", key);
cipher = cipher.toString();
console.log(cipher);

// (D) DECRYPT
var decipher = CryptoJS.AES.decrypt(cipher, key);
decipher = decipher.toString(CryptoJS.enc.Utf8);
console.log(decipher);

Yep, as simple as that – We are still using the same library, and it works just the same as on the client-side.

 

WARNING: 2-WAY ENCRYPTION IS PRETTY BAD!

Before the hater troll things start to spew acid – I don’t really recommend using 2-way encryption for passwords. I.E. Encrypted passwords that can be decrypted. Consider this – If the secret key is compromised, the bad code ninja can pretty much retrieve all the passwords in the system.

 

PASSWORD WITH SALT WITH NODEJS

So for you guys who are thinking of using 2-way encryption for the convenience of password recovery, you have been informed. The safer way is to do a one-way hash with salt instead. Credits to CipherTrick for this one done with simple SHA512.

4-hash-salt.js
// Credits : https://ciphertrick.com/salt-hash-passwords-using-nodejs-crypto/
// (A) REQUIRE CRYPTO LIBRARY
var crypto = require('crypto');

// (B) CREATE PASSWORD HASH
var creepy = function (clear) {
  // Generate random salt
  let length = 16;
  let salt =  crypto.randomBytes(Math.ceil(length / 2))
  .toString('hex') 
  .slice(0, length); 

  // SHA512 at work
  let hash = crypto.createHmac('sha512', salt);
  hash.update(clear);
  return {
    salt: salt,
    hash: hash.digest('hex')
  };
};

// (C) TEST ENCRYPT
// Save BOTH the password and salt into database or file
var clearpass = "He110Wor!d";
var creeped = creepy(clearpass);
console.log("===== HASHED PASSWORD + SALT =====");
console.log(creeped);

// (D) VALIDATE PASSWORD
var validate = function (userpass, hashedpass, salt) {
  let hash = crypto.createHmac('sha512', salt);
  hash.update(userpass);
  userpass = hash.digest('hex');
  return userpass == hashedpass;
};

// (E) TEST VALIDATE
// clearpass = "FOOBAR";
var validated = validate(clearpass, creeped.hash, creeped.salt);
console.log("===== VALIDATION =====");
console.log("Clear password: " + clearpass);
console.log("Validation status: " + validated);

 

 

USEFUL BITS & LINKS

That’s all for this guide, and here is a small section on some extras and links that may be useful to you.

 

LINKS & REFERENCES

Sorry folks, I am not a security expert, so if you are looking for ways to use the Web Crypto API, here are some good links that I found:

For the people who are on PHP and trying to secure the passwords:

 

OTHER CRYPTO LIBRARIES

Don’t like CryptoJS? Here are a few other options.

 

YOUTUBE TUTORIAL

 

INFOGRAPHIC CHEAT SHEET

Encrypt-Decrypt Password In JS (Click to enlarge)

 

THE END

Thank you for reading, and we have come to the end of this guide. I hope that it has helped you with your project, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!

Leave a Comment

Your email address will not be published. Required fields are marked *