Encrypting user credentials, session & recovery data for better application security

Log-ons used to be simple — type in a username and password, match it against a database and generate a session.

This common logon method is used in WordPress blogs, countless web applications and has practically become standard because of its ease of use.

Key loggers, prying eyes and poor password choices have created a culture of two-factor authentication by requirement. It’s simply too easy to break into accounts that use just an ID and password as credentials.

In light of this future, I recommend following a strict set of guidelines that can be implemented into your applications at any time.

These concepts work at scale and with stateless client applications.

 The General Problem

PII (Personally Identifiable Information) is easily matched to a user’s credentials. An ID of the user’s profile, email address, date of birth and other sensitive information that is primarily used to identify them could be easily extracted from your database in a direct breach.

Simply knowing something about the user is enough to gain access to their account in most cases. An email address as key can be used to catch users out with a phishing email.

 Encrypting Credentials

Keeping a repository of encrypted credentials is a good start. Proceeding through a series of steps to access a profile is a good first step.

My approach:

  1. Obtain Credentials (Email, Password, Social ID, Tokens)
  2. Decrypt Credential Object
  3. Generate Session Object
  4. Monitor Future Sessions

 Obtain Credentials

I’m going to use MongoDB, JavaScript & some pseudo code to help describe my process.

Before you can begin, you need to know how you want your users to log-on and what is the best way to identify them. For this example, we’ll use Email + Password and Facebook as well as a One-Time Token Generator.

When a user signs onto your application, they provide their credentials to your server.

{
   "email": "[email protected]",
   "password": "password1"
}

Bob, our test subject, has garbage credentials. But, it’s still not enough to find his account within our database as we’re combining all of this information to find this credential.

// This would be a basic email / pass method handler
const shaCreds = hash(req.body.email + req.body.password);

const account = DB.collection('credentials').findOne({ credentials: shaCreds });

By hashing the credentials and using them as the key to find the server-side credentials object, we’re preventing discovery by using merely an email address.

 Decrypt Credential Object

Once we’ve found the record, we’ll use just the user ID to decrypt the object —  in this case, the email.

// Hash the email address

const shaID = hash(req.body.email)

const { encryptedIdentity } = account;

const decryptedIdentity = decrypt(encryptedIdentity, shaID);

In this object bundle, we want to make sure we don’t use anything helpful — like whole words “_id”, “email”, “record”, etc. This can assist in automatic decryption. My suggestion would be to keep it simple and unstructured.

In this example, our decrypted object contains an ID to the Encrypted Profile and a Token to decrypt it.

{
   "08bhdoiqwe97c2": "2h3o84cf72b384cn23iu4hflmxi2l3rif7gciunh234lifuchmo2384fxmoi"
}

We’ll then go and get the user’s profile from this record.

const user = DB.collection('profiles').findOne({ _id: Object.keys(decryptedIdentity)[0] })

 Generate Session Object

The profile has been encrypted, so we’ll use the same method to decrypt it on-the-fly using a JWT & session.

const _id = Object.keys(decryptedIdentity)[0];
const JWT = jwt.sign({
   sub: _id,
   key: decryptedIdentity[_id]
}, SOME_SECRET, { expires_in: '7 days' });

The user can now access their profile by using a JWT that provides the server with the information needed to decrypt their profile for the duration of their session.

We’ll then store some information about the session in our database to ensure that we can keep track of changes and block any weirdness.

DB.collection('sessions').insertOne({
   browser: req.browser_data,
   jwt: JWT
   last_use: DateTime
});

If any insecure activity occurs, or the user intends on signing out of all devices, we can simply change the decryption key stored in their credentials and re-issue their credentials.

 Recovery Data

Letting users recover their account follows the same process as above. Determine your user’s minimum recovery questions, and create recovery credential objects for each.

 Multiple Authentication Methods

Let’s say your user adds Facebook as a sign-on method. Simply follow the steps from above to create a new credentials object with the Facebook ID as the key.

 Relationships & Sharing

In the case that one user may need to see information stored in another’s profile, a key can be shared to that profile or a group allowing cross-group sharing. The requesting user’s key provides access to the group — the group’s keys provide access to any profiles that are being requested.

This would provide you with a method of completely encrypting your data. It also prevents hackers from rebuilding your database as all relational keys are encrypted also.

 Summary

This is a very roundabout method of providing some extra security on your server side and when handling JWTs. I’ve always been very concerned with the revocation process and optimising servers for efficiency, but ultimately if you want to be exceptionally secure — this is the way to go.

If you find any flaws, have questions or have suggestions for improvements, please get in touch — [email protected].

 
0
Kudos
 
0
Kudos

Now read this

Using Web Workers to run JavaScript in parallel

JavaScript, in the browser, runs in a single thread. This is fine, and works fairly well for most websites. However, when running large, complex tasks or long scripts, it makes the webpage unresponsive. The best way to speed up these... Continue →