Prior to the incalculably game-changing Snowden revelations of 2013 privacy was something assumed by most as being guaranteed. Following them, not so much. The credibility of cryptographic algorithms came into dispute, as did the credibility of most major telco’s, service providers and equipment manufacturers (Facebook, Google, Microsoft, Cisco etc).

A project of mine, now dubbed Akelarre (the meeting place of witches), began several months ago as an experiment with a real-time server>client communication library named SignalR built on top of PostgreSQL.


the main page

I wanted to develop a simple group chat application. As most projects tend to, this quickly spiraled out of control as I came up with more and more ideas: server-side encryption, per-group encryption, secure invites and secure file uploads… but then I thought: but what if the admin goes rogue or the database and key(s) are compromised?

Enter the concept of host-proof applications, where the host cannot be trusted with sensitive data.

Note: This project is no longer maintained. I may revisit it at a later date.

signalr

SignalR is a library for ASP.NET applications that makes real-time bi-directional communication incredibly easy to implement. A SignalR application consists of a server-side hub class that defines server-side functions, and client-side Javascript that – obviously – defines client-side functions. You are able to call client-side methods from the server and vice-versa, and you are able to create groups within your hub and interact with either specific users, groups or all connected users.

For example, when a user clicks on the button to connect to a room, the Javascript may call a server-side method:

function registerEvents(chatHub) {
 // Start chat.
 $("#btnStartChat").click(function() {
  // Logic goes here.
// Connect user to hub.
chatHub.server.connect(name, group);
});
};

On the server, after the method logic completes, client-side functions are then invoked for both the caller (complete sign-on and display cached messages) and everyone else in their group (inform them a new user has connected):

public void Connect(string userName, string groupName) {
 // Logic goes here.

// If there are messages in the cache, present them.
var groupUsers = UserTools.GetUsersInGroup(groupName);
Clients.Caller.onConnected(id, userName, groupUsers, groupMessages, groupName);

// Send to all except caller client.
Clients.OthersInGroup(groupName).onNewUserConnected(id, userName);
}

standards

Strong cipher suites are supported by both modern OS’s and browsers, and with these DHE/ECDHE provides perfect forward secrecy key exchange. It’s fair to say that cryptography is now well established server-side – with a bit of tweaking. Accompanying Akelarre is a script that implements optimal Schannel configuration (sufficient to achieve an A+ on the Qualys SSL Labs test) and secure host headers in IIS.

However, client-side cryptography is still a much debated topic – but it is getting better:

There are plenty of Javascript cryptography libraries around, however very few that are respectable. After reviewing several I have decided upon the Stanford Javascript Crypto Library to facilitate client-side cryptography:

It’s main contender was CryptoJS. The reasons why I sided with SJCL are:

  1. The credibility of the Stanford University Applied Cryptography group.
  2. Performance benchmarking of the libraries ruled in favor of SJCL. It uses true low-level operations.
  3. CryptoJS does not offer GCM mode AES.

Server-side, Bouncy Castle is used:

With the aforementioned NSA backdooring revelations in mind, favoring Bouncy Castle over Microsoft CNG was a no-brainer.

login

  1. A CAPTCHA challenge and honeypot form verifies the user is human:

  • If the user is new:
  1. The user enters an alias, and uses a mouse movement based random string generator (ntropy.js) to create a passphrase that has at least 128bit of entropy:

  • After the entropy of the passphrase is validated an in-browser random function (crypto.getRandomValues) generates a group name.
  • If the user is invited:
  1. The user visits an invite URL to generate the invite token.
  2. An alias and pre-shared passphrase is entered:
  • A SHA256 hash of the group name, combined with the passphrase, is used to derive a 256bit key using 1000 rounds of PBKDF2. The key is stored in the browser window using HTML5 session storage:
// Key derivation…
var salt = sjcl.hash.sha256.hash(group);
var iterations = 1000;
var keySize = 256;
var derivedKey = sjcl.misc.pbkdf2(pass, salt, iterations, keySize);
derivedKey = sjcl.codec.base64.fromBits(derivedKey);
// Add key to HTML5 session storage.
window.sessionStorage.setItem('groupKey', derivedKey);
// Add group name to HTML5 session storage.
window.sessionStorage.setItem('groupName', group);
  • If the entropy of the passphrase is calculated to be insufficient, login is denied:Or else the group is created and joined, and the chat session initiated:

notes:

  • If a user is inactive for 20 minutes their session is terminated both client-side (expiration of cookie) and server-side (removed from the SignalR group and their session record removed from the SQL database).
  • Users cannot join a group directly, they must be invited:An invite will first encrypt the group name with a random key, and produce a URL like the following:https://example.com/invite.aspx?id=42ugpntEeldqH9FO&key=rlyIKyzfRt.nbUdHCbuav.Y1vOBvIuxsXOAmjwc8Asq

    When an invitee visits this URL the key is used to decrypt the string stored in the database against the invite ID. Successful decryption provides the user a single-use token which ensures they join the correct group.

messaging

Sending:

  1. The key is pulled from session storage.
  2. If the message is an image it is first converted into a base64 data URI.
  3. A random IV is generated using SJCL’s PRNG (pseudo random number generator).
  4. The message is encrypted and sent to the server along with the IV, where the plaintext elements (user and group names) are first sanitised using the Microsoft Web Protection libraries before the message is both cached in SQL using a parameterised query builder, and forwarded to group members (group messaging) or a specific user (private messaging, where no caching takes place).
  5. After 20 minutes the message is removed from the SQL database.
group messaging
private messaging
how the ‘messages’ table looks (image ‘message’ data is not returned due to it’s size)
// Get encryption requisites.
var userName = $('#hdUserName').val();
var key = window.sessionStorage.getItem('groupKey');
var groupName = window.sessionStorage.getItem('groupName');
var msg = $("#txtMessage").val();

var iv = sjcl.codec.base64.fromBits(sjcl.random.randomWords(4, 2)); var p = { “iv”: iv, “ts”: 128, “mode”: “gcm”, “adata”: groupName, “cipher”: “aes”, “key”: key }; var prp = new sjcl.cipher[p.cipher](sjcl.codec.base64.toBits(p.key)); // Encrypt. msg = sjcl.codec.base64.fromBits(sjcl.mode[p.mode].encrypt(prp, sjcl.codec.utf8String.toBits(msg), p.iv, p.adata, p.ts)); // Error handling goes here. // Send message to server. chatHub.server.sendMessageToGroup(userName, msg, groupName, iv);

client code

// Sanitize input.
userName = SecurityOps.Sanitize(userName);
groupName = SecurityOps.Sanitize(groupName);
// Broadcast message immediately.
Clients.Group(groupName).messageReceived(userName, message, iv);

// Update user activity.
UserTools.UpdateSessionById(SecurityOps.ReadTokenInCookie());

// Store message in encrypted cache.
AddMessageinCache(userName, message, groupName, iv);


server code

Receiving:

  1. The key is pulled from session storage.
  2. The JSON message is serialised to extract a username, message and IV.
  3. The IV and key are used to decrypt the message.
  4. Upon successful decryption the username and message are sanitised using the OWASP ESAPI library, then displayed in the chat window (or private messaging window).
// Get group key.
var key = window.sessionStorage.getItem('groupKey');
// Get group name.
var groupName = window.sessionStorage.getItem('groupName');
var p = {
"iv": iv,
"ts": 128,
"mode": "gcm",
"adata": groupName,
"cipher": "aes",
"key": key
};

var prp = new sjcl.cipher[p.cipher](sjcl.codec.base64.toBits(p.key));

// Decrypt.
var msg = sjcl.codec.utf8String.fromBits(sjcl.mode[p.mode].decrypt(prp, sjcl.codec.base64.toBits(message), p.iv, p.adata, p.ts));

secret sharing

One challenge faced was a means of securely sharing the key. I didn’t want to transfer it through the server, and should group members assume they are being monitored it was unsafe to send it through a single channel of communication. So, naturally… I looked at what the military was doing.

One solution stood out: Shamir’s Secret Sharing Scheme. The scheme defines a method for sharing secret data, whereby the data is divided into multiple parts and a threshold set for the number of parts required for reconstruction of the secret.

For example, a passphrase can be split into 6 parts and a quorum value of 4 set. Each of the parts is sent using a different channel of communication (SMS, email, forum message, carrier pidgeon, etc). If 2 or 3 of the channels are compromised then the secret cannot be reconstructed by the intercepting party, but if all receive the secret successfully then only the recipient will know the secret.

Using secrets.js, converting a message into secret parts is very easy:

function doEncoding() {
    var strSecret = document.getElementById("txtSecret").value;
    strSecret = secrets.str2hex(strSecret);
    var shares = parseInt(document.getElementById("cmbShares").value);
    var quorum = parseInt(document.getElementById("cmbQuorum").value);
var strOutputs = secrets.share(strSecret, shares, quorum);

document.getElementById("txtSecret").value = "";
document.getElementById("txtMulti").value = "";
for (var i = 0; i < strOutputs.length; i++) {
document.getElementById("txtMulti").value += strOutputs[i] + "\n";
}

$('#txtMulti').show();
$('#txtMulti').focus();
$('#txtMulti').select();
}
constructing secret parts

Reassembling them is just as easy:

function doDecoding() {
    var lines = document.getElementById("txtInput").value.split("\n");
    lines = cleanArray(lines);
var strSecret = secrets.combine(lines);
strSecret = secrets.hex2str(strSecret);

document.getElementById("txtDecoded").value = strSecret;

document.getElementById("txtInput").value = "";

$('#txtDecoded').show();
$('#txtDecoded').focus();
$('#txtDecoded').select();
}
reconstructing a secret
 

summary

Is it perfect? Not quite – I don’t think that’s possible. However, I do believe it provides a very comfortable level of security for users, but you can be the judge of that.
This project is hosted on Bitbucket.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s