📝 【GAS/LINE LIFF開発者必見】ハマりがちな HMAC署名検証エラー INVALID_SIGNATURE の原因と確実な解決策

gas

※プロモーションページが含まれる場合があります

Next.jsや外部からのwebアプリを活用したシステムをGoogle Apps Script (GAS)で構築している開発者にとって、セキュリティの要となるのがHMAC署名検証です。

特に、クライアント(Next.jsやNode.js)とサーバー(GAS)間でこの検証を行う際に発生しやすいのが、INVALID_SIGNATUREエラーです。

「シークレットキーは合っているはずなのに、なぜか署名が一致しない…」

この見えない壁を乗り越えるため、エラーの根本原因確実な解決策を解説します。かなりハマったので備忘録として記載します。


発生する現象と根本原因

INVALID_SIGNATUREエラーは、クライアントが生成した署名と、サーバーが同じデータとシークレットキーを使って再計算した署名が一致しないときに発生します。

今回のケースで最も強力な原因となったのは、以下の**「バイト列の不一致」**でした。

1. 🚨 エンコーディングの不一致(日本語・マルチバイト文字)

HMAC署名は、入力された文字列を特定のエンコーディング(文字コード)で**バイト列(データの塊)**に変換してから計算します。

  • クライアント側(Node.js/Next.js): 通常、デフォルトでUTF-8を使用します。
  • サーバー側(Google Apps Script/GAS): Utilities.computeHmacSha256Signature(string, string)のように文字列を直接渡すと、GASの実行環境が内部的に使用するデフォルトのエンコーディングが適用されます。このデフォルトが、特に日本語などのマルチバイト文字を含む場合、クライアント側のUTF-8と微妙に異なり、結果としてバイト列がズレてしまいます。

わずか1バイトでも異なると、HMAC-SHA256の計算結果は完全に別のものになります。

2. 💣 GASメソッドの引数型の不一致

GASで署名計算を強制的にUTF-8で行おうと、ベース文字列をバイト配列に変換した際に発生する特有のエラーです。

JavaScript

// 誤ったコード例 (GAS)
const baseBytes = Utilities.newBlob(baseString).getBytes();
const calc = Utilities.base64Encode(Utilities.computeHmacSha256Signature(baseBytes, secret)); 
// -> エラー: The parameters (number[],String) don't match...

GASのcomputeHmacSha256Signatureメソッドは、入力の**valueをバイト配列(number[])にする場合、key(シークレット)もバイト配列でなければならない**という厳密なルールがありました。


💡 確実な解決策:GASでエンコーディングを強制する

この問題を完全に解決するには、GAS側でベース文字列とシークレットキーの両方を明示的にUTF-8のバイト配列に変換し、両者が同じ型の引数としてHMAC計算関数に渡されるようにします。

🛠️ GAS verifySignature 関数の修正ポイント

以下の通りにコードを修正することで、両環境のエンコーディングの違いを吸収できます。

JavaScript

function verifySignature(lineId, name, ts, sig) {
    // ... 時刻チェックなどの処理 ...
    const secret = PropertiesService.getScriptProperties().getProperty('API_SHARED_SECRET');
    const baseString = `${lineId}\n${name}\n${ts}`;

    // 【POINT 1】ベース文字列をUTF-8バイト配列に変換
    // 'text/plain' MIMEタイプを使用することで、文字列をUTF-8としてバイト配列化
    const baseBytes = Utilities.newBlob(baseString, 'text/plain').getBytes(); 
    
    // 【POINT 2】シークレットキーもUTF-8バイト配列に変換
    // これにより (バイト配列, バイト配列) のシグネチャに合わせる
    const secretBytes = Utilities.newBlob(secret, 'text/plain').getBytes();

    // 【POINT 3】(バイト配列, バイト配列) の組み合わせで計算を実行
    const rawHmac = Utilities.computeHmacSha256Signature(baseBytes, secretBytes);
    
    // 結果をBase64でエンコードして比較
    const calc = Utilities.base64Encode(rawHmac); 
    
    if (calc !== sig) {
        throw new Error('INVALID_SIGNATURE');
    }
}

まとめ

INVALID_SIGNATUREエラーに遭遇した場合、まずはシークレットキーの完全一致を確認し、問題なければ上記の方法でGAS側のエンコーディングと引数型を修正してみてください。この対応により、日本語を含むデータでも安定した署名検証が可能になります。

AIがHMACやった方がいいですよっていうから、やってみたらかなりハマってしまったが、勉強になったのでヨシとする!

コメント

タイトルとURLをコピーしました