2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【C#】サーバー側でAndroidのレシートを署名検証する

Last updated at Posted at 2024-04-06

C#(.NET)での実装が調べてもあまり出てこないので残しておきます。

コピペで使う場合でも必ずドキュメントを読んだ上で使用してください。
課金に関わることなので重大な問題が発生する可能性もあります。
発生する問題については一切の責任を負いかねます。

はじめに

Androidのレシート検証は2つあります。

  1. 自分のアプリのレシートか検証(ローカルでの署名検証)
  2. Googleのサーバーにレシート情報があるかを検証(APIでの検証)

どちらも行うことが推奨されています。(どちらかしかやってない人が多いのではないでしょうか)
今回は1. 自分のアプリのレシートか検証の部分を書きます。

環境

.NET8

用意するもの

サーバー側で実装しているとレシートの現物が手に入らない、そもそも何を使用するのかというところで最初躓く可能性があるのでまとめておきます。

サーバー側 : 公開鍵
クライアント側 : 発行されたレシートと署名

コード

    public bool Verify(string receiptJson, string publicKey, string signature)
    {
        // 署名
        var sha1Managed = SHA1.Create();
        var hash = sha1Managed.ComputeHash(RemoveNewLinesAndSpaces(receiptJson));

        var rsa = new RSACryptoServiceProvider();
        rsa.ImportSubjectPublicKeyInfo(ConvertPublicKey(publicKey), out _);

        // 署名を検証する
        return rsa.VerifyHash(hash,
            Convert.FromBase64String(signature),
            HashAlgorithmName.SHA1,
            RSASignaturePadding.Pkcs1);
    }

    private static byte[] RemoveNewLinesAndSpaces(string receiptJson)
    {
        return Encoding.UTF8.GetBytes(receiptJson
            .Replace("\n", "")
            .Replace("\r", "")
            .Replace(" ", ""));
    }

    private static byte[] ConvertPublicKey(string publicKey)
    {
        var cleanedKey = publicKey
            .Replace("-----BEGIN PUBLIC KEY-----", "")
            .Replace("-----END PUBLIC KEY-----", "")
            .Replace("\n", "")
            .Replace("\r", "");
    
        return Convert.FromBase64String(cleanedKey);
    }

解説

クライアントから送られてくるレシート(json)の改行コードとスペースを取り除いて、SHA1でハッシュ化します。

var sha1Managed = SHA1.Create();
var hash = sha1Managed.ComputeHash(RemoveNewLinesAndSpaces(receiptJson));

公開鍵をRSACryptoServiceProvider()のImportSubjectPublicKeyInfo()を使用して読み込ませます。

var rsa = new RSACryptoServiceProvider();
rsa.ImportSubjectPublicKeyInfo(ConvertPublicKey(publicKey), out _);

この際、公開鍵の接頭語と接尾語、及び改行コードは取り除かれている必要があります。
ですので、以下のように余分な部分をバリデーションしてあげないといけません。

    private static byte[] ConvertPublicKey(string publicKey)
    {
        var cleanedKey = publicKey
            .Replace("-----BEGIN PUBLIC KEY-----", "")
            .Replace("-----END PUBLIC KEY-----", "")
            .Replace("\n", "")
            .Replace("\r", "");
    
        return Convert.FromBase64String(cleanedKey);
    }

さらにC#のデフォルトのbase64エンコードは4の倍数文字数ではないとエンコードしてくれないので、もっとかっちり作るなら以下の記事のような処理も必要です。

最後にVerifyHash()で署名検証してあげて終わりです。
第1引数から

  1. SHA1でハッシュ化したレシート
  2. クライアントから送られてきた署名をbase64エンコードしたバイト配列
  3. 検証に使用するアルゴリズム(AndroidではSHA1)
  4. 検証で使用するパディングモード(AndroidではPkcs1)
    署名検証が成功すればtrueが返ります。
return rsa.VerifyHash(hash,
    Convert.FromBase64String(signature),
    HashAlgorithmName.SHA1,
    RSASignaturePadding.Pkcs1);

感想

他言語では上述のような色々細かいことしなくてもできている印象。
C#だけドキュメントもなければ、使い方も難しい。
私はレシートのjsonのスペースを取り除き忘れていて1日無駄にしました。

2
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?