← 中文资源

简体中文版本

Secrets 安全令牌生成

用 secrets 模块生成密码学安全的令牌:API key 按字母表取样、URL 令牌免转义、验证码用 randbelow,比较必须恒时。

阅读英文原文

示例说明

random 模块的伪随机可被预测,安全令牌必须用 secrets——它调用操作系统的密码学随机源。三类令牌各有形态:API key 用受控字母表拼出可人读的带前缀键;token_urlsafe 输出 URL 免转义的紧凑串;验证码用 randbelow 限定位数。比较环节的恒时比较防时序侧信道,普通等号比较会在首个不匹配字符处提前返回。

技术要点

  • secrets 基于操作系统密码学随机源
  • compare_digest 恒时比较防时序攻击
  • 令牌附带作用域提示便于审计

代码解读

api_key 用 choice 按字母表取四十个字符并加业务前缀;otp_code 以 randbelow 生成定长数字码;constant_time_match 先比长度再用 compare_digest 比较字节。scoped_token 把令牌与作用域绑定返回结构化结果,作用域泄漏时影响面可控。

使用建议

令牌只显示一次,存储侧只存哈希。前缀便于泄漏扫描工具识别。验证码的有效期与尝试次数限制要在服务端配套。

代码示例

import hmac
import secrets

ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'


def api_key(prefix: str = 'sk') -> str:
    body = ''.join(secrets.choice(ALPHABET) for _ in range(40))
    return f'{prefix}_{body}'


def url_token(nbytes: int = 32) -> str:
    return secrets.token_urlsafe(nbytes)


def otp_code(digits: int = 6) -> str:
    # 朴素做法 random.randint 可预测,必须用 secrets
    floor = 10 ** (digits - 1)
    return str(floor + secrets.randbelow(9 * floor))


def constant_time_match(candidate: str, expected: str) -> bool:
    if len(candidate) != len(expected):
        return False
    return hmac.compare_digest(
        candidate.encode(), expected.encode()
    )


def scoped_token(scope: str, nbytes: int = 16) -> dict:
    return {
        'token': url_token(nbytes),
        'scope': scope,
        'hint': f'scope={scope}',
    }


key = api_key()
code = otp_code()
print('key length:', len(key), 'otp:', code)
print('compare ok:', constant_time_match(code, code))
print('compare tampered:', constant_time_match(code, '123456'))