src/eric7/EricUtilities/crypto/__init__.py

branch
eric7
changeset 10928
46651e194fbe
parent 10503
6a37b6ac3928
child 11090
f5f5f5803935
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/EricUtilities/crypto/__init__.py	Thu Sep 26 15:49:36 2024 +0200
@@ -0,0 +1,350 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2011 - 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package implementing cryptography related functionality.
+"""
+
+import base64
+import random
+
+from PyQt6.QtCore import QCoreApplication
+from PyQt6.QtWidgets import QInputDialog, QLineEdit
+
+from eric7 import Preferences
+from eric7.EricWidgets import EricMessageBox
+
+###############################################################################
+## password handling functions below
+###############################################################################
+
+
+EncodeMarker = "CE4"
+CryptoMarker = "CR5"
+
+Delimiter = "$"
+
+MainPassword = None
+
+
+def pwEncode(pw):
+    """
+    Module function to encode a password.
+
+    @param pw password to encode
+    @type str
+    @return encoded password
+    @rtype str
+    """
+    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
+    @type str
+    @return decoded password
+    @rtype str
+    """
+    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 __getMainPassword():
+    """
+    Private module function to get the password from the user.
+    """
+    from .py3PBKDF2 import verifyPassword
+
+    global MainPassword
+
+    pw, ok = QInputDialog.getText(
+        None,
+        QCoreApplication.translate("Crypto", "Main Password"),
+        QCoreApplication.translate("Crypto", "Enter the main password:"),
+        QLineEdit.EchoMode.Password,
+    )
+    if ok:
+        mainPassword = Preferences.getUser("MainPassword")
+        try:
+            if mainPassword:
+                if verifyPassword(pw, mainPassword):
+                    MainPassword = pwEncode(pw)
+                else:
+                    EricMessageBox.warning(
+                        None,
+                        QCoreApplication.translate("Crypto", "Main Password"),
+                        QCoreApplication.translate(
+                            "Crypto", """The given password is incorrect."""
+                        ),
+                    )
+            else:
+                EricMessageBox.critical(
+                    None,
+                    QCoreApplication.translate("Crypto", "Main Password"),
+                    QCoreApplication.translate(
+                        "Crypto", """There is no main password registered."""
+                    ),
+                )
+        except ValueError as why:
+            EricMessageBox.warning(
+                None,
+                QCoreApplication.translate("Crypto", "Main Password"),
+                QCoreApplication.translate(
+                    "Crypto",
+                    """<p>The given password cannot be verified.</p>"""
+                    """<p>Reason: {0}""".format(str(why)),
+                ),
+            )
+
+
+def pwEncrypt(pw, mainPW=None):
+    """
+    Module function to encrypt a password.
+
+    @param pw password to encrypt
+    @type str
+    @param mainPW password to be used for encryption
+    @type str
+    @return encrypted password (string) and flag indicating success
+    @rtype bool
+    """
+    from .py3AES import encryptData
+    from .py3PBKDF2 import hashPasswordTuple
+
+    if mainPW is None:
+        if MainPassword is None:
+            __getMainPassword()
+            if MainPassword is None:
+                return "", False
+
+        mainPW = pwDecode(MainPassword)
+
+    digestname, iterations, salt, pwHash = hashPasswordTuple(mainPW)
+    key = pwHash[: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, mainPW=None):
+    """
+    Module function to decrypt a password.
+
+    @param epw hashed password to decrypt
+    @type str
+    @param mainPW password to be used for decryption
+    @type str
+    @return decrypted password (string) and flag indicating success
+    @rtype bool
+    """
+    from .py3AES import decryptData
+    from .py3PBKDF2 import rehashPassword
+
+    if not epw.startswith(CryptoMarker):
+        return epw, False  # it was not encoded using pwEncrypt
+
+    if mainPW is None:
+        if MainPassword is None:
+            __getMainPassword()
+            if MainPassword is None:
+                return "", False
+
+        mainPW = pwDecode(MainPassword)
+
+    hashParameters, epw = epw[3:].rsplit(Delimiter, 1)
+    try:
+        # recreate the key used to encrypt
+        key = rehashPassword(mainPW, 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
+    @type str
+    @param oldPassword password used to encrypt
+    @type str
+    @param newPassword new password to be used
+    @type str
+    @return encrypted password (string) and flag indicating success
+    @rtype bool
+    """
+    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
+    @type str
+    @param oldPassword password used to encode
+    @type str
+    @param newPassword new password to be used
+    @type str
+    @return encoded password
+    @rtype str
+    """
+    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
+    @type str
+    @param encode flag indicating an encode or decode function
+    @type bool
+    @return encoded or decoded password
+    @rtype str
+    """
+    if pw == "":
+        return pw
+
+    if encode:
+        # plain text -> encoded
+        if Preferences.getUser("UseMainPassword"):
+            epw = pwEncrypt(pw)[0]
+        else:
+            epw = pwEncode(pw)
+        return epw
+    else:
+        # encoded -> plain text
+        if Preferences.getUser("UseMainPassword"):
+            plain, ok = pwDecrypt(pw)
+        else:
+            plain, ok = pwDecode(pw), True
+        return plain if ok else pw
+
+
+def changeRememberedMain(newPassword):
+    """
+    Module function to change the remembered main password.
+
+    @param newPassword new password to be used
+    @type str
+    """
+    global MainPassword
+    MainPassword = 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
+    @type bytes
+    @param password password to be used for encryption
+    @type str
+    @param keyLength length of the key to be generated for encryption (16, 24 or 32)
+    @type int
+    @param hashIterations number of hashes to be applied to the password for
+        generating the encryption key
+    @type int
+    @return encrypted data (bytes) and flag indicating success
+    @rtype bool
+    """
+    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
+    @type str
+    @param password password to be used for decryption
+    @type str
+    @param keyLength length of the key to be generated for decryption (16, 24 or 32)
+    @type int
+    @return decrypted data (bytes) and flag indicating success
+    @rtype bool
+    """
+    from .py3AES import decryptData
+    from .py3PBKDF2 import rehashPassword
+
+    if not edata.startswith(CryptoMarker.encode("utf-8")):
+        return edata, False  # it was not encoded using dataEncrypt
+
+    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