Go と Ruby 間の相互運用性: RSA 署名を検証する
概要
良い一日!
Ruby の Sinatra アプリケーションがあります。このアプリケーションは、base64 でエンコードされたメッセージとメッセージの署名を期待し、公開キーを使用してそれを検証します。 Ruby 3.2 を実行し、openssl を使用しています。このアプリケーションは正常に動作しており、多くのユーザーが長年にわたって使用しています。現在、Go で新しいクライアントを作成しているので、メッセージの構造体を定義し、JSON にマーシャリングし、base64 でエンコードし、秘密キーを使用してハッシュし、署名しました。何も派手なことはありません。ただし、Ruby アプリケーションでの検証は常に失敗します。
誰かがヒントを共有してくれることを願っています。助けていただければ幸いです。
問題を再現するサンプル コードを次に示します。私はこれを M1 チップを搭載した Mac で実行しています (これは関係ないと思いますが、これが Apple マジックだとわかっても驚かないでしょう)。 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 エンコードの strict、raw、url オプションを使用してみました。また、Ruby でリクエストに署名し、Go で検証しようとしましたが、同じ結果でした。そしてアイデアも尽きました。
解決策
Go コードでは、Base 64 でエンコードする前に JSON ペイロードに署名していますが、Ruby 側で Base 64 でエンコードされたペイロードの署名を検証しようとしているようです。
Ruby コードを保持したいと仮定すると、生の JSON ではなく、Base 64 でエンコードされたペイロードに署名していることを確認する必要があります。
コードでは 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 側で検証される値が得られるはずです。