Utilities/crypto/__init__.py

Mon, 26 Dec 2011 19:31:22 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Mon, 26 Dec 2011 19:31:22 +0100
changeset 1509
c0b5e693b0eb
parent 1130
3e9f0330f833
child 1680
28e57079dab5
child 2303
0ed4ed026c16
permissions
-rw-r--r--

Updated copyright for 2012.

# -*- coding: utf-8 -*-

# Copyright (c) 2011 - 2012 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Package implementing cryptography related functionality.
"""

import random
import base64

from PyQt4.QtCore import QCoreApplication
from PyQt4.QtGui import QLineEdit, QInputDialog

from E5Gui import E5MessageBox

from .py3AES import encryptData, decryptData
from .py3PBKDF2 import verifyPassword, hashPasswordTuple, rehashPassword

import Preferences

################################################################################
## password handling functions below
################################################################################


EncodeMarker = "CE4"
CryptoMarker = "CR5"

Delimiter = "$"

MasterPassword = None


def pwEncode(pw):
    """
    Module function to encode a password.
    
    @param pw password to encode (string)
    @return encoded password (string)
    """
    pop = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,;:-_!$?*+#"
    rpw = "".join(random.sample(pop, 32)) + pw + "".join(random.sample(pop, 32))
    return EncodeMarker + base64.b64encode(rpw.encode("utf-8")).decode("ascii")


def pwDecode(epw):
    """
    Module function to decode a password.
    
    @param epw encoded password to decode (string)
    @return decoded password (string)
    """
    if not epw.startswith(EncodeMarker):
        return epw  # it was not encoded using pwEncode
    
    return base64.b64decode(epw[3:].encode("ascii"))[32:-32].decode("utf-8")


def __getMasterPassword():
    """
    Private module function to get the password from the user.
    """
    global MasterPassword
    
    pw, ok = QInputDialog.getText(
        None,
        QCoreApplication.translate("Crypto", "Master Password"),
        QCoreApplication.translate("Crypto", "Enter the master password:"),
        QLineEdit.Password)
    if ok:
        masterPassword = Preferences.getUser("MasterPassword")
        try:
            if masterPassword:
                if verifyPassword(pw, masterPassword):
                    MasterPassword = pwEncode(pw)
                else:
                    E5MessageBox.warning(None,
                        QCoreApplication.translate("Crypto", "Master Password"),
                        QCoreApplication.translate("Crypto",
                            """The given password is incorrect."""))
            else:
                E5MessageBox.critical(None,
                    QCoreApplication.translate("Crypto", "Master Password"),
                    QCoreApplication.translate("Crypto",
                        """There is no master password registered."""))
        except ValueError as why:
            E5MessageBox.warning(None,
                QCoreApplication.translate("Crypto", "Master Password"),
                QCoreApplication.translate("Crypto",
                    """<p>The given password cannot be verified.</p>"""
                    """<p>Reason: {0}""".format(str(why))))


def pwEncrypt(pw, masterPW=None):
    """
    Module function to encrypt a password.
    
    @param pw password to encrypt (string)
    @param masterPW password to be used for encryption (string)
    @return encrypted password (string) and flag indicating
        success (boolean)
    """
    if masterPW is None:
        if MasterPassword is None:
            __getMasterPassword()
            if MasterPassword is None:
                return "", False
        
        masterPW = pwDecode(MasterPassword)
    
    digestname, iterations, salt, hash = hashPasswordTuple(masterPW)
    key = hash[:32]
    try:
        cipher = encryptData(key, pw.encode("utf-8"))
    except ValueError:
        return "", False
    return CryptoMarker + Delimiter.join([
        digestname,
        str(iterations),
        base64.b64encode(salt).decode("ascii"),
        base64.b64encode(cipher).decode("ascii")
    ]), True


def pwDecrypt(epw, masterPW=None):
    """
    Module function to decrypt a password.
    
    @param epw hashed password to decrypt (string)
    @param masterPW password to be used for encryption (string)
    @return decrypted password (string) and flag indicating
        success (boolean)
    """
    if not epw.startswith(CryptoMarker):
        return epw, False  # it was not encoded using pwEncrypt
    
    if masterPW is None:
        if MasterPassword is None:
            __getMasterPassword()
            if MasterPassword is None:
                return "", False
        
        masterPW = pwDecode(MasterPassword)
    
    hashParameters, epw = epw[3:].rsplit(Delimiter, 1)
    try:
        # recreate the key used to encrypt
        key = rehashPassword(masterPW, hashParameters)[:32]
        plaintext = decryptData(key, base64.b64decode(epw.encode("ascii")))
    except ValueError:
        return "", False
    return plaintext.decode("utf-8"), True


def pwReencrypt(epw, oldPassword, newPassword):
    """
    Module function to re-encrypt a password.
    
    @param epw hashed password to re-encrypt (string)
    @param oldPassword password used to encrypt (string)
    @param newPassword new password to be used (string)
    @return encrypted password (string) and flag indicating
        success (boolean)
    """
    plaintext, ok = pwDecrypt(epw, oldPassword)
    if ok:
        return pwEncrypt(plaintext, newPassword)
    else:
        return "", False


def pwRecode(epw, oldPassword, newPassword):
    """
    Module function to re-encode a password.
    
    In case of an error the encoded password is returned unchanged.
    
    @param epw encoded password to re-encode (string)
    @param oldPassword password used to encode (string)
    @param newPassword new password to be used (string)
    @return encoded password (string)
    """
    if epw == "":
        return epw
    
    if newPassword == "":
        plaintext, ok = pwDecrypt(epw)
        return (pwEncode(plaintext) if ok else epw)
    else:
        if oldPassword == "":
            plaintext = pwDecode(epw)
            cipher, ok = pwEncrypt(plaintext, newPassword)
            return (cipher if ok else epw)
        else:
            npw, ok = pwReencrypt(epw, oldPassword, newPassword)
            return (npw if ok else epw)


def pwConvert(pw, encode=True):
    """
    Module function to convert a plaintext password to the encoded form or
    vice versa.
    
    If there is an error, an empty code is returned for the encode function
    or the given encoded password for the decode function.
    
    @param pw password to encode (string)
    @param encode flag indicating an encode or decode function (boolean)
    @return encode or decoded password (string)
    """
    if pw == "":
        return pw
    
    if encode:
        # plain text -> encoded
        if Preferences.getUser("UseMasterPassword"):
            epw = pwEncrypt(pw)[0]
        else:
            epw = pwEncode(pw)
        return epw
    else:
        # encoded -> plain text
        if Preferences.getUser("UseMasterPassword"):
            plain, ok = pwDecrypt(pw)
        else:
            plain, ok = pwDecode(pw), True
        return (plain if ok else pw)


def changeRememberedMaster(newPassword):
    """
    Module function to change the remembered master password.
    
    @param newPassword new password to be used (string)
    """
    global MasterPassword
    
    if newPassword == "":
        MasterPassword = None
    else:
        MasterPassword = pwEncode(newPassword)
    
if __name__ == "__main__":
    import sys
    from PyQt4.QtGui import QApplication
    
    app = QApplication([])
    
    mpw = "blahblah"
    cpw = "SomeSecret"
    
    cipher, ok = pwEncrypt(cpw)
    print(ok, cipher)
    plain, ok = pwDecrypt(cipher)
    print(ok, plain)
    
    cipher, ok = pwEncrypt(cpw, mpw)
    print(ok, cipher)
    plain, ok = pwDecrypt(cipher, mpw)
    print(ok, plain)
    
    sys.exit(0)

eric ide

mercurial