美好的一天!
我有一个 ruby 中的 sinatra 应用程序,它需要一条以 base64 编码的消息和消息签名,然后使用公钥来验证它。我们正在运行 ruby 3.2 并使用 openssl。该应用程序运行良好,许多用户已经使用它多年。现在我正在用 Go 编写一个新客户端,因此我为消息定义了一个结构,将其编组为 JSON,以 Base64 进行编码,使用我的私钥对其进行哈希处理和签名。没有什么花哨。但 ruby 应用程序中的验证总是失败。
希望有人可以分享任何提示,非常感谢任何帮助。
这里有一些重现问题的示例代码。我在带有 M1 芯片的 Mac 上运行这个程序(不认为这有什么关系,但如果发现它是苹果的魔法在起作用,也不会感到惊讶)。 Go 版本 1.22.2 和 Ruby 3.2.3。
红宝石部分:
def validate(public_key, message, signature)
p_key = OpenSSL::PKey::RSA.new(public_key)
return p_key.verify('sha256', Base64.strict_decode64(signature), message)
end
其中
public_key
是带有pem编码公钥的字符串,message
是base64编码的有效负载,signature
是签名。
去部分:
func Test_RequestSignature(t *testing.T) {
const pemKey = `-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
`
data := []byte(pemKey)
block, _ := pem.Decode(data)
key, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
rq := request{
CreatedAt: time.Now().Unix(),
AppId: 1,
Message: "something we'd like to sign of course",
Uuid: "030D842B-700D-41F6-BFB6-5CB10ADCA4EF",
}
encoded := rq.ToBase64()
signature := rq.Signature(key)
// Print stuff so I can copy and paste in my ruby snippet
fmt.Println("message: ", encoded)
fmt.Println("signature: ", signature)
// Validate here, as sanity check
publicKey := key.PublicKey
decoded, _ := base64.StdEncoding.Strict().DecodeString(encoded)
hashed := sha256.Sum256(decoded)
sig, _ := base64.StdEncoding.Strict().DecodeString(signature)
err := rsa.VerifyPKCS1v15(&publicKey, crypto.SHA256, hashed[:], sig)
assert.Nil(t, err)
}
type request struct {
CreatedAt int64 `json:"created_at"`
AppId int `json:"app_id"`
Message string `json:"message"`
Uuid string `json:"uuid"`
}
func (r request) ToBase64() string {
data, err := json.Marshal(&r)
if err != nil {
panic(err)
}
return base64.StdEncoding.Strict().EncodeToString(data)
}
func (r request) Signature(key *rsa.PrivateKey) string {
data, err := json.Marshal(&r)
if err != nil {
panic(err)
}
hashed := sha256.Sum256(data)
signature, err := rsa.SignPKCS1v15(nil, key, crypto.SHA256, hashed[:])
if err != nil {
panic(err)
}
return base64.StdEncoding.Strict().EncodeToString(signature)
}
Go 中的测试顺利通过,但在 ruby 代码片段中使用相同的密钥、消息和签名进行验证总是返回 false。我已经检查了两个应用程序上的公钥是否匹配,我已经使用了严格的、原始的、base64 编码的 url 选项。我还尝试在 Ruby 中签署请求并在 Go 中验证,结果相同。我已经没有主意了。
在你的Go代码中,你正在对json有效负载进行签名之前你对它进行base 64编码,但看起来你正在尝试在Ruby端验证base 64编码有效负载上的签名。
假设 Ruby 代码是您想要保留的内容,您需要确保签署的是 Base 64 编码的有效负载,而不是原始 json。
您的代码还会重复 json 编组,因此最简单的解决方案可能是将
Signature
和 ToBase64
合并为:
func (r request) EncodeAndSign(key *rsa.PrivateKey) (string, string) {
data, err := json.Marshal(&r)
if err != nil {
panic(err)
}
// Base 64 encode the data before signing.
data_b64 := base64.StdEncoding.Strict().EncodeToString(data)
hashed := sha256.Sum256([]byte(data_b64))
signature, err := rsa.SignPKCS1v15(nil, key, crypto.SHA256, hashed[:])
if err != nil {
panic(err)
}
signature_b64 := base64.StdEncoding.Strict().EncodeToString(signature)
// Return both the base 64 encoded json data and the base 64 encoded
// signature.
return data_b64, signature_b64
}
那就改变吧
encoded := rq.ToBase64()
signature := rq.Signature(key)
到
encoded, signature := rq.EncodeAndSign(key)
这应该会给你在 Ruby 端验证的值。