IT history is plastered with failures to keep secrets, such as when millions of user names and passwords were offered for sale on a shady dark website.
I recently had to solve a variant of this task. While integrating a shipping service into an e-commerce site I stumbled onto the simple question: how to store the API credentials of the shipping service in the shop database?
The challenge is that we can’t use hashing, as we would do for user passwords (this excellent article explains in detail why hashing is the only safe option for user passwords). We need the credentials to access the API, hence we need to reveal them upon retrieval from the store — as shown simplified in the pic below.
Encryption comes to mind. AES-256 bit encryption is deemed today as the state of the art because trying out all keys until you get the right one might take great resources and a long time. Encryption alone has its risks, however. In the simplest case, a mindless user chooses the word “password” for the password and suddenly the potential hacker may have an easier task because they only need to try until the password is revealed.
One way to circumvent this problem is to obfuscate the credentials before encrypting them. That can be done very easily and fast.
There are many other ways to obfuscate text. Let’s look at a simple one, for the purpose of demonstration: spread the credentials characters among a larger string —like spreading some pepper in a plate of salt.
// obfuscate and encrypt credentials
const saltCredentials = "jf02heg9u64a{%m<83#@;Pxrjg17uyr#@&*%^Y";
// encode credentials before storing
function encodeCredentials(crds){
// json object expected e.g. {'api-id':'K0xf56g', 'pwd':'Some.Pa$$w0rd'}
const crd = JSON.stringify(crds);
const len = crd.length;
// fail if json longer thatn 159 chars. Due to storing the length in one byte
if (len > 159) return null;
let s = Array.from(saltCredentials);
let i = 0, j = 2, step = Math.floor(s.length / len);
// make sure the pepper is well salted (at least 3 bytes in between)
while(step <= 2){
s = s.concat(s.reverse());
step = Math.floor(s.length / len);
}
// encode length and step in the first two bytes
s.splice(0, 0, String.fromCharCode(96 + len));
s.splice(1, 0, String.fromCharCode(96 + step));
// pepper the salt
while( i < len ){
s.splice(j, 0, crd.charAt(i++));
j += step;
}
// AES encrypt to wrap it up
return CryptoJS.AES.encrypt(s.join(''), saltCredentials).toString();
}
We insert consecutively each character from the input crds
between every Nth byte of saltCredentials
and pay attention to make the latter long enough to host the full input. We also need to keep the length of the original and the step increase for the reverse extraction. We store this info in the first two bytes of the resulting payload before encrypting it.
The reverse process is equally simple. We decrypt first before extracting the meta info from the first two bytes from the payload. Then loop the extraction of the original pepper from the salt.
// decode credentials upon receiving them from store
function decodeCredentials(crd){
// decrypt it first
const dec =
CryptoJS.AES.decrypt(crd, saltCredentials).toString(CryptoJS.enc.Utf8);
// extract the creds length and pepper step
const len = dec.charCodeAt(0) - 96;
const step = dec.charCodeAt(1) - 96;
let i = 0, j = 2, d = [];
// extract the pepper from the salt
while( i < len ){
d[i++] = dec[j];
j += step;
}
// return the json object
return JSON.parse(d.join(''));
}
Is this hackerproof? No. If a hacker is intended to spend resources on cracking your scheme, they will manage. Will it make it more difficult? Probably, especially because they won’t be able to tell which decryption key is the right one. The final bit is then to hide the software, which can be done easily by encapsulating it with a php
wrapper.
There are no limits as to making obfuscation more complex. The one above is meant to inspire to develop your own.
Happy puzzling!
The full code is on GitHub too. Comments and improvements are welcome.
Thank you for reading. I hope this was interesting.