password now encrypts random "data key" which is then used for encryption of the actual data

This commit is contained in:
azivner 2017-09-16 23:21:46 -04:00
parent fdc668e28b
commit 1d395badfa
8 changed files with 107 additions and 48 deletions

View file

@ -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()

View file

@ -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)

View file

@ -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 {

View file

@ -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)

View file

@ -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()

View file

@ -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)

View file

@ -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) {

View file

@ -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 #