commit 424812432ce13e4402864e0b72fc0a819dcc3493 Author: raphael Date: Fri Dec 10 17:46:53 2021 +0100 adds all initial communication functionality - adds rest server wraper - adds secret generation - adds secret visualization diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..beb9746 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/* +.bump* diff --git a/README.md b/README.md new file mode 100644 index 0000000..a2f8dfb --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +## Bump Python Client + +This Bump client enables easy access to the bump service through a python api. +Bump any other client anywhere through an easy to use API. + +## working functions + +- peek +- pop +- push +- list +- clear +- generate new cryptographicaly safe secret +- visualize with rudimentary qr code to share with other clients + +## missing + +- add an existing secret +- scan existing qr code +- sync 'read' to other clients +- alert function that shows any new notifications + diff --git a/main.py b/main.py new file mode 100644 index 0000000..b9fa735 --- /dev/null +++ b/main.py @@ -0,0 +1,144 @@ +import random +import qrcode +import requests +import base64 +import re + +SENDER_LENGTH = 4 +PASSWORD_LENGTH = 8 + +from random_word import RandomWords + +import secrets +from base64 import urlsafe_b64encode as b64e, urlsafe_b64decode as b64d + +from cryptography.fernet import Fernet +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + +r = RandomWords() + +backend = default_backend() +iterations = 100_000 + +def _derive_key(password: bytes, salt: bytes, iterations: int = iterations) -> bytes: + """Derive a secret key from a given password and salt""" + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), length=32, salt=salt, + iterations=iterations, backend=backend) + return b64e(kdf.derive(password)) + +def password_encrypt(message: bytes, password: str, iterations: int = iterations) -> bytes: + salt = secrets.token_bytes(16) + key = _derive_key(password.encode(), salt, iterations) + return b64e( + b'%b%b%b' % ( + salt, + iterations.to_bytes(4, 'big'), + b64d(Fernet(key).encrypt(message)), + ) + ) + +def password_decrypt(token: bytes, password: str) -> bytes: + decoded = b64d(token) + salt, iter, token = decoded[:16], decoded[16:20], b64e(decoded[20:]) + iterations = int.from_bytes(iter, 'big') + key = _derive_key(password.encode(), salt, iterations) + return Fernet(key).decrypt(token) + +class Bump: + def __init__(self, secret=None, secret_file='.bump_secrets'): + self.secrets = self.load_secrets(secret, secret_file) + if self.secrets == []: + self.generate_secret(secret_file) + self.show_secrets() + + self.URL = "http://0.0.0.0:4000/api/" + + + + def load_secrets(self, secret, secrets_file='.bump_secrets'): + try: + with open(secrets_file, 'r') as f: + secrets = f.read().splitlines() + except FileNotFoundError: + secrets = [] + if secret is not None and secret not in self.secrets: + secrets.append(secret) + + return secrets + + def generate_secret(self, secret_file): + WORDS = r.get_random_words() + secret = "" + for _ in range(SENDER_LENGTH + PASSWORD_LENGTH): + secret += random.choice(WORDS) + "-" + while len(secret[SENDER_LENGTH:]) < 32: + secret += random.choice(WORDS) + "-" + secret = secret[:-1] + + self.secrets.append(secret) + + with open(secret_file, 'a+') as f: + f.write(secret + '\n') + + + def show_secrets(self): + for secret in self.secrets: + qrcode.make(secret).show() + print(secret) + + def get_password(self, index=0): + secret = self.secrets[index].split('-') + return "-".join(secret[SENDER_LENGTH:]) + + + def get_sender(self, index=0): + words = self.secrets[index].split('-') + return "-".join(words[0:SENDER_LENGTH]) + + def encrypt(self, data): + password = self.get_password() + return password_encrypt(data.encode(), password) + + def decrypt(self, data): + password = self.get_password() + return password_decrypt(data, password).decode() + + def push(self, data): + url = self.URL + "push" + params = { + 'sender': self.get_sender(), + 'data': self.encrypt(data) + } + + print(url) + r = requests.post(url, params) + print(r.json()) + + + def peak(self): + return self.get_post("peak") + + def pop(self): + return self.get_post("pop") + + def list(self): + return self.get_post("list") + + def clear(self): + return self.get_post("clear") + + def get_post(self, mechanism): + url = self.URL + mechanism + params = { + 'sender': self.get_sender() + } + r = requests.post(url, params) + if r.json() == {}: + return {} + return self.decrypt(r.json().get('data')) + +if __name__ == '__main__': + b = Bump()