src/eric7/Utilities/crypto/__init__.py

Wed, 13 Jul 2022 14:55:47 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 13 Jul 2022 14:55:47 +0200
branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9413
80c06d472826
permissions
-rw-r--r--

Reformatted the source code using the 'Black' utility.

# -*- 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