Utilities/crypto/__init__.py

changeset 1127
b1802ebe0066
child 1130
3e9f0330f833
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utilities/crypto/__init__.py	Sun Jun 19 15:19:46 2011 +0200
@@ -0,0 +1,315 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2011 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package implementing cryptography related functionality.
+"""
+
+##import os
+##import sys
+##sys.path.insert(1, os.path.join(os.path.dirname(__file__), "../.."))
+##
+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
+##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 passwordHash(pw):
+##    """
+##    Module function to calculate the hash for the given password.
+##    
+##    This is done by hashing it 65.000 times with SHA1 in order to make brute force
+##    attacks a bit harder.
+##    
+##    @param pw password to be hashed (string)
+##    @return password hash (string)
+##    """
+##    hash = QCryptographicHash.hash(QByteArray(pw.encode("utf-8")),
+##                                   QCryptographicHash.Sha1)
+##    for i in range(65000):
+##        hash = QCryptographicHash.hash(hash, QCryptographicHash.Sha1)
+##    return base64.b64encode(bytes(hash)).decode("ascii")
+##
+##
+##def generateCryptoKey(pw, keyLength=32):
+##    """
+##    Module function to calculate a crypto key given a password.
+##    
+##    This is done by hashing the password 32.000 times MD5 and 32.000 times with MD4.
+##    These hashes are concatenated and and the first bytes are taken depending on the
+##    desired key length.
+##    
+##    @param pw password to be used (string)
+##    @param keyLength length of the desired key (16, 24 or 32) (default is
+##        32 bytes suitable for AES256 encryption)
+##    @return crypto key (bytes)
+##    """
+##    if keyLength not in [16, 24, 32]:
+##        raise ValueError(QCoreApplication.translate(
+##            "Crypto", "Illegal key length ({0}) given.").format(keyLength))
+##    
+##    hash1 = QCryptographicHash.hash(QByteArray(pw.encode("utf-8")),
+##                                    QCryptographicHash.Md5)
+##    hash2 = QCryptographicHash.hash(QByteArray(pw.encode("utf-8")),
+##                                    QCryptographicHash.Md4)
+##    for i in range(32000):
+##        hash1 = QCryptographicHash.hash(hash1, QCryptographicHash.Md5)
+##        hash2 = QCryptographicHash.hash(hash2, QCryptographicHash.Md4)
+##    hash = (hash1 + hash2)[:keyLength]
+##    return bytes(hash)
+
+
+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