How to Encrypt LocalStorage data?

How to Encrypt LocalStorage data?

Sometimes, you might want to encrypt your data stored in the LocalStorage given that it is easily readable by anyone having sufficient knowledge on where to look. Lets see how we can encrypt localstorage data with ease.

Instead of reinventing the wheel, we are going to use a localStorage wrapper that takes care of checking browser support, supports storing more than just strings in the localStorage and most importantly supports encryption – localstorage-slim.js.

Encrypt Localstorage
Localstorage-slim has an API similar to the LocalStorage API. Besides encryption, it also allows to set an expiry time (ttl) on the data in the LocalStorage, if that’s something that you want.

As per Localstorage-slim’s documentation, the encryption that it provides by default is not a true encryption but a mere obfuscation. However it should keep most of the users at bay. Considering that, we’ll show you a live example of the same.


1. Obfuscate Localstorage data

As described earlier, we will first see what localstorage-slim provides by default. So lets go ahead and install it with NPM.

# install localstorage-slim
npm install localstorage-slim --save

Okay, so we get 2 options,

  • Encrypt all data in localStorage (global encryption)
  • Encrypt part of the data (i.e. only a few key-value pairs)

We’ll see how to encrypt/obfuscate all data first. To enable global encryption is pretty simple. You only need to turn on the encrypt flag as seen below –

import ls from 'localstorage-slim';

// enable global encryption
ls.config.encrypt = true;
// ls.config.secret = 85; // you can provide a global custom secret

// save data in localStorage 
ls.set('hey', 'Hello World');

// data saved gets obfuscated automatically
console.log(localStorage.getItem('hey')); // °··ºk¢º½·¯

// calling get will deobfuscate data automatically
ls.get('hey');  // Hello world

// you can optionally provide a custom secret
ls.set('foo', { bar: "baz" }, { secret: 50 });

// calling get with incorrect/missing secret
ls.get('foo'); // °··ºk¢º½·¯

// using correct secret 
ls.get('foo', { secret: 50 }); // { bar: "baz" }

// disable encryption for a particular field
ls.set('hello', ['Hey', 'beautiful'], { encrypt: false });
ls.get('hello'); // ['Hey', 'beautiful'];

Now, we just dealt with global encryption/obfuscation which encrypts every item added with localStorage-slim. To encrypt/obfuscate only certain key-value pairs, all you gotta do is pass a 3rd parameter to ls.set(...) with { encrypt: true } as shown below –

// encrypt/Obfuscate particular fields (we encrypt "foo")
ls.set('foo', { bar: "baz" }, { encrypt: true });
ls.get('foo'); // °··ºk¢º½·¯
ls.get('foo', { decrypt: true }); // { bar: "baz" }

// encrypt with a custom secret
ls.set('hey', 'how are you', { encrypt: true, secret: 88 });

ls.get('hey'); // "zÀÇÏx¹Ê½xÑÇÍz"
ls.get('hey', { decrypt: true }) // zÀÇÏx¹Ê½xÑÇÍz
ls.get('hey', { decrypt: true, secret: 40 });  // zÀÇÏx¹Ê½xÑÇÍz
ls.get('hey', { decrypt: true, secret: 88 });  // how are you

As seen above, we encrypted/obfuscated the value of “foo” stored in the localstorage. To retrieve the correct value with ls.get(...), you must set the 3rd parameter { decrypt: true }. If you used a custom secret, then the custom secret should also be passed to get the expected value.


Feel free to tinker with this JS fiddle that demonstrates encryption/obfuscation with localstorage-slim


2. Encrypt Localstorage data

Sometimes, you want a real secure encryption, localstorage-slim.js allows you to configure its encryption, decryption functions to provide your custom logic. To do so, update the encrypter() and decrypter() functions as shown –

 
// import ls ...;

ls.config.encrypt = true;
ls.config.secret = '...'; // set a global secret if you wish

/* override encrypter */
ls.config.encrypter = (data, secret) => {
  // your custom logic to encrypt "data"
  // ...
  return encryptedString;
};

/* override decrypter */
ls.config.decrypter = (encryptedString, secret) => {
  // your custom logic to decrypt "data"
  // ...
  return decryptedData;
};

The encrypter and decrypter functions get called internally and perform encryption while setting and decryption while getting data respectively.
Thereafter, you can use ls.set('...')/ls.get('...') as you normally would.

/* You can use global encryption */
ls.config.encrypt = true;
ls.config.secret = '...';

ls.set('Hi', 'Hello'); // data gets encrypted as per your logic
ls.get('Hi');          // original data is returned after decryption


/* You can encrypt only certain fields */
ls.config.encrypt = false; // default is false

// encrypt data
ls.set('Hi', 'Hello', { encrypt: true, secret: '...' });
// decrypts and returns original data
ls.get('Hi', { decrypt: true, secret: '...' });

You sure are free to write your own algorithm to encrypt data, however, we’re going to use a ready-made, well established and secure algorithm provided by the popular crypto-js library. CryptoJS provides several ciphers/algorithms like AES, TDES, RC4 and Rabbit. As an example, we will be using the AES algorithm. But you are free to use any.

So lets go ahead and install it…

# install localstorage-slim and crypto-js
npm install localstorage-slim --save
npm install crypto-js --save

Next, we need to update localstorage-slim’s config to make it use the encryption provided by cryptoJS.

import ls from 'localstorage-slim';
import encUTF8 from 'crypto-js/enc-utf8';
import AES from 'crypto-js/aes';
// import tripleDES from 'crypto-js/tripledes'; // to use TDES, replace AES by tripleDES
// import RC4 from 'crypto-js/rc4';             // to use RC4, replace AES by RC4
// import rabbit from 'crypto-js/rabbit';       // to use rabbit, replace AES by rabbit

// update localstorage-slim
ls.config.encrypt = true;             // global encryption
ls.config.secret = 'secret-string';   // global secret

// update encrypter to use AES encryption
ls.config.encrypter = (data, secret) => AES.encrypt(JSON.stringify(data), secret).toString();

// update decrypter to decrypt AES-encrypted data
ls.config.decrypter = (data, secret) => {
  try {
    return JSON.parse(AES.decrypt(data, secret).toString(encUTF8));
  } catch (e) {
    // incorrect/missing secret, return the encrypted data instead
    return data;
  }
};

// use ls.set() to encrypt data  
ls.set('number', 25865); // "U2FsdGVkX1/eFb+LVJ4rpM7NN4KiS4ComYEhS4NvF3s="
ls.set('text', 'hello world');  // "U2FsdGVkX1/rYEw5ozWDDBDwsrbMnFkCiUMX7ZCmYyM="
const myObject = {
  foo: 'bar',
  krypton: { clark: 'kent' }
}
ls.set('object', myObject); // "U2FsdGVkX19dt+N13QlAiCBbfiFSyH92lt84xn16Y4gsvFDozGESNhhMvNcX1EiHC3/3YXYWujygFvwT9bFkpA=="

// use ls.get() to decrypt data
ls.get('number'); // 25865
ls.get('text');   // hello world
ls.get('object'); // { foo: 'bar', krypton: { clark: 'kent' } };

// encrypt data with a different secret
ls.set('foo', 'bar', { secret: 'new-password' }); // "U2FsdGVkX1+LlKYCJ+uPdJoyWQQKwrbGd4mwk15zdkk="
ls.get('foo'); // "U2FsdGVkX1+LlKYCJ+uPdJoyWQQKwrbGd4mwk15zdkk="
ls.get('foo', { secret: 'new-password' }); // bar

The above example shows how you could setup global encryption (i.e. encrypt all items in the localstorage). Many a times, that is not desired and you want to encrypt only certain key-value pairs. The code snippet below shows how to do it –

/* we use the same "ls.config" as defined above (encrypter, decrypter,..)
 There is only 1 change, we disable global encryption as seen below */

ls.config.encrypt = false;        // default is false
ls.config.secret = 'secret-word'; // global secret

ls.set('hi', 'hello');    // not encrypted
ls.set('hey', 'Bonjour', { encrypt: true });  // encrypted using global secret
ls.get('hey'); // "U2FsdGVkX1+Xz0Walh44lamoxF/rsJD51HiZXh0iiFU="
ls.get('hey', { decrypt: true }); // Bonjour (decrypts using global secret)

// if you wish to use a different secret than the global secret
ls.set('hey', 'Bonjour', { encrypt: true, secret: 'my-password' });  // encrypted
ls.get('hey'); // "U2FsdGVkX18r43fSTa2JgCAUPbzR5i4+CT9rcb2sTcg="
ls.get('hey', { decrypt: true }); // "U2FsdGVkX18r43fS5i4+CT9rcb2sTcg=" (tries to decrypt using global secret, but fails) 
ls.get('hey', { decrypt: true, secret: 'my-password' }); // Bonjour

That’s about it! Your data will be secured successfully within LocalStorage. Here’s a JS fiddle to demonstrate the working of the same.


Note:

If you’re using a global secret for encryption, ensure to not expose “ls” to the window object as there could be a risk for exposing your secret. It is therefore recommended to only encrypt data that really needs to be encrypted (i.e. not use global encryption if “ls” is exposed to the window object).


Bonus

Do you wish to set a ttl (an expiry time) on your encrypted data ? Well, then that’s pretty simple with localstorage-slim. All you need to do is to provide an additional ttl option expressed in seconds as seen below

// encrypts and sets ttl to 5 secs
ls.set('hey', 'Bonjour', { encrypt: true, ttl: 5 });  

// within 5 secs
ls.get('hey', { decrypt: true }); // Bonjour

// after 5 secs
ls.get('hey', { decrypt: true }); // null

The data expires after 5 seconds and is removed from the localStorage too.
Hope this helps 🙂


References

Leave a Reply