mirror of
https://github.com/darkwire/darkwire.io.git
synced 2025-07-18 10:49:02 +00:00
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:
parent
55feed3257
commit
323c7a903d
22
readme.md
22
readme.md
@ -18,25 +18,23 @@ Build source
|
|||||||
|
|
||||||
### How it works
|
### 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):
|
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).
|
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 a public/private key pair. She is sent Bob's public key and she sends Bob her public key.
|
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, three things are created: a session key (AES-CBC), a signing key (HMAC SHA-256) and an initialization vector (used in the encryption process).
|
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 the signing key.
|
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 and signing key are encrypted with each recipient's public key (in this case only Alice, but in a group chat multiple).
|
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, encrypted session key and encrypted signing key are sent to all recipients (in this case just Alice) as a package.
|
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 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.
|
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)
|
### [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).
|
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.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
### Sockets & Server
|
### Sockets & Server
|
||||||
|
|
||||||
|
@ -90,22 +90,31 @@ $(function() {
|
|||||||
$inputMessage.focus();
|
$inputMessage.focus();
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
createPrimaryKeys()
|
createPrimaryKeys(),
|
||||||
|
createSigningKeys()
|
||||||
])
|
])
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
keys = {
|
keys = {
|
||||||
public: data[0].publicKey,
|
primary: {
|
||||||
private: data[0].privateKey
|
public: data[0].publicKey,
|
||||||
|
private: data[0].privateKey
|
||||||
|
},
|
||||||
|
signing: {
|
||||||
|
public: data[1].publicKey,
|
||||||
|
private: data[1].privateKey
|
||||||
|
}
|
||||||
};
|
};
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
exportKey(data[0].publicKey, "spki")
|
exportKey(data[0].publicKey, "spki"),
|
||||||
|
exportKey(data[1].publicKey, "jwk")
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
.then(function(exportedKeys) {
|
.then(function(exportedKeys) {
|
||||||
// Tell the server your username and send public keys
|
// Tell the server your username and send public keys
|
||||||
socket.emit('add user', {
|
socket.emit('add user', {
|
||||||
username: username,
|
username: username,
|
||||||
publicKey: exportedKeys[0]
|
primaryPublicKey: exportedKeys[0],
|
||||||
|
signingPublicKey: exportedKeys[1],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -299,13 +308,15 @@ $(function() {
|
|||||||
let promise = new Promise(function(resolve, reject) {
|
let promise = new Promise(function(resolve, reject) {
|
||||||
let currentUser = user;
|
let currentUser = user;
|
||||||
Promise.all([
|
Promise.all([
|
||||||
importPrimaryKey(currentUser.publicKey, "spki")
|
importPrimaryKey(currentUser.primaryPublicKey, "spki"),
|
||||||
|
importSigningKey(currentUser.signingPublicKey)
|
||||||
])
|
])
|
||||||
.then(function(keys) {
|
.then(function(keys) {
|
||||||
users.push({
|
users.push({
|
||||||
id: currentUser.id,
|
id: currentUser.id,
|
||||||
username: currentUser.username,
|
username: currentUser.username,
|
||||||
publicKey: keys[0]
|
primaryPublicKey: keys[0],
|
||||||
|
signingPublicKey: keys[1]
|
||||||
});
|
});
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
@ -356,17 +367,12 @@ $(function() {
|
|||||||
let secretKeys;
|
let secretKeys;
|
||||||
let messageData;
|
let messageData;
|
||||||
let signature;
|
let signature;
|
||||||
let signingKey;
|
|
||||||
let encryptedMessageData;
|
let encryptedMessageData;
|
||||||
|
|
||||||
// Generate new secret key and vector for each message
|
// Generate new secret key and vector for each message
|
||||||
createSecretKey()
|
createSecretKey()
|
||||||
.then(function(key) {
|
.then(function(key) {
|
||||||
secretKey = key;
|
secretKey = key;
|
||||||
return createSigningKey();
|
|
||||||
})
|
|
||||||
.then(function(key) {
|
|
||||||
signingKey = key;
|
|
||||||
// Generate secretKey and encrypt with each user's public key
|
// Generate secretKey and encrypt with each user's public key
|
||||||
let promises = [];
|
let promises = [];
|
||||||
_.each(users, function(user) {
|
_.each(users, function(user) {
|
||||||
@ -380,25 +386,14 @@ $(function() {
|
|||||||
// Export secret key
|
// Export secret key
|
||||||
exportKey(secretKey, "raw")
|
exportKey(secretKey, "raw")
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
return encryptSecretKey(data, thisUser.publicKey);
|
return encryptSecretKey(data, thisUser.primaryPublicKey);
|
||||||
})
|
})
|
||||||
.then(function(encryptedSecretKey) {
|
.then(function(encryptedSecretKey) {
|
||||||
let encData = new Uint8Array(encryptedSecretKey);
|
let encData = new Uint8Array(encryptedSecretKey);
|
||||||
secretKeyStr = convertArrayBufferViewToString(encData);
|
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({
|
resolve({
|
||||||
id: thisUser.id,
|
id: thisUser.id,
|
||||||
secretKey: secretKeyStr,
|
secretKey: secretKeyStr
|
||||||
encryptedSigningKey: str
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -410,7 +405,7 @@ $(function() {
|
|||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
secretKeys = data;
|
secretKeys = data;
|
||||||
messageData = convertStringToArrayBufferView(message);
|
messageData = convertStringToArrayBufferView(message);
|
||||||
return signKey(messageData, signingKey);
|
return signKey(messageData, keys.signing.private);
|
||||||
})
|
})
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
signature = data;
|
signature = data;
|
||||||
@ -445,7 +440,7 @@ $(function() {
|
|||||||
let message = data.message;
|
let message = data.message;
|
||||||
let messageData = convertStringToArrayBufferView(message);
|
let messageData = convertStringToArrayBufferView(message);
|
||||||
let username = data.username;
|
let username = data.username;
|
||||||
let senderId = data.id
|
let senderId = data.id;
|
||||||
let vector = data.vector;
|
let vector = data.vector;
|
||||||
let vectorData = convertStringToArrayBufferView(vector);
|
let vectorData = convertStringToArrayBufferView(vector);
|
||||||
let secretKeys = data.secretKeys;
|
let secretKeys = data.secretKeys;
|
||||||
@ -458,9 +453,10 @@ $(function() {
|
|||||||
let signature = data.signature;
|
let signature = data.signature;
|
||||||
let signatureData = convertStringToArrayBufferView(signature);
|
let signatureData = convertStringToArrayBufferView(signature);
|
||||||
let secretKeyArrayBuffer = convertStringToArrayBufferView(mySecretKey.secretKey);
|
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) {
|
.then(function(data) {
|
||||||
return importSecretKey(new Uint8Array(data), "raw");
|
return importSecretKey(new Uint8Array(data), "raw");
|
||||||
})
|
})
|
||||||
@ -470,15 +466,8 @@ $(function() {
|
|||||||
})
|
})
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
decryptedMessageData = data;
|
decryptedMessageData = data;
|
||||||
decryptedMessage = convertArrayBufferViewToString(new Uint8Array(data))
|
decryptedMessage = convertArrayBufferViewToString(new Uint8Array(data));
|
||||||
return decryptSigningKey(signingKeyArrayBuffer, keys.private)
|
return verifyKey(signatureData, decryptedMessageData, sender.signingPublicKey);
|
||||||
})
|
|
||||||
.then(function(data) {
|
|
||||||
return importSigningKey(new Uint8Array(data), "raw");
|
|
||||||
})
|
|
||||||
.then(function(data) {
|
|
||||||
let signingKey = data;
|
|
||||||
return verifyKey(signatureData, decryptedMessageData, signingKey);
|
|
||||||
})
|
})
|
||||||
.then(function(bool) {
|
.then(function(bool) {
|
||||||
if (bool) {
|
if (bool) {
|
||||||
@ -588,12 +577,13 @@ $(function() {
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSigningKey() {
|
function createSigningKeys() {
|
||||||
return window.crypto.subtle.generateKey(
|
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"
|
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)
|
true, //whether the key is extractable (i.e. can be used in exportKey)
|
||||||
["sign", "verify"] //can be any combination of "sign" and "verify"
|
["sign", "verify"] //can be any combination of "sign" and "verify"
|
||||||
@ -747,15 +737,13 @@ $(function() {
|
|||||||
|
|
||||||
function importSigningKey(jwkData) {
|
function importSigningKey(jwkData) {
|
||||||
return window.crypto.subtle.importKey(
|
return window.crypto.subtle.importKey(
|
||||||
"raw", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
|
"jwk", //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
|
|
||||||
jwkData,
|
jwkData,
|
||||||
{ //these are the algorithm options
|
{ //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"
|
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
|
["verify"] //"verify" for public key import, "sign" for private key imports
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -764,25 +752,29 @@ $(function() {
|
|||||||
// Will use my private key
|
// Will use my private key
|
||||||
return window.crypto.subtle.sign(
|
return window.crypto.subtle.sign(
|
||||||
{
|
{
|
||||||
name: "HMAC",
|
name: "RSASSA-PKCS1-v1_5",
|
||||||
hash: {name: "SHA-256"}
|
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
|
keyToSignWith, //from generateKey or importKey above
|
||||||
data //ArrayBuffer of data you want to sign
|
data //ArrayBuffer of data you want to sign
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function verifyKey(signature, data, keyToVerifyWith) {
|
function verifyKey(signature, data, keyToVerifyWith) {
|
||||||
// Will verify with sender's public key
|
// Will verify with sender's public key
|
||||||
return window.crypto.subtle.verify(
|
return window.crypto.subtle.verify(
|
||||||
{
|
{
|
||||||
name: "HMAC",
|
name: "RSASSA-PKCS1-v1_5",
|
||||||
hash: {name: "SHA-256"}
|
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
|
keyToVerifyWith, //from generateKey or importKey above
|
||||||
signature, //ArrayBuffer of the signature
|
signature, //ArrayBuffer of the signature
|
||||||
data //ArrayBuffer of the data
|
data //ArrayBuffer of the data
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user