File: C:/Python311/Lib/site-packages/jupyter_server/auth/security.py
"""
Password generation for the Jupyter Server.
"""
import getpass
import hashlib
import json
import os
import random
import traceback
import warnings
from contextlib import contextmanager
from jupyter_core.paths import jupyter_config_dir
from traitlets.config import Config
from traitlets.config.loader import ConfigFileNotFound, JSONFileConfigLoader
# Length of the salt in nr of hex chars, which implies salt_len * 4
# bits of randomness.
salt_len = 12
def passwd(passphrase=None, algorithm="argon2"):
"""Generate hashed password and salt for use in server configuration.
In the server configuration, set `c.ServerApp.password` to
the generated string.
Parameters
----------
passphrase : str
Password to hash. If unspecified, the user is asked to input
and verify a password.
algorithm : str
Hashing algorithm to use (e.g, 'sha1' or any argument supported
by :func:`hashlib.new`, or 'argon2').
Returns
-------
hashed_passphrase : str
Hashed password, in the format 'hash_algorithm:salt:passphrase_hash'.
Examples
--------
>>> passwd("mypassword") # doctest: +ELLIPSIS
'argon2:...'
"""
if passphrase is None:
for _ in range(3):
p0 = getpass.getpass("Enter password: ")
p1 = getpass.getpass("Verify password: ")
if p0 == p1:
passphrase = p0
break
warnings.warn("Passwords do not match.", stacklevel=2)
else:
msg = "No matching passwords found. Giving up."
raise ValueError(msg)
if algorithm == "argon2":
import argon2
ph = argon2.PasswordHasher(
memory_cost=10240,
time_cost=10,
parallelism=8,
)
h_ph = ph.hash(passphrase)
return f"{algorithm}:{h_ph}"
h = hashlib.new(algorithm)
salt = ("%0" + str(salt_len) + "x") % random.getrandbits(4 * salt_len)
h.update(passphrase.encode("utf-8") + salt.encode("ascii"))
return f"{algorithm}:{salt}:{h.hexdigest()}"
def passwd_check(hashed_passphrase, passphrase):
"""Verify that a given passphrase matches its hashed version.
Parameters
----------
hashed_passphrase : str
Hashed password, in the format returned by `passwd`.
passphrase : str
Passphrase to validate.
Returns
-------
valid : bool
True if the passphrase matches the hash.
Examples
--------
>>> myhash = passwd("mypassword")
>>> passwd_check(myhash, "mypassword")
True
>>> passwd_check(myhash, "otherpassword")
False
>>> passwd_check("sha1:0e112c3ddfce:a68df677475c2b47b6e86d0467eec97ac5f4b85a", "mypassword")
True
"""
if hashed_passphrase.startswith("argon2:"):
import argon2
import argon2.exceptions
ph = argon2.PasswordHasher()
try:
return ph.verify(hashed_passphrase[7:], passphrase)
except argon2.exceptions.VerificationError:
return False
try:
algorithm, salt, pw_digest = hashed_passphrase.split(":", 2)
except (ValueError, TypeError):
return False
try:
h = hashlib.new(algorithm)
except ValueError:
return False
if len(pw_digest) == 0:
return False
h.update(passphrase.encode("utf-8") + salt.encode("ascii"))
return h.hexdigest() == pw_digest
@contextmanager
def persist_config(config_file=None, mode=0o600):
"""Context manager that can be used to modify a config object
On exit of the context manager, the config will be written back to disk,
by default with user-only (600) permissions.
"""
if config_file is None:
config_file = os.path.join(jupyter_config_dir(), "jupyter_server_config.json")
os.makedirs(os.path.dirname(config_file), exist_ok=True)
loader = JSONFileConfigLoader(os.path.basename(config_file), os.path.dirname(config_file))
try:
config = loader.load_config()
except ConfigFileNotFound:
config = Config()
yield config
with open(config_file, "w", encoding="utf8") as f:
f.write(json.dumps(config, indent=2))
try:
os.chmod(config_file, mode)
except Exception:
tb = traceback.format_exc()
warnings.warn(
f"Failed to set permissions on {config_file}:\n{tb}", RuntimeWarning, stacklevel=2
)
def set_password(password=None, config_file=None):
"""Ask user for password, store it in JSON configuration file"""
hashed_password = passwd(password)
with persist_config(config_file) as config:
config.IdentityProvider.hashed_password = hashed_password
return hashed_password