Je kunt niet meer dan 25 onderwerpen selecteren Onderwerpen moeten beginnen met een letter of nummer, kunnen streepjes bevatten ('-') en kunnen maximaal 35 tekens lang zijn.

139 regels
5.1 KiB

from base64 import b64decode, b64encode
from binascii import hexlify, unhexlify
from copy import deepcopy
import hashlib
import hmac
import json
import sys
from Crypto.Cipher import AES
from Crypto.Random import random
BITS_PER_HEX_DIGIT = 4
PASS_KEY_LEN = 96
AES_KEY_LEN = 256
AES_BLK_SIZE = 16
AES_STR_KEY_LEN = AES_KEY_LEN // BITS_PER_HEX_DIGIT
AES_IV_LEN = 128
AES_STR_IV_LEN = AES_IV_LEN // BITS_PER_HEX_DIGIT
class EncryptionHelper:
def pure_generate_password_and_key(self, password, pw_salt, pw_cost):
output = hashlib.pbkdf2_hmac(
'sha512', password.encode(), pw_salt.encode(), pw_cost,
dklen=PASS_KEY_LEN)
output = hexlify(output).decode()
output_length = len(output)
split_length = output_length // 3
pw = output[0 : split_length]
mk = output[split_length : split_length * 2]
ak = output[split_length * 2 : split_length * 3]
return dict(pw=pw, mk=mk, ak=ak)
def encrypt_dirty_items(self, dirty_items, keys):
return [self.pure_encrypt_item(item, keys) for item in dirty_items]
def decrypt_response_items(self, response_items, keys):
return [self.pure_decrypt_item(item, keys) for item in response_items]
def pure_encrypt_item(self, item, keys):
uuid = item['uuid']
content = json.dumps(item['content'])
# all this is to follow the Standard Notes spec
item_key = hex(random.getrandbits(AES_KEY_LEN * 2))
# remove '0x', pad with 0's, then split in half
item_key = item_key[2:].rjust(AES_STR_KEY_LEN * 2, '0')
item_ek = item_key[:AES_STR_KEY_LEN]
item_ak = item_key[AES_STR_KEY_LEN:]
enc_item = deepcopy(item)
enc_item['content'] = self.pure_encrypt_string_002(
content, item_ek, item_ak, uuid)
enc_item['enc_item_key'] = self.pure_encrypt_string_002(
item_key, keys['mk'], keys['ak'], uuid)
return enc_item
def pure_decrypt_item(self, item, keys):
if item['deleted']:
return item
uuid = item['uuid']
content = item['content']
enc_item_key = item['enc_item_key']
if content[:3] == '001':
print('Old encryption protocol detected. This version is not '
'supported by standardnotes-fs. Please resync all of '
'your notes by following the instructions here:\n'
'https://standardnotes.org/help/resync')
sys.exit(1)
elif content[:3] == '002':
item_key = self.pure_decrypt_string_002(
enc_item_key, keys['mk'], keys['ak'], uuid)
item_key_length = len(item_key)
item_ek = item_key[:item_key_length//2]
item_ak = item_key[item_key_length//2:]
dec_content = self.pure_decrypt_string_002(
content, item_ek, item_ak, uuid)
else:
print('Invalid protocol version. This could indicate tampering or '
'that something is wrong with the server. Exiting.')
sys.exit(1)
dec_item = deepcopy(item)
dec_item['content'] = json.loads(dec_content)
return dec_item
def pure_encrypt_string_002(self, string_to_encrypt, encryption_key,
auth_key, uuid):
IV = hex(random.getrandbits(AES_IV_LEN))
IV = IV[2:].rjust(AES_STR_IV_LEN, '0') # remove '0x', pad with 0's
cipher = AES.new(unhexlify(encryption_key), AES.MODE_CBC, unhexlify(IV))
pt = string_to_encrypt.encode()
pad = AES_BLK_SIZE - len(pt) % AES_BLK_SIZE
padded_pt = pt + pad * bytes([pad])
ciphertext = b64encode(cipher.encrypt(padded_pt)).decode()
string_to_auth = ':'.join(['002', uuid, IV, ciphertext])
auth_hash = hmac.new(
unhexlify(auth_key), string_to_auth.encode(), 'sha256').digest()
auth_hash = hexlify(auth_hash).decode()
result = ':'.join(['002', auth_hash, uuid, IV, ciphertext])
return result
def pure_decrypt_string_002(self, string_to_decrypt, encryption_key,
auth_key, uuid):
components = string_to_decrypt.split(':')
version, auth_hash, local_uuid, IV, ciphertext = components
if local_uuid != uuid:
print('UUID does not match. This could indicate tampering or '
'that something is wrong with the server. Exiting.')
sys.exit(1)
string_to_auth = ':'.join([version, uuid, IV, ciphertext])
local_auth_hash = hmac.new(
unhexlify(auth_key), string_to_auth.encode(), 'sha256').digest()
local_auth_hash = hexlify(local_auth_hash).decode()
if local_auth_hash != auth_hash:
print('Auth hash does not match. This could indicate tampering or '
'that something is wrong with the server. Exiting.')
sys.exit(1)
cipher = AES.new(unhexlify(encryption_key), AES.MODE_CBC, unhexlify(IV))
result = cipher.decrypt(b64decode(ciphertext))
result = result[:-result[-1]] # remove PKCS#7 padding
result = result.decode()
return result