mirror of
https://github.com/zadam/trilium.git
synced 2025-01-31 03:19:11 +08:00
password now encrypts random "data key" which is then used for encryption of the actual data
This commit is contained in:
parent
fdc668e28b
commit
1d395badfa
8 changed files with 107 additions and 48 deletions
24
setup.py
24
setup.py
|
@ -10,6 +10,9 @@ from builtins import input
|
||||||
import src.config_provider
|
import src.config_provider
|
||||||
import src.sql
|
import src.sql
|
||||||
import src.my_scrypt
|
import src.my_scrypt
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
from Crypto.Util import Counter
|
||||||
|
import hashlib
|
||||||
|
|
||||||
config = src.config_provider.getConfig()
|
config = src.config_provider.getConfig()
|
||||||
src.sql.connect(config['Document']['documentPath'])
|
src.sql.connect(config['Document']['documentPath'])
|
||||||
|
@ -29,14 +32,25 @@ password2 = getpass.getpass(prompt='Repeat the same password: ')
|
||||||
|
|
||||||
if password1 == password2:
|
if password1 == password2:
|
||||||
# urandom is secure enough, see https://docs.python.org/2/library/os.html
|
# urandom is secure enough, see https://docs.python.org/2/library/os.html
|
||||||
src.sql.setOption('flask_secret_key', base64.b64encode(os.urandom(24)))
|
src.sql.setOption('flask_secret_key', base64.b64encode(os.urandom(32)))
|
||||||
src.sql.setOption('verification_salt', base64.b64encode(os.urandom(24)))
|
src.sql.setOption('password_verification_salt', base64.b64encode(os.urandom(32)))
|
||||||
src.sql.setOption('encryption_salt', base64.b64encode(os.urandom(24)))
|
src.sql.setOption('password_derived_key_salt', base64.b64encode(os.urandom(32)))
|
||||||
|
|
||||||
hash = src.my_scrypt.getVerificationHash(password1)
|
password_derived_key = src.my_scrypt.getPasswordDerivedKey(password1)
|
||||||
|
|
||||||
|
aes = AES.new(password_derived_key, AES.MODE_CTR, counter=Counter.new(128, initial_value=5))
|
||||||
|
|
||||||
|
data_key = os.urandom(32)
|
||||||
|
data_key_digest = hashlib.sha256(data_key).digest()[:4]
|
||||||
|
|
||||||
|
encrypted_data_key = aes.encrypt(data_key_digest + data_key)
|
||||||
|
|
||||||
|
src.sql.setOption('encrypted_data_key', base64.b64encode(encrypted_data_key))
|
||||||
|
|
||||||
|
verification_hash = src.my_scrypt.getVerificationHash(password1)
|
||||||
|
|
||||||
src.sql.setOption('username', username)
|
src.sql.setOption('username', username)
|
||||||
src.sql.setOption('password', binascii.hexlify(hash))
|
src.sql.setOption('password_verification_hash', base64.b64encode(verification_hash))
|
||||||
|
|
||||||
src.sql.commit()
|
src.sql.commit()
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import binascii
|
import binascii
|
||||||
|
import base64
|
||||||
from flask import Flask, request, send_from_directory
|
from flask import Flask, request, send_from_directory
|
||||||
from flask import render_template, redirect
|
from flask import render_template, redirect
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
|
@ -61,7 +62,7 @@ certPath = config['Network']['certPath']
|
||||||
certKeyPath = config['Network']['certKeyPath']
|
certKeyPath = config['Network']['certKeyPath']
|
||||||
|
|
||||||
def verify_password(guessed_password):
|
def verify_password(guessed_password):
|
||||||
hashed_password = binascii.unhexlify(getOption('password'))
|
hashed_password = base64.b64decode(getOption('password_verification_hash'))
|
||||||
|
|
||||||
guess_hashed = my_scrypt.getVerificationHash(guessed_password)
|
guess_hashed = my_scrypt.getVerificationHash(guessed_password)
|
||||||
|
|
||||||
|
|
|
@ -11,16 +11,16 @@ import src.my_scrypt
|
||||||
def change_password(current_password, new_password):
|
def change_password(current_password, new_password):
|
||||||
current_password_hash = binascii.hexlify(src.my_scrypt.getVerificationHash(current_password))
|
current_password_hash = binascii.hexlify(src.my_scrypt.getVerificationHash(current_password))
|
||||||
|
|
||||||
if current_password_hash != src.sql.getOption('password'):
|
if current_password_hash != src.sql.getOption('password_verification_hash'):
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': "Given current password doesn't match hash"
|
'message': "Given current password doesn't match hash"
|
||||||
}
|
}
|
||||||
|
|
||||||
current_password_encryption_key = src.my_scrypt.getEncryptionHash(current_password)
|
current_password_encryption_key = src.my_scrypt.getPasswordDerivedKey(current_password)
|
||||||
|
|
||||||
new_password_verification_key = binascii.hexlify(src.my_scrypt.getVerificationHash(new_password))
|
new_password_verification_key = binascii.hexlify(src.my_scrypt.getVerificationHash(new_password))
|
||||||
new_password_encryption_key = src.my_scrypt.getEncryptionHash(new_password)
|
new_password_encryption_key = src.my_scrypt.getPasswordDerivedKey(new_password)
|
||||||
|
|
||||||
encrypted_notes = src.sql.getResults("select note_id, note_title, note_text from notes where encryption = 1")
|
encrypted_notes = src.sql.getResults("select note_id, note_title, note_text from notes where encryption = 1")
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ def change_password(current_password, new_password):
|
||||||
src.sql.execute("update notes set note_title = ?, note_text = ? where note_id = ?",
|
src.sql.execute("update notes set note_title = ?, note_text = ? where note_id = ?",
|
||||||
[re_encrypted_title, re_encrypted_text, note['note_id']])
|
[re_encrypted_title, re_encrypted_text, note['note_id']])
|
||||||
|
|
||||||
src.sql.setOption('password', new_password_verification_key)
|
src.sql.setOption('password_verification_hash', new_password_verification_key)
|
||||||
src.sql.commit()
|
src.sql.commit()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -2,12 +2,12 @@ import scrypt # pip install scrypt
|
||||||
import sql
|
import sql
|
||||||
|
|
||||||
def getVerificationHash(password):
|
def getVerificationHash(password):
|
||||||
salt = sql.getOption('verification_salt')
|
salt = sql.getOption('password_verification_salt')
|
||||||
|
|
||||||
return getScryptHash(password, salt)
|
return getScryptHash(password, salt)
|
||||||
|
|
||||||
def getEncryptionHash(password):
|
def getPasswordDerivedKey(password):
|
||||||
salt = sql.getOption('encryption_salt')
|
salt = sql.getOption('password_derived_key_salt')
|
||||||
|
|
||||||
return getScryptHash(password, salt)
|
return getScryptHash(password, salt)
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ password_api = Blueprint('password_api', __name__)
|
||||||
def verifyPassword():
|
def verifyPassword():
|
||||||
req = request.get_json(force=True)
|
req = request.get_json(force=True)
|
||||||
|
|
||||||
hashedPassword = sql.getOption('password')
|
hashedPassword = sql.getOption('password_verification_hash')
|
||||||
hashedPasswordBytes = binascii.unhexlify(hashedPassword)
|
hashedPasswordBytes = binascii.unhexlify(hashedPassword)
|
||||||
hashedPasswordSha = hashlib.sha256(hashedPasswordBytes).hexdigest()
|
hashedPasswordSha = hashlib.sha256(hashedPasswordBytes).hexdigest()
|
||||||
|
|
||||||
|
|
|
@ -40,8 +40,9 @@ def getTree():
|
||||||
retObject = {}
|
retObject = {}
|
||||||
retObject['notes'] = rootNotes
|
retObject['notes'] = rootNotes
|
||||||
retObject['start_note_id'] = getSingleResult('select * from options where opt_name = "start_node"')['opt_value'];
|
retObject['start_note_id'] = getSingleResult('select * from options where opt_name = "start_node"')['opt_value'];
|
||||||
retObject['verification_salt'] = getOption('verification_salt')
|
retObject['password_verification_salt'] = getOption('password_verification_salt')
|
||||||
retObject['encryption_salt'] = getOption('encryption_salt')
|
retObject['password_derived_key_salt'] = getOption('password_derived_key_salt')
|
||||||
|
retObject['encrypted_data_key'] = getOption('encrypted_data_key')
|
||||||
retObject['encryption_session_timeout'] = getOption('encryption_session_timeout')
|
retObject['encryption_session_timeout'] = getOption('encryption_session_timeout')
|
||||||
|
|
||||||
return jsonify(retObject)
|
return jsonify(retObject)
|
|
@ -28,30 +28,17 @@ let globalEncryptionKey = null;
|
||||||
let globalLastEncryptionOperationDate = null;
|
let globalLastEncryptionOperationDate = null;
|
||||||
|
|
||||||
function deriveEncryptionKey(password) {
|
function deriveEncryptionKey(password) {
|
||||||
const verificationPromise = computeScrypt(password, globalVerificationSalt, (key, resolve, reject) => {
|
return computeScrypt(password, globalEncryptionSalt, (key, resolve, reject) => {
|
||||||
$.ajax({
|
const dataKeyAes = getDataKeyAes(key);
|
||||||
url: baseUrl + 'password/verify',
|
|
||||||
type: 'POST',
|
|
||||||
data: JSON.stringify({
|
|
||||||
password: sha256(key)
|
|
||||||
}),
|
|
||||||
contentType: "application/json",
|
|
||||||
success: function (result) {
|
|
||||||
if (result.valid) {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
alert("Wrong password");
|
|
||||||
|
|
||||||
reject();
|
const decryptedDataKey = decrypt(dataKeyAes, globalEncryptedDataKey);
|
||||||
}
|
|
||||||
}
|
if (decryptedDataKey === false) {
|
||||||
});
|
reject("Wrong password.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return decryptedDataKey;
|
||||||
});
|
});
|
||||||
|
|
||||||
const encryptionKeyPromise = computeScrypt(password, globalEncryptionSalt, (key, resolve, reject) => resolve(key));
|
|
||||||
|
|
||||||
return Promise.all([ verificationPromise, encryptionKeyPromise ]).then(results => results[1]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeScrypt(password, salt, callback) {
|
function computeScrypt(password, salt, callback) {
|
||||||
|
@ -110,7 +97,8 @@ $("#encryptionPasswordForm").submit(function() {
|
||||||
|
|
||||||
globalEncryptionCallback = null;
|
globalEncryptionCallback = null;
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.catch(reason => alert(reason));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
@ -141,12 +129,16 @@ function isEncryptionAvailable() {
|
||||||
return globalEncryptionKey !== null;
|
return globalEncryptionKey !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAes() {
|
function getDataAes() {
|
||||||
globalLastEncryptionOperationDate = new Date();
|
globalLastEncryptionOperationDate = new Date();
|
||||||
|
|
||||||
return new aesjs.ModeOfOperation.ctr(globalEncryptionKey, new aesjs.Counter(5));
|
return new aesjs.ModeOfOperation.ctr(globalEncryptionKey, new aesjs.Counter(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDataKeyAes(key) {
|
||||||
|
return new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5));
|
||||||
|
}
|
||||||
|
|
||||||
function encryptNoteIfNecessary(note) {
|
function encryptNoteIfNecessary(note) {
|
||||||
if (note.detail.encryption === 0) {
|
if (note.detail.encryption === 0) {
|
||||||
return note;
|
return note;
|
||||||
|
@ -157,21 +149,72 @@ function encryptNoteIfNecessary(note) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function encryptString(str) {
|
function encryptString(str) {
|
||||||
const aes = getAes();
|
return encrypt(getDataAes(), str);
|
||||||
const bytes = aesjs.utils.utf8.toBytes(str);
|
}
|
||||||
|
|
||||||
const encryptedBytes = aes.encrypt(bytes);
|
function encrypt(aes, str) {
|
||||||
|
const payload = aesjs.utils.utf8.toBytes(str);
|
||||||
|
const digest = sha256Array(payload).slice(0, 4);
|
||||||
|
|
||||||
|
const digestWithPayload = concat(digest, payload);
|
||||||
|
|
||||||
|
const encryptedBytes = aes.encrypt(digestWithPayload);
|
||||||
|
|
||||||
return uint8ToBase64(encryptedBytes);
|
return uint8ToBase64(encryptedBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
function decryptString(encryptedBase64) {
|
function decryptString(encryptedBase64) {
|
||||||
const aes = getAes();
|
const decryptedBytes = decrypt(getDataAes(), encryptedBase64);
|
||||||
|
|
||||||
|
return aesjs.utils.utf8.fromBytes(decryptedBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function decrypt(aes, encryptedBase64) {
|
||||||
const encryptedBytes = base64ToUint8Array(encryptedBase64);
|
const encryptedBytes = base64ToUint8Array(encryptedBase64);
|
||||||
|
|
||||||
const decryptedBytes = aes.decrypt(encryptedBytes);
|
const decryptedBytes = aes.decrypt(encryptedBytes);
|
||||||
|
|
||||||
return aesjs.utils.utf8.fromBytes(decryptedBytes);
|
const digest = decryptedBytes.slice(0, 4);
|
||||||
|
const payload = decryptedBytes.slice(4);
|
||||||
|
|
||||||
|
const hashArray = sha256Array(payload);
|
||||||
|
|
||||||
|
const computedDigest = hashArray.slice(0, 4);
|
||||||
|
|
||||||
|
if (!arraysIdentical(digest, computedDigest)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
function concat(a, b) {
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
for (let key in a) {
|
||||||
|
result.push(a[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let key in b) {
|
||||||
|
result.push(b[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sha256Array(content) {
|
||||||
|
const hash = sha256.create();
|
||||||
|
hash.update(content);
|
||||||
|
return hash.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
function arraysIdentical(a, b) {
|
||||||
|
let i = a.length;
|
||||||
|
if (i !== b.length) return false;
|
||||||
|
while (i--) {
|
||||||
|
if (a[i] !== b[i]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function encryptNote(note) {
|
function encryptNote(note) {
|
||||||
|
|
|
@ -83,17 +83,17 @@ function setExpandedToServer(note_id, is_expanded) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let globalVerificationSalt;
|
|
||||||
let globalEncryptionSalt;
|
let globalEncryptionSalt;
|
||||||
let globalEncryptionSessionTimeout;
|
let globalEncryptionSessionTimeout;
|
||||||
|
let globalEncryptedDataKey;
|
||||||
|
|
||||||
$(function(){
|
$(function(){
|
||||||
$.get(baseUrl + 'tree').then(resp => {
|
$.get(baseUrl + 'tree').then(resp => {
|
||||||
const notes = resp.notes;
|
const notes = resp.notes;
|
||||||
let startNoteId = resp.start_note_id;
|
let startNoteId = resp.start_note_id;
|
||||||
globalVerificationSalt = resp.verification_salt;
|
globalEncryptionSalt = resp.password_derived_key_salt;
|
||||||
globalEncryptionSalt = resp.encryption_salt;
|
|
||||||
globalEncryptionSessionTimeout = resp.encryption_session_timeout;
|
globalEncryptionSessionTimeout = resp.encryption_session_timeout;
|
||||||
|
globalEncryptedDataKey = resp.encrypted_data_key;
|
||||||
|
|
||||||
if (document.location.hash) {
|
if (document.location.hash) {
|
||||||
startNoteId = document.location.hash.substr(1); // strip initial #
|
startNoteId = document.location.hash.substr(1); // strip initial #
|
||||||
|
|
Loading…
Reference in a new issue