Repository
https://github.com/kingswisdom/SteemMessenger
Merged request: https://github.com/kingswisdom/SteemMessenger/pull/18
About the project
Steem Messenger aims to cover a need in our ecosysteem, the need for a chat on Steem. We aim to do it securely. We want to build the ultimate chat application for the Steem blockchain.
Designed for privacy, with the clever use of the Private Memo Key to authenticate yourself into the Messenger's network, you'll soon be able to safely engage with everybody on the Steem social network without needing anything more than your recipient username.
We use the Steem blockchain as a Decentralized Public Key Infrastructure (DPKI), meaning the Steem blockchain is our contact book, with usernames and they public keys associated to them. We also add a local encrypted wallet (another missing component of steem) to store user keys, like the memo private key.
New Features
Scrypt Benchmark and Parameters choice
Scrypt is a password-based key derivation function that help increase the security of the usage of users password. It hardens the job of an attacker trying to crack your password, specially against GPUs. GPUs are well suited for cracking password because they have more computing units to run the same function and they are easier to scale than CPUs. Using Scrypt greatly decreases the advantage of using GPUs for attackers. This is the reason why it was interesting for mining, although cryptocurrencies like Litecoin used very weak parameters.
So we evaluate the default parameters and benchmark our library to see how much we could increase the default parameters while staying well under the 10s for the first login.
We use https://github.com/bestiejs/benchmark.js/
First download benchmark.js, platform.js and lodash.js.
Second bundle the benchmmark js for the browser:
browserify tests.js -o bundle.js
Now fire index.html
in your browser and look in the console for the result.
Some results:
Settings: VM with 2 cpus, 3GB of RAM running Ubuntu
Using directly node tests.js
(outside of the browser)
========================
starting Benchmark
========================
default x 0.38 ops/sec ±14.89% (6 runs sampled) => average time 2.651413617 seconds
power15 x 0.24 ops/sec ±1.35% (5 runs sampled) => average time 4.1795559346 seconds
power16 x 0.12 ops/sec ±1.62% (5 runs sampled) => average time 8.406441287000002 seconds
power17 x 0.06 ops/sec ±5.65% (5 runs sampled) => average time 17.863167007 seconds
power18 x 0.03 ops/sec ±2.32% (5 runs sampled) => average time 36.5808561954 seconds
Fastest is default
In Firefox I get:
========================
starting Benchmark
========================
default x 0.72 ops/sec ±7.56% (6 runs sampled) => average time 1.3905 seconds
power15 x 0.33 ops/sec ±18.72% (5 runs sampled) => average time 3.0620000000000003 seconds
power16 x 0.18 ops/sec ±7.46% (5 runs sampled) => average time 5.6842 seconds
power17 x 0.08 ops/sec ±14.08% (5 runs sampled) => average time 12.9344 seconds
power18 x 0.10 ops/sec ±2.06% (5 runs sampled) => average time 10.2472 seconds
Fastest is default
I recommended N=2^16, r=8, p=1
. I would like to increase in the future N
to 2^18
(remember the default is 2^14
). An attacker will certainly not use Javascript code to attack so we are only making her job easier if we can not wait a bit longer to use our keys.
I will write a complete article on password state of the art and wallet encryption in a future post/ on the project wiki. Some sources:
- scrypt wikipedia
- ethereum wallet (2^18): https://ethereum.stackexchange.com/questions/37150/ethereum-wallet-v3-format
- filippo explanation: https://blog.filippo.io/the-scrypt-parameters/
- ethereum wallet cracker : https://stealthsploit.com/2018/01/04/ethereum-wallet-cracking-pt-2-gpu-vs-cpu/
- https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2015/march/enough-with-the-salts-updates-on-secure-password-schemes/
Choose SJCL library
It is hard to do proper cryptography in Javascript. With NodeJS, you get some functions but they usually come from different libraries and it is a better practice to rely on one cryptographic library. We also have the issue that the code needs to work on the client side and not all libraries/function work with browserify
. We choose the sjcl library because it has no dependecies, was written by cryptographers, is also used by steem js libcrypto, support various enconding(base64url) and cryptographic functions (AES GCM).
Here is what the decryption function looks like now:
/**
* Decryption of AES-GCM.
* @param {string} decryptionKey A base64url encoded symmetric key. Error if it is not the right key
* @param {string} data string. Make sure that data is a printable (base64url if possible) string.
* @return {string} The flat json ciphertext.
*/
Crypto.decrypt = function(decryptionKey,data){
try{
var rawDecryptionKey = sjcl.codec.base64url.toBits(decryptionKey);
//convert base64url to base64
data = data.replace(/-/g,'+').replace(/_/g,'/')
var plaintext = sjcl.decrypt(rawDecryptionKey, data);
return plaintext;
}catch(err) {
//either the raw dercyption key is not base64url
//either the decryption fail: which is because
//the decryption key is not the good one
//or/and the authentication tag is wrong
return err;
}
}
In the next version, we will try to upgrade the remaining security functions, like scrypt or random number generation, that do not rely yet on SJCL.
cryptography migration
The Steem.memo.encode/decode function does two things:
- (asymmetric) key exchange to generate a shared secret using the sender memo key and the recipient memo key. This is Diffie–Hellman key exchange.
- (symmetric) uses the shared secret with AES in mode CBC to encrypt/decrypt the message.
We ran run into two problems:
- asymmetric cryptography is significantly slower than the symmetric counterpart. So this will be the main bottleneck.
It is a bad idea to redo this operation every time we need to send a message. In Steem-Messenger, we encrypt messages between sender and recipient, then this message is encrypted by the sender for the server, then decrypted by the server, and again encrypted by the server for the recipient. This means, if we use the steem.memo.encode/decode
function, that we will do two asymetric operations per messages because of the encryption and super-encryption. This was really slow in the previous version.
We now store as much as possible the shared secret. Note that this is mainly a client side problem.
- Since we are off the chain, we do not have any signature on the message. This means that recipient cannot verify authenticity and integrity of the messages.
On Steem, you usually sign the operation, whether you write a comment with your posting key or send a transfer with your active key. Steem.memo.encode
does not cover this need because it uses AES in the CBC mode. We decide to switch to the mode GCM which provide an authentication tag with the ciphertext.
/**
* Encryption using AES-GCM. The choices were CCM, GCM, and OCB2. I need to check the other 2.
* @param {string} encryptionKey A base64url encoded symmetric key
* @param {string} data string. Make sure that data is a printable (base64url if possible) string.
* @return {string} The flat json ciphertext.
*/
Crypto.encrypt = function (encryptionKey,data){
try{
var rawEncryptionKey = sjcl.codec.base64url.toBits(encryptionKey);
var ciphertext = sjcl.encrypt(rawEncryptionKey, data, {mode : "gcm"});
var rawct = sjcl.json.decode(ciphertext);
var rawOutput = {iv:sjcl.codec.base64url.fromBits(rawct.iv),
mode : "gcm", cipher :"aes",
ct:sjcl.codec.base64url.fromBits(rawct.ct)};
var output = sjcl.json.encode(rawOutput);
return output;
}catch(err) {//should happen only if the encryption key is not base64url
return err;
}
}
We then update our core functions to use the our new cryptographic choices:
- user to user message encryption. This greatly improves file encryption performance.
- user to server (and vice-versa) socket encryption.
- wallet storage
Various fixes
Previous version ran two round of Scrypt which was rather unnecessary. The salt was also fixed, which is defeating the purpose of using a salt.
Wallet update was re-using the same initializing vector This is bad for stream (mode) encryption because:
CT1 = M1 xor KEY
CT2 = M2 xor KEY
=> CT1 xor CT2 = M1 xor KEY xor M2 xor KEY = M1 xor M2
There are ways to get some of the contents of both messages form their xor. This was a small issue because the function was not used yet. The fix was simple, the update function just use the create function.
We also moved from base58 to base64url. I am not sure if it is necessary to use base64url instead of just base64 but for now I am sticking to it.
Next features
We are looking into the Signal protocol to securely add group chat and voice/video calls. We are also exploring how we can extend the scope of the wallet to store more keys and do in-chat token transfer.