Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
security.py202 linesDownload Raw Back to werkzeug
1from __future__ import annotations2 3import hashlib4import hmac5import os6import posixpath7import secrets8 9SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"10DEFAULT_PBKDF2_ITERATIONS = 1_000_00011 12_os_alt_seps: list[str] = list(13    sep for sep in [os.sep, os.altsep] if sep is not None and sep != "/"14)15# https://chrisdenton.github.io/omnipath/Special%20Dos%20Device%20Names.html16_windows_device_files = {17    "AUX",18    "CON",19    "CONIN$",20    "CONOUT$",21    *(f"COM{c}" for c in "123456789¹²³"),22    *(f"LPT{c}" for c in "123456789¹²³"),23    "NUL",24    "PRN",25}26 27 28def gen_salt(length: int) -> str:29    """Generate a random string of SALT_CHARS with specified ``length``."""30    if length <= 0:31        raise ValueError("Salt length must be at least 1.")32 33    return "".join(secrets.choice(SALT_CHARS) for _ in range(length))34 35 36def _hash_internal(method: str, salt: str, password: str) -> tuple[str, str]:37    method, *args = method.split(":")38    salt_bytes = salt.encode()39    password_bytes = password.encode()40 41    if method == "scrypt":42        if not args:43            n = 2**1544            r = 845            p = 146        else:47            try:48                n, r, p = map(int, args)49            except ValueError:50                raise ValueError("'scrypt' takes 3 arguments.") from None51 52        maxmem = 132 * n * r * p  # ideally 128, but some extra seems needed53        return (54            hashlib.scrypt(55                password_bytes, salt=salt_bytes, n=n, r=r, p=p, maxmem=maxmem56            ).hex(),57            f"scrypt:{n}:{r}:{p}",58        )59    elif method == "pbkdf2":60        len_args = len(args)61 62        if len_args == 0:63            hash_name = "sha256"64            iterations = DEFAULT_PBKDF2_ITERATIONS65        elif len_args == 1:66            hash_name = args[0]67            iterations = DEFAULT_PBKDF2_ITERATIONS68        elif len_args == 2:69            hash_name = args[0]70            iterations = int(args[1])71        else:72            raise ValueError("'pbkdf2' takes 2 arguments.")73 74        return (75            hashlib.pbkdf2_hmac(76                hash_name, password_bytes, salt_bytes, iterations77            ).hex(),78            f"pbkdf2:{hash_name}:{iterations}",79        )80    else:81        raise ValueError(f"Invalid hash method '{method}'.")82 83 84def generate_password_hash(85    password: str, method: str = "scrypt", salt_length: int = 1686) -> str:87    """Securely hash a password for storage. A password can be compared to a stored hash88    using :func:`check_password_hash`.89 90    The following methods are supported:91 92    -   ``scrypt``, the default. The parameters are ``n``, ``r``, and ``p``, the default93        is ``scrypt:32768:8:1``. See :func:`hashlib.scrypt`.94    -   ``pbkdf2``, less secure. The parameters are ``hash_method`` and ``iterations``,95        the default is ``pbkdf2:sha256:600000``. See :func:`hashlib.pbkdf2_hmac`.96 97    Default parameters may be updated to reflect current guidelines, and methods may be98    deprecated and removed if they are no longer considered secure. To migrate old99    hashes, you may generate a new hash when checking an old hash, or you may contact100    users with a link to reset their password.101 102    :param password: The plaintext password.103    :param method: The key derivation function and parameters.104    :param salt_length: The number of characters to generate for the salt.105 106    .. versionchanged:: 3.1107        The default iterations for pbkdf2 was increased to 1,000,000.108 109    .. versionchanged:: 2.3110        Scrypt support was added.111 112    .. versionchanged:: 2.3113        The default iterations for pbkdf2 was increased to 600,000.114 115    .. versionchanged:: 2.3116        All plain hashes are deprecated and will not be supported in Werkzeug 3.0.117    """118    salt = gen_salt(salt_length)119    h, actual_method = _hash_internal(method, salt, password)120    return f"{actual_method}${salt}${h}"121 122 123def check_password_hash(pwhash: str, password: str) -> bool:124    """Securely check that the given stored password hash, previously generated using125    :func:`generate_password_hash`, matches the given password.126 127    Methods may be deprecated and removed if they are no longer considered secure. To128    migrate old hashes, you may generate a new hash when checking an old hash, or you129    may contact users with a link to reset their password.130 131    :param pwhash: The hashed password.132    :param password: The plaintext password.133 134    .. versionchanged:: 2.3135        All plain hashes are deprecated and will not be supported in Werkzeug 3.0.136    """137    try:138        method, salt, hashval = pwhash.split("$", 2)139    except ValueError:140        return False141 142    return hmac.compare_digest(_hash_internal(method, salt, password)[0], hashval)143 144 145def safe_join(directory: str, *untrusted: str) -> str | None:146    """Safely join zero or more untrusted path components to a trusted base147    directory to avoid escaping the base directory.148 149    The untrusted path is assumed to be from/for a URL, such as for serving150    files. Therefore, it should only use the forward slash ``/`` path separator,151    and will be joined using that separator. On Windows, the backslash ``\\``152    separator is not allowed.153 154    :param directory: The trusted base directory.155    :param untrusted: The untrusted path components relative to the156        base directory.157    :return: A safe path, otherwise ``None``.158 159    .. versionchanged:: 3.1.6160        Special device names in multi-segment paths are not allowed on Windows.161 162    .. versionchanged:: 3.1.5163        More special device names, regardless of extension or trailing spaces,164        are not allowed on Windows.165 166    .. versionchanged:: 3.1.4167        Special device names are not allowed on Windows.168    """169    if not directory:170        # Ensure we end up with ./path if directory="" is given,171        # otherwise the first untrusted part could become trusted.172        directory = "."173 174    parts = [directory]175 176    for part in untrusted:177        if not part:178            continue179 180        part = posixpath.normpath(part)181 182        if (183            os.path.isabs(part)184            # ntpath.isabs doesn't catch this185            or part.startswith("/")186            or part == ".."187            or part.startswith("../")188            or any(sep in part for sep in _os_alt_seps)189            or (190                os.name == "nt"191                and any(192                    p.partition(".")[0].strip().upper() in _windows_device_files193                    for p in part.split("/")194                )195            )196        ):197            return None198 199        parts.append(part)200 201    return posixpath.join(*parts)202 
codekingpro/portable-devtools · Team Ai