Last active
November 1, 2021 15:58
-
-
Save tannercollin/d7474c6afba0dc2026dd996b9dedf197 to your computer and use it in GitHub Desktop.
Standard Notes protocol v004 reference decryption demo
This file contains hidden or 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
# Standard Notes protocol v004 reference decryption demo | |
# by Tanner Collin, MIT license | |
# | |
# Install dependencies in your Python 3 environment: | |
# pip install argon2-cffi requests pycryptodome | |
# | |
# do not use your real account | |
# use a fresh account so there's no 003 items | |
# or use this test account | |
email = '[email protected]' | |
password = 'testaccount' | |
import argon2 | |
import hashlib | |
import json | |
import requests | |
from binascii import hexlify, unhexlify | |
from base64 import b64decode | |
from Crypto.Cipher import ChaCha20_Poly1305 | |
p = dict(email=email, api='20200115') | |
r = requests.get('https://sync.standardnotes.org/auth/params', params=p) | |
auth_params = r.json() | |
print('Auth params:', auth_params) | |
print() | |
string_to_hash = email + ':' + auth_params['pw_nonce'] | |
print('Hashing:', string_to_hash) | |
salt = hashlib.sha256(string_to_hash.encode()).hexdigest()[:32] | |
print('Salt:', salt) | |
argon2_hash = argon2.low_level.hash_secret_raw( | |
secret=password.encode(), | |
salt=unhexlify(salt), | |
time_cost=5, | |
memory_cost=65536, | |
parallelism=1, | |
hash_len=64, | |
type=argon2.Type.ID, | |
) | |
derived_key = hexlify(argon2_hash).decode() | |
print('Derived key:', derived_key, 'Length:', len(derived_key)) | |
master_key = derived_key[:64] | |
server_password = derived_key[64:] | |
print() | |
print('Master key:', master_key) | |
print('Server password:', server_password) | |
d = dict( | |
email=email, | |
api='20200115', | |
ephemeral=False, | |
password=server_password, | |
) | |
r = requests.post('https://sync.standardnotes.org/auth/sign_in', data=d) | |
sign_in = r.json() | |
session = sign_in['session'] | |
key_params = sign_in['key_params'] | |
user = sign_in['user'] | |
print() | |
print('Session:', session) | |
print('Key params:', key_params) | |
print('User:', user) | |
h = {'Authorization': 'Bearer ' + session['access_token']} | |
d = dict(items=[], compute_integrity=True, limit=150, api='20200115') | |
r = requests.post('https://sync.standardnotes.org/items/sync', headers=h, data=d) | |
sync = r.json() | |
print() | |
print('retrieved_items:', sync['retrieved_items']) | |
print('sync_token', sync['sync_token']) | |
default_items_key = None | |
notes = {} | |
for item in sync['retrieved_items']: | |
uuid = item['uuid'] | |
content = item['content'] | |
content_type = item['content_type'] | |
enc_item_key = item['enc_item_key'] | |
print() | |
print('Processing item', uuid) | |
print(' content_type', content_type) | |
print(' Decrypting enc_item_key') | |
version, nonce, ciphertext, encoded_authenticated_data = enc_item_key.split(':') | |
authenticated_data = json.loads(b64decode(encoded_authenticated_data).decode()) | |
print(' version:', version) | |
print(' nonce:', nonce) | |
print(' ciphertext:', ciphertext) | |
print(' auth data:', authenticated_data) | |
if content_type == 'SN|ItemsKey': | |
key = master_key | |
else: | |
key = default_items_key | |
cipher = ChaCha20_Poly1305.new(key=unhexlify(key), nonce=unhexlify(nonce)) | |
item_key = cipher.decrypt(b64decode(ciphertext))[:-16].decode() | |
print(' item_key', item_key) | |
print(' Decrypting content') | |
version, nonce, ciphertext, encoded_authenticated_data = content.split(':') | |
authenticated_data = json.loads(b64decode(encoded_authenticated_data).decode()) | |
print(' version:', version) | |
print(' nonce:', nonce) | |
print(' ciphertext:', ciphertext) | |
print(' auth data:', authenticated_data) | |
cipher = ChaCha20_Poly1305.new(key=unhexlify(item_key), nonce=unhexlify(nonce)) | |
plaintext = cipher.decrypt(b64decode(ciphertext))[:-16].decode() | |
print(' plaintext', plaintext) | |
plainjson = json.loads(plaintext) | |
if plainjson.get('isDefault', False): | |
default_items_key = plainjson['itemsKey'] | |
if content_type == 'Note': | |
notes[plainjson['title']] = plainjson['text'] | |
print() | |
print() | |
print('Here are your notes:') | |
for title, text in notes.items(): | |
print() | |
print(title) | |
print(text) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment