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やった方がいいですよっていうから、やってみたらかなりハマってしまったが、勉強になったのでヨシとする!
コメント