src/eric7/Utilities/crypto/__init__.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/Utilities/crypto/__init__.py	Thu Jul 07 11:23:56 2022 +0200
@@ -0,0 +1,315 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2011 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package implementing cryptography related functionality.
+"""
+
+import random
+import base64
+
+from PyQt6.QtCore import QCoreApplication
+from PyQt6.QtWidgets import QLineEdit, QInputDialog
+
+from EricWidgets import EricMessageBox
+
+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.EchoMode.Password)
+    if ok:
+        from .py3PBKDF2 import verifyPassword
+        masterPassword = Preferences.getUser("MasterPassword")
+        try:
+            if masterPassword:
+                if verifyPassword(pw, masterPassword):
+                    MasterPassword = pwEncode(pw)
+                else:
+                    EricMessageBox.warning(
+                        None,
+                        QCoreApplication.translate(
+                            "Crypto", "Master Password"),
+                        QCoreApplication.translate(
+                            "Crypto",
+                            """The given password is incorrect."""))
+            else:
+                EricMessageBox.critical(
+                    None,
+                    QCoreApplication.translate("Crypto", "Master Password"),
+                    QCoreApplication.translate(
+                        "Crypto",
+                        """There is no master password registered."""))
+        except ValueError as why:
+            EricMessageBox.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)
+    
+    from .py3PBKDF2 import hashPasswordTuple
+    digestname, iterations, salt, pwHash = hashPasswordTuple(masterPW)
+    key = pwHash[:32]
+    from .py3AES import encryptData
+    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 decryption (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)
+    
+    from .py3AES import decryptData
+    from .py3PBKDF2 import rehashPassword
+    
+    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 encoded 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
+    MasterPassword = pwEncode(newPassword) if newPassword else None
+
+
+def dataEncrypt(data, password, keyLength=32, hashIterations=10000):
+    """
+    Module function to encrypt a password.
+    
+    @param data data to encrypt (bytes)
+    @param password password to be used for encryption (string)
+    @param keyLength length of the key to be generated for encryption
+        (16, 24 or 32)
+    @param hashIterations number of hashes to be applied to the password for
+        generating the encryption key (integer)
+    @return encrypted data (bytes) and flag indicating
+        success (boolean)
+    """
+    from .py3AES import encryptData
+    from .py3PBKDF2 import hashPasswordTuple
+    
+    digestname, iterations, salt, pwHash = hashPasswordTuple(
+        password, iterations=hashIterations)
+    key = pwHash[:keyLength]
+    try:
+        cipher = encryptData(key, data)
+    except ValueError:
+        return b"", False
+    return CryptoMarker.encode("utf-8") + Delimiter.encode("utf-8").join([
+        digestname.encode("utf-8"),
+        str(iterations).encode("utf-8"),
+        base64.b64encode(salt),
+        base64.b64encode(cipher)
+    ]), True
+
+
+def dataDecrypt(edata, password, keyLength=32):
+    """
+    Module function to decrypt a password.
+    
+    @param edata hashed data to decrypt (string)
+    @param password password to be used for decryption (string)
+    @param keyLength length of the key to be generated for decryption
+        (16, 24 or 32)
+    @return decrypted data (bytes) and flag indicating
+        success (boolean)
+    """
+    if not edata.startswith(CryptoMarker.encode("utf-8")):
+        return edata, False  # it was not encoded using dataEncrypt
+    
+    from .py3AES import decryptData
+    from .py3PBKDF2 import rehashPassword
+    
+    hashParametersBytes, edata = edata[3:].rsplit(Delimiter.encode("utf-8"), 1)
+    hashParameters = hashParametersBytes.decode()
+    try:
+        # recreate the key used to encrypt
+        key = rehashPassword(password, hashParameters)[:keyLength]
+        plaintext = decryptData(key, base64.b64decode(edata))
+    except ValueError:
+        return "", False
+    return plaintext, True

eric ide

mercurial