SSO 的哈希密钥 + 载荷

我正在尝试遵循官方 SSO 指南。我的服务器使用的是 Golang,我试图将 payload 加上 secret 的 HMAC-SHA256 哈希值与 sig 进行匹配,但得到的值不同。你们是如何进行哈希计算的?你们是否将 payload 拆解为原始未签名的内容并重新进行哈希?

你的代码是什么样的?

你是否对有效载荷进行了 Base64 编码?

参数顺序是否正确?不同的语言或库有不同的参数顺序,可能是 (key, message) 或 (message, key)。这总是让我犯错。

解析 payload 后发现,默认情况下它是 base64 编码的,但对该 base64 编码后的 payload 进行 256SHA 哈希计算的结果不正确。我使用的是原始帖子中的示例密钥和 URL。

以下代码产生的输出如下:

 payloadURL = bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGI=

sig = 2828aa29899722b35a2f191d34ef9b3ce695e0e6eeec47deb46d588d70c7cb56
payloadRaw = nonce=cb68251eefb5211e58c00ff1395f0c0b
payload256 = KCiqKYmXIrNaLxkdNO+bPOaV4Obu7EfetG1YjXDHy1Y=

我的代码:

testURL := "http://www.example.com/discourse/sso?sso=bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGI%3D%0A\u0026sig=2828aa29899722b35a2f191d34ef9b3ce695e0e6eeec47deb46d588d70c7cb56"

ssoSecret := "d836444a9e4084d5b224a60c208dce14"

// 从 URL 查询参数中获取 payload 和 sig
u, err := url.Parse(testURL)
if err != nil {
    log.Fatal(err)
}

q := u.Query()
payloadURL := strings.Trim(fmt.Sprint(q["sso"]), "=[]")
sig := strings.Trim(fmt.Sprint(q["sig"]), "[]")

fmt.Printf("payloadURL = %s\n", payloadURL)
fmt.Printf("sig = %s\n", sig)

// 从 base64 版本中获取原始 payload
payloadRaw, err := base64.StdEncoding.DecodeString(payloadURL)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("payloadRaw = %s\n", payloadRaw)

key := []byte(ssoSecret)
message := []byte(payloadURL)
hash := hmac.New(sha256.New, key)
hash.Write(message)
payload256 := base64.StdEncoding.EncodeToString(hash.Sum(nil))
if err != nil {
    log.Fatal(err)
}

fmt.Printf("payload256 = %s\n", payload256)

// sig 是十六进制编码的,因此将哈希后的 payload 也进行十六进制编码以便比较。
//hashAsString := hex.EncodeToString(hash.Sum(nil))

// 将 payload 的十六进制编码哈希值与十六进制编码的 sig 进行比较
if strings.Compare(payload256, sig) != 0 {
    log.Fatal(err)
}

更新:我在将生成的 HMAC-SHA256 哈希值编码为十六进制后修复了此问题(签名也是十六进制编码的)。另请参阅 这篇帖子,其中指出 SSO 指南中的示例有误。因此,我修改了以下代码:

key := []byte(ssoSecret)
message := []byte(payloadURL)
hash := hmac.New(sha256.New, key)
hash.Write(message)
payload256 := hex.EncodeToString(hash.Sum(nil))
if err != nil {
    log.Fatal(err)
}

payload256 是从哪里来的?

抱歉,我已更新原帖,代码更简洁了。

那个 %0A\u0026sig 之前是做什么的?那是 URL 编码的换行符……这就是为什么你在打印 payloadURL 后,调试输出中会出现一个空行,因为它仍然被附加在末尾。

好眼力!我通过向 .Trim() 参数添加换行符来修复了这里的问题:

payloadURL := strings.Trim(fmt.Sprint(q["sso"]), "[]\n")

现在我的输出是:

payloadURL = bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGI=
sig = 2828aa29899722b35a2f191d34ef9b3ce695e0e6eeec47deb46d588d70c7cb56
payloadRaw = nonce=cb68251eefb5211e58c00ff1395f0c0b
payload256 = HOFJT5RIS29qCSvpsVzMHNr7H4Rgo4OPuw4Ig8Q5BHE=

你应该查看换行符究竟是从哪里来的,并避免其被添加。

此外,负载中是否应该包含比 nonce 更多的 name=value 对?

这仅仅是 Discourse SSO 指南中的示例内容,因此我无法确定换行符的来源。显然,该指南有误(根据这篇帖子),看起来示例中存在复制/粘贴错误。

也许是从不同版本中复制粘贴的内容有点多了。

但看看你上面那篇帖子,你的 payloadURL 没有进行 URL 编码,而 payload256 也不是十六进制编码,而是 Base64 编码。

或许你可以重新发布一下你当前的代码和当前的输出。