Skip to content

Instantly share code, notes, and snippets.

@mathieuancelin
Last active July 25, 2022 05:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mathieuancelin/fed3244b93fb9bbc115c09cb47a5af13 to your computer and use it in GitHub Desktop.
Save mathieuancelin/fed3244b93fb9bbc115c09cb47a5af13 to your computer and use it in GitHub Desktop.
/*
"dependencies": {
"bcrypt": "3.0.3",
"node-jsencrypt": "1.0.0"
}
*/
const bcrypt = require('bcrypt');
const JSEncrypt = require('node-jsencrypt');
const _crypto = require('crypto');
const aes = {
encrypt: (text, masterkey) => {
const iv = _crypto.randomBytes(16);
const salt = _crypto.randomBytes(64);
const key = _crypto.pbkdf2Sync(masterkey, salt, 2145, 32, 'sha512');
const cipher = _crypto.createCipheriv('aes-256-gcm', key, iv);
const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
const tag = cipher.getAuthTag();
return Buffer.concat([salt, iv, tag, encrypted]).toString('base64');
},
decrypt: (encdata, masterkey) => {
const bData = Buffer.from(encdata, 'base64');
const salt = bData.slice(0, 64);
const iv = bData.slice(64, 80);
const tag = bData.slice(80, 96);
const text = bData.slice(96);
const key = _crypto.pbkdf2Sync(masterkey, salt , 2145, 32, 'sha512');
const decipher = _crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);
const decrypted = decipher.update(text, 'binary', 'utf8') + decipher.final('utf8');
return decrypted;
}
};
class Server {
constructor() {
this.users = {};
this.messages = {};
}
createUser(email, password, name) {
const hash = bcrypt.hashSync(password, 10);
this.users[email] = {
email,
name,
password: hash
};
}
login(email, password) {
const user = this.users[email];
if (user) {
if (bcrypt.compareSync(password, user.password)) {
return user;
} else {
return null;
}
} else {
return null;
}
}
storeKey(email, salt, publicKey, privateKey) {
const user = this.users[email];
user.salt = salt;
user.publicKey = publicKey;
user.privateKey = privateKey;
this.users[email] = user;
}
createMessage(email, content) {
const messages = this.messages[email] || [];
messages.push(content);
this.messages[email] = messages;
}
loadMessages(email) {
const messages = this.messages[email] || [];
return messages;
}
debug() {
console.log('========== debug ==========');
console.log('users', JSON.stringify(this.users, null, 2));
console.log('messages', JSON.stringify(this.messages, null, 2));
console.log('===========================');
}
}
class Client {
constructor(server) {
this.server = server;
this.crypt = new JSEncrypt({ default_key_size: 2056 })
}
generateSalt() {
return bcrypt.genSaltSync(10);
}
encryptPrivateKey(privateKey, salt, password) {
const hash = bcrypt.hashSync(password, salt);
return aes.encrypt(privateKey, hash);
}
decryptPrivateKey(encodedPrivateKey, salt, password) {
const hash = bcrypt.hashSync(password, salt);
return aes.decrypt(encodedPrivateKey, hash);
}
loadMessage() {
this.messages = this.server.loadMessages(this.email);
}
login(email, password) {
const res = this.server.login(email, password);
if (res) {
this.email = email;
this.password = password;
this.name = res.name
if (!res.privateKey && !res.publicKey) {
console.log('Generating keys ...');
this.privateKey = this.crypt.getPrivateKey();
this.publicKey = this.crypt.getPublicKey();
this.salt = this.generateSalt();
console.log('Sending keys to server');
this.server.storeKey(
this.email,
aes.encrypt(this.salt, this.password),
this.publicKey,
this.encryptPrivateKey(this.privateKey, this.salt, this.password)
);
console.log('Logged in as ' + this.email);
this.loadMessage();
} else {
this.salt = aes.decrypt(res.salt, this.password);
this.privateKey = this.decryptPrivateKey(res.privateKey, this.salt, this.password);
this.publicKey = res.publicKey;
console.log('Logged in as ' + this.email);
this.loadMessage();
}
} else {
console.log('bad login :(');
process.exit(1);
}
}
encryptMessage(content, pubKey) {
const messageKey = Date.now() + ''; // TODO: true random stuff;
const encryptedContent = aes.encrypt(content, messageKey);
this.crypt.setKey(pubKey || this.publicKey); // if not, encrypt for myself
const encryptedKey = this.crypt.encrypt(messageKey);
return {
key: encryptedKey,
content: encryptedContent
};
}
decryptMessage(message) {
this.crypt.setKey(this.privateKey);
const key = this.crypt.decrypt(message.key);
const content = aes.decrypt(message.content, key);
return content;
}
createMessage(content) {
const encryptedMessage = this.encryptMessage(content);
this.server.createMessage(this.email, encryptedMessage);
}
displayMessages() {
const messages = this.messages || [];
const decryptedMessages = messages.map(message => {
return this.decryptMessage(message);
});
console.log(JSON.stringify(decryptedMessages, null, 2));
}
close() {
console.log('closing client\n\n')
}
}
const server = new Server();
server.createUser('mathieu@foo.bar', 'password', 'Mathieu Mathieu');
server.debug();
const client = new Client(server);
client.login('mathieu@foo.bar', 'password');
client.createMessage('Message 1');
client.createMessage('Message 2');
client.createMessage('Message 3');
server.debug();
client.close();
const client2 = new Client(server);
client2.login('mathieu@foo.bar', 'password');
client2.displayMessages();
client2.close();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment