You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

175 lines
3.7 KiB
Python

# SPDX-License-Identifier: MIT
from __future__ import annotations
import platform
import sys
from dataclasses import dataclass
from typing import Any
from .exceptions import InvalidHashError, UnsupportedParametersError
from .low_level import Type
NoneType = type(None)
def _check_types(**kw: Any) -> str | None:
"""
Check each ``name: (value, types)`` in *kw*.
Returns a human-readable string of all violations or `None``.
"""
errors = []
for name, (value, types) in kw.items():
if not isinstance(value, types):
if isinstance(types, tuple):
types = ", or ".join(t.__name__ for t in types)
else:
types = types.__name__
errors.append(
f"'{name}' must be a {types} (got {type(value).__name__})"
)
if errors != []:
return ", ".join(errors) + "."
return None
def _is_wasm() -> bool:
return sys.platform == "emscripten" or platform.machine() in [
"wasm32",
"wasm64",
]
def _decoded_str_len(length: int) -> int:
"""
Compute how long an encoded string of length *l* becomes.
"""
rem = length % 4
if rem == 3:
last_group_len = 2
elif rem == 2:
last_group_len = 1
else:
last_group_len = 0
return length // 4 * 3 + last_group_len
@dataclass
class Parameters:
"""
Argon2 hash parameters.
See :doc:`parameters` on how to pick them.
Attributes:
type: Hash type.
version: Argon2 version.
salt_len: Length of the salt in bytes.
hash_len: Length of the hash in bytes.
time_cost: Time cost in iterations.
memory_cost: Memory cost in kibibytes.
parallelism: Number of parallel threads.
.. versionadded:: 18.2.0
"""
type: Type
version: int
salt_len: int
hash_len: int
time_cost: int
memory_cost: int
parallelism: int
__slots__ = (
"hash_len",
"memory_cost",
"parallelism",
"salt_len",
"time_cost",
"type",
"version",
)
_NAME_TO_TYPE = {"argon2id": Type.ID, "argon2i": Type.I, "argon2d": Type.D}
_REQUIRED_KEYS = sorted(("v", "m", "t", "p"))
def extract_parameters(hash: str) -> Parameters:
"""
Extract parameters from an encoded *hash*.
Args:
hash: An encoded Argon2 hash string.
Returns:
The parameters used to create the hash.
.. versionadded:: 18.2.0
"""
parts = hash.split("$")
# Backwards compatibility for Argon v1.2 hashes
if len(parts) == 5:
parts.insert(2, "v=18")
if len(parts) != 6:
raise InvalidHashError
if parts[0]:
raise InvalidHashError
try:
type = _NAME_TO_TYPE[parts[1]]
kvs = {
k: int(v)
for k, v in (
s.split("=") for s in [parts[2], *parts[3].split(",")]
)
}
except Exception: # noqa: BLE001
raise InvalidHashError from None
if sorted(kvs.keys()) != _REQUIRED_KEYS:
raise InvalidHashError
return Parameters(
type=type,
salt_len=_decoded_str_len(len(parts[4])),
hash_len=_decoded_str_len(len(parts[5])),
version=kvs["v"],
time_cost=kvs["t"],
memory_cost=kvs["m"],
parallelism=kvs["p"],
)
def validate_params_for_platform(params: Parameters) -> None:
"""
Validate *params* against current platform.
Args:
params: Parameters to be validated
Returns:
None
"""
if _is_wasm() and params.parallelism != 1:
msg = "In WebAssembly environments `parallelism` must be 1."
raise UnsupportedParametersError(msg)