Last active
July 25, 2022 05:06
-
-
Save mathieuancelin/fed3244b93fb9bbc115c09cb47a5af13 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
"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('[email protected]', 'password', 'Mathieu Mathieu'); | |
server.debug(); | |
const client = new Client(server); | |
client.login('[email protected]', 'password'); | |
client.createMessage('Message 1'); | |
client.createMessage('Message 2'); | |
client.createMessage('Message 3'); | |
server.debug(); | |
client.close(); | |
const client2 = new Client(server); | |
client2.login('[email protected]', 'password'); | |
client2.displayMessages(); | |
client2.close(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment