在 Python 中生成 YubiOTP 验证 HMAC-SHA-1 签名

Generate YubiOTP verification HMAC-SHA-1 signatures in Python

我对 Python 需要做的事情有点困惑,但是从 Yubikey API 文档中验证具有 YubiOTP 的 Yubikeys 需要生成 HMAC 签名具体方式——来自他们的 documentation:

Generating signatures

The protocol uses HMAC-SHA-1 signatures. The HMAC key to use is the client API key.

Generate the signature over the parameters in the message. Each message contains a set of key/value pairs, and the signature is always over the entire set (excluding the signature itself), and sorted in alphabetical order of the keys. More precisely, to generate a message signature do:

  • Alphabetically sort the set of key/value pairs by key order.

  • Construct a single line with each ordered key/value pair concatenated using &, and each key and value contatenated with =. Do not add any linebreaks. Do not add whitespace. For example: a=2&b=1&c=3.

  • Apply the HMAC-SHA-1 algorithm on the line as an octet string using the API key as key (remember to base64decode the API key obtained from Yubico).

  • Base 64 encode the resulting value according to RFC 4648, for example, t2ZMtKeValdA+H0jVpj3LIichn4=.

  • Append the value under key h to the message.

现在我从他们的文档中对他们的 API 的理解说明了以下有效的请求参数:

我总共尝试使用两个函数来尝试处理所有这些事情并生成 URL。即,我们的 HMAC 支持函数和生成 URL 的 verify_url_generate(并且 API_KEY 是静态编码的 - 我来自 Yubico 的 API 密钥):

def generate_signature(message, key=base64.b64decode(API_KEY)):
    message = bytes(message, 'UTF-8')

    digester = hmac.new(key, message, hashlib.sha1)
    digest = digester.digest()

    signature = base64.urlsafe_b64encode(digest)

    return str(signature, 'UTF-8')


def verify_url_generate(otp):
    nonce = "".join(secrets.choice(ascii_lowercase) for _ in range(40))

    data = OrderedDict(
        {
            "id": None,
            "nonce": None,
            "otp": None,
            "sl": 50,
            "timeout": 10,
            "timestamp": 1
        }
    )

    data['otp'] = otp
    data['id'] = CLIENT_ID
    data['nonce'] = nonce

    args = ""

    for key, value in data.items():
        args += f"{key}={value}&"

    sig = generate_signature(args[:-1])

    url = YUBICO_API_URL + args + "&h=" + sig

    print(url)
    return

由此生成的任何 URL 都会触发来自远程站点的关于“BAD_SIGNATURE”的通知 - 生成的任何 URL 减去 HMAC sig (h=) 参数都有效.所以我们知道问题不在于 URL,而是 HMAC 签名。

有谁知道我的 HMAC 生成方法做错了什么,通过将 key=value 中由 & 分隔的有序字典中的串联参数传递给 HMAC sig 生成器,每个参数格式?

你能否尝试使用 standard_b64encode,然后在你的最终 URL 中使用 urllib.parse.quote(url)?

我问是因为 this page 说“因此,所有参数都必须正确 URL 编码。特别是,值字段中的某些 base64 字符(例如“+”)需要被逃脱。”这意味着它在 args 中期待 +(或 %2B)并执行取消引用然后正常解码。