| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 | # ============================================================================# This file is part of Pwman3.## Pwman3 is free software; you can redistribute it and/or modify# it under the terms of the GNU General Public License, version 2# as published by the Free Software Foundation;## Pwman3 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 Pwman3; if not, write to the Free Software# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA# ============================================================================# Copyright (C) 2014 Oz Nahum <nahumoz@gmail.com># ============================================================================from __future__ import print_functionfrom Crypto.Cipher import AESfrom Crypto.Protocol.KDF import PBKDF2import base64import osimport sysimport binasciiimport timefrom pwman.util.callback import Callbackimport pwman.util.config as configif sys.version_info.major > 2:  # pragma: no cover    raw_input = inputEncodeAES = lambda c, s: base64.b64encode(c.encrypt(s))DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip()class CryptoException(Exception):    passdef get_digest(password, salt):    """    Get a digest based on clear text password    """    iterations = 5000    return PBKDF2(password, salt, dkLen=32, count=iterations)def authenticate(password, salt, digest):    """    salt and digest are stored in a file or a database    """    dig = get_digest(password, salt)    return binascii.hexlify(dig) == digestdef write_password(reader=raw_input):    """    Write a secret password as a hash and the salt used for this hash    to a file    """    salt = base64.b64encode(os.urandom(32))    passwd = reader("Please type in the secret key:")    key = get_digest(passwd, salt)    f = open('passwords.txt', 'wt')    hpk = salt+'$6$'.encode('utf8')+binascii.hexlify(key)    f.write(hpk.decode('utf-8'))    f.close()def get_digest_from_file(filename):    """    Read a digested password and salt from the file    """    f = open(filename, 'rt')    salt, digest = f.readline().split('$6$')    return salt.encode('utf-8'), digest.encode('utf-8')def get_cipher(password, salt):    """    Create a chiper object from a hashed password    """    iv = os.urandom(AES.block_size)    dig = get_digest(password, salt)    chiper = AES.new(dig, AES.MODE_ECB, iv)    return chiperdef cli_auth(reader=raw_input):    """    Read password from the user, if the password is correct,    finish the execution an return the password and salt which    are read from the file.    """    salt, digest = get_digest_from_file('passwords.txt')    tries = 0    while tries < 5:        password = reader("Please type in your master password:"                          ).encode('utf-8')        if authenticate(password, salt, digest):            return password, salt        print("You entered a wrong password...")        tries += 1    raise CryptoException("You entered wrong password 5 times..")def prepare_data(text, block_size):    """    prepare data before encryption so the lenght matches the expected    lenght by the algorithm.    """    num_blocks = len(text)//block_size + 1    newdatasize = block_size*num_blocks    return text.ljust(newdatasize)def save_a_secret_message(reader=raw_input):    """    PoC to show we can encrypt a message    """    secret_msg = """This is a very important message! Learn Cryptography!!!"""    # the secret message will be encrypted with the secret password found    # in the file    passwd, salt = cli_auth(reader=reader)    cipher = get_cipher(passwd, salt)    # explictly destroy password, so now there is no clear text reference    # to the input given by the user    del(passwd)    msg = EncodeAES(cipher, prepare_data(secret_msg, AES.block_size))    with open('secret.enc', 'wt') as s:        s.write(msg.decode())    print("The cipher message is:", msg.decode())def read_a_secret_message(reader=raw_input):    """    PoC to show we can decrypt a message    """    passwd, salt = cli_auth(reader)    cipher = get_cipher(passwd, salt)    del(passwd)    with open('secret.enc') as s:        msg = s.readline()        print("The decrypted secret message is:")        decoded = DecodeAES(cipher, msg)        print(decoded)class CryptoEngine(object):  # pagma: no cover    _timeoutcount = 0    _instance = None    _instance_new = None    _callback = None    @classmethod    def get(cls, dbver=0.5):        if CryptoEngine._instance:            return CryptoEngine._instance        if CryptoEngine._instance_new:            return CryptoEngine._instance_new        algo = config.get_value("Encryption", "algorithm")        if not algo:            raise Exception("Parameters missing, no algorithm given")        try:            timeout = int(config.get_value("Encryption", "timeout"))        except ValueError:            timeout = -1        salt, digest = get_digest_from_file('passwords.txt')        kwargs = {'algorithm': algo,                  'timeout': timeout, 'salt': salt, 'digest': digest}        if dbver >= 0.5:            CryptoEngine._instance_new = CryptoEngine(**kwargs)            return CryptoEngine._instance_new    def __init__(self, salt=None, digest=None, algorithm='AES',                 timeout=-1, reader=None):        """        Initialise the Cryptographic Engine        """        self._algo = algorithm        self._digest = digest if digest else None        self._salt = salt if salt else None        self._timeout = timeout        self._cipher = None        self._reader = reader        self._callback = None    def authenticate(self, password):        """        salt and digest are stored in a file or a database        """        dig = get_digest(password, self._salt)        if binascii.hexlify(dig) == self._digest:            CryptoEngine._timeoutcount = time.time()            self._cipher = get_cipher(password, self._salt)            return True        return False    def _auth(self):        """        Read password from the user, if the password is correct,        finish the execution an return the password and salt which        are read from the file.        """        salt, digest = self._salt, self._digest        tries = 0        while tries < 5:            password = self._getsecret("Please type in your master password:"                                       ).encode('utf-8')            if authenticate(password, salt, digest):                return password, salt            print("You entered a wrong password...")            tries += 1        raise CryptoException("You entered wrong password 5 times..")    def encrypt(self, text):        if not self._is_authenticated():            p, s = self._auth()            cipher = get_cipher(p, s)            del(p)            return EncodeAES(cipher, prepare_data(text, AES.block_size))        return EncodeAES(self._cipher, prepare_data(text, AES.block_size))    def decrypt(self, cipher_text):        if not self._is_authenticated():            p, s = self._auth()            cipher = get_cipher(p, s)            del(p)            return DecodeAES(cipher, prepare_data(cipher_text, AES.block_size))        return DecodeAES(self._cipher, prepare_data(cipher_text,                                                    AES.block_size))    def forget(self):        """        discard cipher        """        self._cipher = None    def _is_authenticated(self):        if not self._is_timedout() and self._cipher is not None:            return True        return False    def _is_timedout(self):        if self._timeout > 0:            if (time.time() - CryptoEngine._timeoutcount) > self._timeout:                self._cipher = None            return True        return False    def changepassword(self, reader=raw_input):        if self._callback is None:            raise CryptoException("No callback class has been "                                  "specified")        self._keycrypted = self._create_password()        return self._keycrypted    @property    def callback(self):        """        return call back function        """        return self._callback    @callback.setter    def callback(self, callback):        if isinstance(callback, Callback):            self._callback = callback            self._getsecret = callback.getsecret            self._getnewsecret = callback.getnewsecret        else:            raise Exception("callback must be an instance of Callback!")    def _create_password(self):        """        Create a secret password as a hash and the salt used for this hash.        Change reader to manipulate how input is given.        """        salt = base64.b64encode(os.urandom(32))        passwd = self._getsecret("Please type in the secret key:")        key = self._get_digest(passwd, salt)        hpk = salt+'$6$'.encode('utf8')+binascii.hexlify(key)        return hpk.decode('utf-8')    def _get_digest(self, password, salt):        """        Get a digest based on clear text password        """        iterations = 5000        return PBKDF2(password, salt, dkLen=32, count=iterations)    def set_cryptedkey(self, key):        # TODO: rename this method!        salt, digest = key.split('$6$')        self._digest = digest.encode('utf-8')        self._salt = salt.encode('utf-8')    def get_cryptedkey(self):        # TODO: rename this method!        """        return _keycrypted        """        return self._salt + '$6$' + self._digestif __name__ == '__main__':  # pragma: no cover    if '-i' in sys.argv:        write_password()    save_a_secret_message()    read_a_secret_message()
 |