You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在不存储全部密码参数的前提下验证用户密码?

密码哈希参数的存储方案:合并字符串或替代方案

当然可以将这些密码哈希相关的参数(salt、哈希值、scrypt的n/r/p等)合并为单个字符串存储,这也是行业内的常规做法,同时还有更省心的现成方案可选,下面具体说明:

方案一:手动合并为单个字符串

你需要把验证密码时必须的所有参数都编码为字符串,用一个不会冲突的分隔符拼接起来,存储到数据库的单个字段中。验证时再拆分解析这些参数,重新计算哈希并比对。

具体实现(基于你提供的代码)

import hashlib
import hmac

# 保留你原有的hashpw和checkpw函数
def hashpw(
    password: str,
    encoding: str,
    salt: bytes,
    n: int,
    r: int,
    p: int,
    maxmem: int = 32 * 1024 * 1024,  # 32 MiB
    dklen: int = 64,
) -> bytes:

    if len(password) > 1024:
        raise ValueError(
            "Password cannot be longer than 1024 characters",
        )

    encoded = password.encode(encoding)
    if len(encoded) > 1024:
        raise ValueError(
            "Password cannot be longer than 1024 bytes when encoded",
        )

    return hashlib.scrypt(encoded, salt=salt, n=n, r=r, p=p, maxmem=maxmem, dklen=dklen)


def checkpw(
    password: str,
    encoding: str,
    salt: bytes,
    hashed_password: bytes,
    n: int,
    r: int,
    p: int,
    maxmem: int = 32 * 1024 * 1024,
    dklen: int = 64,
) -> bool:
    candidate = hashpw(password, encoding, salt, n, r, p, maxmem, dklen)
    return hmac.compare_digest(candidate, hashed_password)

# 新增生成可存储字符串的函数
def generate_storable_hash(
    password: str,
    encoding: str = "utf-8",
    n: int = 16384,
    r: int = 8,
    p: int = 1,
    maxmem: int = 32 * 1024 * 1024,
    dklen: int = 64
) -> str:
    # 生成每个用户唯一的随机salt(必须,不能共用)
    salt = hashlib.random_bytes(16)
    hashed = hashpw(password, encoding, salt, n, r, p, maxmem, dklen)
    # 用$作为分隔符,拼接所有必要参数(hex编码二进制数据避免乱码)
    parts = [
        "scrypt",          # 标识使用的哈希算法,方便后续扩展
        encoding,
        str(n),
        str(r),
        str(p),
        str(dklen),
        salt.hex(),
        hashed.hex()
    ]
    return "$".join(parts)

# 新增解析存储字符串的函数
def parse_storable_hash(stored_str: str) -> tuple:
    parts = stored_str.split("$")
    if parts[0] != "scrypt":
        raise ValueError("不支持的哈希算法")
    encoding = parts[1]
    n = int(parts[2])
    r = int(parts[3])
    p = int(parts[4])
    dklen = int(parts[5])
    salt = bytes.fromhex(parts[6])
    hashed_password = bytes.fromhex(parts[7])
    return encoding, n, r, p, dklen, salt, hashed_password

# 新增从存储字符串验证密码的函数
def checkpw_from_stored(
    password: str,
    stored_str: str,
    maxmem: int = 32 * 1024 * 1024
) -> bool:
    encoding, n, r, p, dklen, salt, hashed = parse_storable_hash(stored_str)
    return checkpw(password, encoding, salt, hashed, n, r, p, maxmem, dklen)

关键注意事项

  • 分隔符选择:要选不会出现在编码后内容里的字符,比如用hex编码二进制数据时,字符范围是0-9a-f,用$或|这类分隔符不会冲突。
  • salt必须唯一:每个用户的salt都要随机生成,不能共用,否则会大幅降低哈希的安全性。
  • 参数完整性:必须包含验证时需要的所有参数(n/r/p/dklen/encoding等),少一个都无法正确验证密码。

方案二:使用成熟的密码哈希库(更推荐)

手动实现容易遗漏细节(比如salt生成的随机性、参数的兼容性处理),更推荐用现成的库,比如passlib,它已经封装了哈希参数的存储、salt生成、算法兼容等所有细节,代码更简洁,安全性更有保障。

示例代码

from passlib.context import CryptContext

# 初始化密码上下文,指定使用scrypt算法
pwd_context = CryptContext(schemes=["scrypt"], deprecated="auto")

# 生成可直接存储的哈希字符串
def generate_hash(password: str) -> str:
    return pwd_context.hash(password)

# 验证密码与存储的哈希字符串
def verify_password(password: str, stored_hash: str) -> bool:
    return pwd_context.verify(password, stored_hash)

使用时只需要调用generate_hash得到字符串存入数据库,验证时调用verify_password传入密码和存储的字符串即可,完全不用自己处理salt、参数拼接等问题。


内容的提问来源于stack exchange,提问作者Lorang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 00:43:13