首页 > 解决方案 > 在 Python 中生成 YubiOTP 验证 HMAC-SHA-1 签名

问题描述

我对我需要在这里为 Python 做的事情有点困惑,但是从用于验证具有 YubiOTP 的 Yubikey 的 Yubikey API 文档中,需要以特定的方式生成 HMAC 签名 - 从他们的文档中:

生成签名

该协议使用 HMAC-SHA-1 签名。要使用的 HMAC 密钥是客户端 API 密钥。

在消息中的参数上生成签名。每条消息都包含一组键/值对,并且签名始终覆盖整个集合(不包括签名本身),并按键的字母顺序排序。更准确地说,要生成消息签名,请执行以下操作:

  • 按键顺序按字母顺序对键/值对集进行排序。

  • 构造一行,每个有序键/值对使用 & 连接,每个键和值使用 连接=。不要添加任何换行符。不要添加空格。例如: a=2&b=1&c=3

  • 使用 API 密钥作为密钥,将 HMAC-SHA-1 算法作为八位字节字符串应用在线路上(记得对从 Yubico 获得的 API 密钥进行 base64decode)。

  • Base 64 根据 RFC 4648 对结果值进行编码,例如t2ZMtKeValdA+H0jVpj3LIichn4=.

  • 将键 h 下的值附加到消息中。

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

我总共尝试使用两个函数来尝试处理所有这些事情并生成 URL。即,我们支持 HMAC 功能并verify_url_generate生成 URL(并且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 生成方法做错了什么,通过将 HMAC sig 生成器从有序 dict 中的串联参数传递key=value&每个参数格式?

标签: pythonpython-3.xhmacsha1yubico

解决方案


您可以尝试使用 standard_b64encode,然后在最终 URL 中使用 urllib.parse.quote(url) 吗?

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


推荐阅读