LINE BOT Webhooks の署名検証をC#で実装する

LINE BOT 開発では、ユーザーが送信したメッセージなどのイベントは、指定したURLにHTTPSのPOSTリクエストとして送信されますが、リクエストの送信元が間違いなくLINEからのものであることを確認する必要があります。

LINE API Referenceには以下の様に書かれています。

Signature validation

リクエストの送信元がLINEであることを確認するために署名検証を行わなくてはなりません。 各リクエストには X-Line-Signature ヘッダが付与されています。 X-Line-Signature ヘッダの値と、request body と Channel secret から計算した signature が同じものであることをリクエストごとに 必ず検証してください。

検証は以下の手順で行います。

  1. Channel secretを秘密鍵として、HMAC-SHA256アルゴリズムによりrequest bodyのダイジェスト値を得る。
  2. ダイジェスト値をBASE64エンコードした文字列が、request headerに付与されたsignatureと一致することを確認する。

今回は、これをC#で実装してみましょう。

必要なデータは以下の3つです。

  • リクエストのX-Line-Signature ヘッダに格納されている値
  • リクエストBODYの値
  • Bot アカウントに付与されるChannel Secret(Line DeveloppersのChannels>>Basic Informationで確認できます)

これらを引数にして検証を行う関数を作ります。

using System.Security.Cryptography;
using System.Text;

public bool VerifySignature(string xLineSignature, string requestBody, string channelSecret)
{
    try
    {
        //channnel secret と request body をByte配列に変換する
        var key = Encoding.UTF8.GetBytes(channelSecret);
        var body = Encoding.UTF8.GetBytes(requestBody);
        
        //channel secretをキーにしてHMAC-SHA256アルゴリズムでrequest bodyのダイジェスト値を得る
        using (HMACSHA256 hmac = new HMACSHA256(key))
        {
            var hash = hmac.ComputeHash(body, 0, body.Length);
            //ダイジェスト値をBASE64に変換
            var hash64 = Convert.ToBase64String(hash);
            //X-LINE-Signatureヘッダの値と一致すればOK!!
            return xLineSignature == hash64;
        }
    }
    catch
    {
        return false;
    }
}

これを、Azure Functions(HttpTrigger)で使用する例を以下に示します。

using System.Configuration;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    //Channel Secretの取得。Channel SecretはAzure ポータルでアプリケーション設定に登録しておきます。
    var channelSecret = ConfigurationManager.AppSettings["ChannelSecret"]);
    //Request Body の取得
    var contentJson = await req.Content.ReadAsStringAsync();
    //X-Line-Signatureヘッダ値の取得
    var xLineSignature = req.Headers.GetValues("X-Line-Signature").FirstOrDefault()
        
    if (string.IsNullOrEmpty(xLineSignature) ||
        !VerifySignature(xLineSignature, contentJson, channelSecret))
    {
        //検証に失敗
        return req.CreateResponse(HttpStatusCode.Forbidden, new { Message = "Signature validation faild." });
    }

    //BOTでやりたい処理...
}