mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 21:11:30 +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
					
				
							
								
								
									
										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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 azivner
						azivner