在 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 的理解说明了以下有效的请求参数:
id
- 来自 Yubico 的客户端 ID API
otp
- 来自 yubikey 的 YubiOTP 组件的 YubiOTP 值。
h
- 请求的 HMAC-SHA1 签名
timestamp
- empty 什么都不做,1
在服务器的回复中包含时间戳
nonce
- 16 到 40 个字符长的字符串,带有随机唯一数据。
sl
- 0 到 100 的值表示客户端要求的同步百分比,或字符串“fast”或“Secure”以使用服务器值;如果不存在的服务器决定
timeout
- 等待同步响应的秒数;让服务器决定是否缺席。
我总共尝试使用两个函数来尝试处理所有这些事情并生成 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)并执行取消引用然后正常解码。
我对 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 的理解说明了以下有效的请求参数:
id
- 来自 Yubico 的客户端 ID APIotp
- 来自 yubikey 的 YubiOTP 组件的 YubiOTP 值。h
- 请求的 HMAC-SHA1 签名timestamp
- empty 什么都不做,1
在服务器的回复中包含时间戳nonce
- 16 到 40 个字符长的字符串,带有随机唯一数据。sl
- 0 到 100 的值表示客户端要求的同步百分比,或字符串“fast”或“Secure”以使用服务器值;如果不存在的服务器决定timeout
- 等待同步响应的秒数;让服务器决定是否缺席。
我总共尝试使用两个函数来尝试处理所有这些事情并生成 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)并执行取消引用然后正常解码。