Use asymmetric signing keys instead of HMAC

Participants now generate RSASSA-PKCS1-v1_5 public/private key pairs
and sign messages with their private key. Recipients verify signatures
with the sender’s public key.
This commit is contained in:
Alan Friedman 2016-01-26 15:05:17 -05:00
parent 55feed3257
commit 323c7a903d
2 changed files with 54 additions and 64 deletions

View File

@ -18,25 +18,23 @@ Build source
### How it works
Darkwire uses a combination of asymmetric encryption (RSA-OAEP), symmetric session keys (AES-CBC) and signing keys (HMAC) for security.
Darkwire uses a combination of asymmetric encryption (RSA-OAEP), asymmetric signing (RSASSA-PKCS1-v1_5) and symmetric session encryption (AES-CBC) for security.
Here's an overview of a chat between Alice and Bob (also applies to group chats):
1. Bob creates a room and immediately creates a public/private key pair (RSA-OAEP).
2. Alice joins the room and also creates a public/private key pair. She is sent Bob's public key and she sends Bob her public key.
3. When Bob goes to send a message, three things are created: a session key (AES-CBC), a signing key (HMAC SHA-256) and an initialization vector (used in the encryption process).
4. Bob's message is encrypted with the session key and initialization vector, and a signature is created using the signing key.
5. The session key and signing key are encrypted with each recipient's public key (in this case only Alice, but in a group chat multiple).
6. The encrypted message, initialization vector, signature, encrypted session key and encrypted signing key are sent to all recipients (in this case just Alice) as a package.
7. Alice receives the package and decrypts the session key and signing key using her private key. She decrypts the message with the decrypted session key and vector, and verifies the signature with the decrypted signing key.
1. Bob creates a room and immediately creates both a primary public/private key pair (RSA-OAEP) and a signing public/private key pair (RSASSA-PKCS1-v1_5).
2. Alice joins the room and also creates primary and signing public/private key pairs. She is sent Bob's two public keys and she sends Bob her two public keys.
3. When Bob goes to send a message, two things are created: a session key (AES-CBC) and an initialization vector (these are generated every time a new message is sent).
4. Bob's message is encrypted with the session key and initialization vector, and a signature is created using his private signing key.
5. The session key is encrypted with each recipient's public key (in this case only Alice, but in a group chat multiple).
6. The encrypted message, initialization vector, signature and encrypted session key are sent to all recipients (in this case just Alice) as a package.
7. Alice receives the package and decrypts the session key using her private key. She decrypts the message with the decrypted session key and vector, and verifies the signature with Bob's public signing key.
Group chats work the same way because in step 5 we encrypt keys with everyone's public key. When a message is sent out, it includes encrypted keys for everyone in the room, and the recipients then pick out the ones for them based on their user ID.
Group chats work the same way because in step 5 we encrypt the session key with everyone's public key. When a message is sent out, it includes encrypted keys for everyone in the room, and the recipients then pick out the ones for them based on their user ID.
### [Man-in-the-middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack)
Darkwire does not provide any guarantee that the person you're communicating with is who you think they are (authentication).
A good idea is to ask the recipient a question, the answer to which only they should know. Or, ask them to enter a pass phrase you've agreed upon in advance. This functionality may be incorporated In future versions.
Darkwire does not provide any guarantee that the person you're communicating with is who you think they are. Authentication functionality may be incorporated in future versions.
### Sockets & Server

View File

@ -90,22 +90,31 @@ $(function() {
$inputMessage.focus();
Promise.all([
createPrimaryKeys()
createPrimaryKeys(),
createSigningKeys()
])
.then(function(data) {
keys = {
public: data[0].publicKey,
private: data[0].privateKey
primary: {
public: data[0].publicKey,
private: data[0].privateKey
},
signing: {
public: data[1].publicKey,
private: data[1].privateKey
}
};
return Promise.all([
exportKey(data[0].publicKey, "spki")
exportKey(data[0].publicKey, "spki"),
exportKey(data[1].publicKey, "jwk")
]);
})
.then(function(exportedKeys) {
// Tell the server your username and send public keys
socket.emit('add user', {
username: username,
publicKey: exportedKeys[0]
primaryPublicKey: exportedKeys[0],
signingPublicKey: exportedKeys[1],
});
});
}
@ -299,13 +308,15 @@ $(function() {
let promise = new Promise(function(resolve, reject) {
let currentUser = user;
Promise.all([
importPrimaryKey(currentUser.publicKey, "spki")
importPrimaryKey(currentUser.primaryPublicKey, "spki"),
importSigningKey(currentUser.signingPublicKey)
])
.then(function(keys) {
users.push({
id: currentUser.id,
username: currentUser.username,
publicKey: keys[0]
primaryPublicKey: keys[0],
signingPublicKey: keys[1]
});
resolve();
});
@ -356,17 +367,12 @@ $(function() {
let secretKeys;
let messageData;
let signature;
let signingKey;
let encryptedMessageData;
// Generate new secret key and vector for each message
createSecretKey()
.then(function(key) {
secretKey = key;
return createSigningKey();
})
.then(function(key) {
signingKey = key;
// Generate secretKey and encrypt with each user's public key
let promises = [];
_.each(users, function(user) {
@ -380,25 +386,14 @@ $(function() {
// Export secret key
exportKey(secretKey, "raw")
.then(function(data) {
return encryptSecretKey(data, thisUser.publicKey);
return encryptSecretKey(data, thisUser.primaryPublicKey);
})
.then(function(encryptedSecretKey) {
let encData = new Uint8Array(encryptedSecretKey);
secretKeyStr = convertArrayBufferViewToString(encData);
// Export HMAC signing key
return exportKey(signingKey, "raw");
})
.then(function(data) {
// Encrypt signing key with user's public key
return encryptSigningKey(data, thisUser.publicKey);
})
.then(function(encryptedSigningKey) {
let encData = new Uint8Array(encryptedSigningKey);
var str = convertArrayBufferViewToString(encData);
resolve({
id: thisUser.id,
secretKey: secretKeyStr,
encryptedSigningKey: str
secretKey: secretKeyStr
});
});
});
@ -410,7 +405,7 @@ $(function() {
.then(function(data) {
secretKeys = data;
messageData = convertStringToArrayBufferView(message);
return signKey(messageData, signingKey);
return signKey(messageData, keys.signing.private);
})
.then(function(data) {
signature = data;
@ -445,7 +440,7 @@ $(function() {
let message = data.message;
let messageData = convertStringToArrayBufferView(message);
let username = data.username;
let senderId = data.id
let senderId = data.id;
let vector = data.vector;
let vectorData = convertStringToArrayBufferView(vector);
let secretKeys = data.secretKeys;
@ -458,9 +453,10 @@ $(function() {
let signature = data.signature;
let signatureData = convertStringToArrayBufferView(signature);
let secretKeyArrayBuffer = convertStringToArrayBufferView(mySecretKey.secretKey);
let signingKeyArrayBuffer = convertStringToArrayBufferView(mySecretKey.encryptedSigningKey);
decryptSecretKey(secretKeyArrayBuffer, keys.private)
let sender = _.findWhere(users, {id: senderId});
decryptSecretKey(secretKeyArrayBuffer, keys.primary.private)
.then(function(data) {
return importSecretKey(new Uint8Array(data), "raw");
})
@ -470,15 +466,8 @@ $(function() {
})
.then(function(data) {
decryptedMessageData = data;
decryptedMessage = convertArrayBufferViewToString(new Uint8Array(data))
return decryptSigningKey(signingKeyArrayBuffer, keys.private)
})
.then(function(data) {
return importSigningKey(new Uint8Array(data), "raw");
})
.then(function(data) {
let signingKey = data;
return verifyKey(signatureData, decryptedMessageData, signingKey);
decryptedMessage = convertArrayBufferViewToString(new Uint8Array(data));
return verifyKey(signatureData, decryptedMessageData, sender.signingPublicKey);
})
.then(function(bool) {
if (bool) {
@ -588,12 +577,13 @@ $(function() {
return str;
}
function createSigningKey() {
function createSigningKeys() {
return window.crypto.subtle.generateKey(
{
name: "HMAC",
name: "RSASSA-PKCS1-v1_5",
modulusLength: 2048, //can be 1024, 2048, or 4096
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
//length: 256, //optional, if you want your key length to differ from the hash function's block length
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["sign", "verify"] //can be any combination of "sign" and "verify"
@ -747,15 +737,13 @@ $(function() {
function importSigningKey(jwkData) {
return window.crypto.subtle.importKey(
"raw", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
//this is an example jwk key, other key types are Uint8Array objects
"jwk", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
jwkData,
{ //these are the algorithm options
name: "HMAC",
name: "RSASSA-PKCS1-v1_5",
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
//length: 256, //optional, if you want your key length to differ from the hash function's block length
},
true, //whether the key is extractable (i.e. can be used in exportKey)
false, //whether the key is extractable (i.e. can be used in exportKey)
["verify"] //"verify" for public key import, "sign" for private key imports
);
}
@ -764,25 +752,29 @@ $(function() {
// Will use my private key
return window.crypto.subtle.sign(
{
name: "HMAC",
hash: {name: "SHA-256"}
name: "RSASSA-PKCS1-v1_5",
modulusLength: 2048, //can be 1024, 2048, or 4096
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
},
keyToSignWith, //from generateKey or importKey above
data //ArrayBuffer of data you want to sign
);
);
}
function verifyKey(signature, data, keyToVerifyWith) {
// Will verify with sender's public key
return window.crypto.subtle.verify(
{
name: "HMAC",
hash: {name: "SHA-256"}
name: "RSASSA-PKCS1-v1_5",
modulusLength: 2048, //can be 1024, 2048, or 4096
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
},
keyToVerifyWith, //from generateKey or importKey above
signature, //ArrayBuffer of the signature
data //ArrayBuffer of the data
);
);
}
});