From 8c4f590c61472aa754a180e918ca5de7d1af5ad6 Mon Sep 17 00:00:00 2001 From: Michael Rasmussen Date: Sun, 5 Aug 2018 06:38:18 +0200 Subject: [PATCH] Basic framework finished --- cryptonize.py | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++ db.py | 71 +++++++++++++++++++++++++++++++++++++ user.py | 89 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 257 insertions(+) create mode 100644 cryptonize.py create mode 100644 db.py create mode 100644 user.py diff --git a/cryptonize.py b/cryptonize.py new file mode 100644 index 0000000..9cc4631 --- /dev/null +++ b/cryptonize.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2018 Michael Rasmussen + +# This file is part of SecureMail. + +# SecureMail is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SecureMail is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SecureMail. If not, see . + +from nacl.secret import SecretBox +from nacl.public import PrivateKey, Box +from nacl.utils import random +from nacl.encoding import HexEncoder +import nacl.hash + +class Cryptonize: + """ + Encrypt and decrypt objects + """ + + def symmetric_encrypt(self, key, plain): + skey = self.sanitize_key(key) + box = SecretBox(skey) + cipher = box.encrypt(plain) + box = None + + return cipher + + def symmetric_decrypt(self, key, cipher): + skey = self.sanitize_key(key) + box = SecretBox(skey) + plain = box.decrypt(cipher) + box = None + + return plain + + def asymmetric_encrypt(self, privkey, pubkey, plain): + box = Box(privkey, pubkey) + cipher = box.encrypt(plain) + box = None + + return cipher + + def asymmetric_decrypt(self, privkey, pubkey, cipher): + box = Box(privkey, pubkey) + plain = box.decrypt(cipher) + box = None + + return plain + + def get_random_key(self): + return random(SecretBox.KEY_SIZE) + + def sanitize_key(self, key): + if not isinstance(key, bytes): + key = key.encode('utf-8') + size = len(key) + if size < SecretBox.KEY_SIZE: + """We must pad""" + pad = None + for i in range(SecretBox.KEY_SIZE - size): + if pad is None: + pad = b'\0' + else: + pad += b'\0' + newkey = key + pad + else: + newkey = key + + + return newkey + + def get_key_pair(self): + privkey = PrivateKey.generate() + pubkey = privkey.public_key + + return (privkey, pubkey) + + def generate_hash(self, key): + if not isinstance(key, bytes): + key = key.encode('utf-8') + HASHER = nacl.hash.sha512 + digest = HASHER(key, encoder=HexEncoder) + + return digest.decode() + + diff --git a/db.py b/db.py new file mode 100644 index 0000000..e904fb1 --- /dev/null +++ b/db.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2018 Michael Rasmussen + +# This file is part of SecureMail. + +# SecureMail is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SecureMail is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SecureMail. If not, see . + +from config import DBTYPE, DBHOST, DBPORT, DBUID, DBPWD, DBNAME + +class Singleton: + def __init__(self, klass): + self.klass = klass + self.instance = None + + def __call__(self, *args, **kwargs): + if self.instance == None: + self.instance = self.klass(*args, **kwargs) + return self.instance + +@Singleton +class DB: + conn = None + + def get_connection(self): + if self.conn is None: + if DBTYPE == 'mysql': + import mysql.connector + self.conn = mysql.connector.connect(host=DBHOST, port=DBPORT, user=DBUID, password=DBPWD, database=DBNAME) + elif DBTYPE == 'postgresql': + import psycopg2 + self.conn = psycopg2.connect(host=DBHOST, port=DBPORT, user=DBUID, password=DBPWD, dbname=DBNAME) + return self.conn + + def __del__(self): + if self.conn is not None: + self.conn.close() + +class DBInterface: + @staticmethod + def load_user(key): + conn = DB().get_connection() + cursor = conn.cursor() + cursor.execute("select a.cipher from account a where id = '{0}'".format(key)) + row = cursor.fetchone() + if row is None: + obj = None + else: + obj = row[0].tobytes() + cursor.close() + + return obj + + @staticmethod + def store_user(key, cipher): + conn = DB().get_connection() + cursor = conn.cursor() + cursor.execute("insert into account(id, cipher) values(%s, %s)", (key, cipher)) + conn.commit() + cursor.close() diff --git a/user.py b/user.py new file mode 100644 index 0000000..3b4c051 --- /dev/null +++ b/user.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2018 Michael Rasmussen + +# This file is part of SecureMail. + +# SecureMail is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SecureMail is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SecureMail. If not, see . + +try: + import cPickle as pickle +except: + import pickle +from db import DBInterface as DBI +from cryptonize import Cryptonize + +class NoSuchUserException(Exception): + pass + +class User: + """ + Class implementing the backend users + """ + def __init__(self, key=None): + if key is not None: + self.load(key) + + def store(self, key): + crypto = Cryptonize() + cipher = crypto.symmetric_encrypt(key, pickle.dumps(self.__dict__)) + DBI.store_user(crypto.generate_hash(key), cipher) + + def load(self, key): + crypto = Cryptonize() + cipher = DBI.load_user(crypto.generate_hash(key)) + if cipher is None: + raise NoSuchUserException('{0}: User not found'.format(key)) + plain = crypto.symmetric_decrypt(key, cipher) + try: + obj = pickle.loads(plain) + self.__dict__.update(obj) + except pickle.UnpicklingError as e: + raise NoSuchUserException(e) + + @property + def name(self): + return self._name + + @name.setter + def name(self, name): + self._name = name + + @property + def email(self): + return self.email + + @email.setter + def email(self, email): + self._email = email + + +if __name__ == '__main__': + try: + u = User('test') + for attr, value in u.__dict__.items(): + print ('{0}: {1}'.format(attr, value)) + c = Cryptonize() + key = 'æselØre' #c.get_random_key() + cipher = c.symmetric_encrypt(key, pickle.dumps(u)) + obj = pickle.loads(c.symmetric_decrypt(key, cipher)) + for attr, value in obj.__dict__.items(): + print ('{0}: {1}'.format(attr, value)) + except NoSuchUserException: + u = User() + u.name = 'testname' + u.email = 'testname@securemail.icu' + u.store('test') + except Exception as e: + print (e) -- 2.39.2