質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.37%
Google Apps Script

Google Apps ScriptはGoogleの製品と第三者のサービスでタスクを自動化するためのJavaScriptのクラウドのスクリプト言語です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

3337閲覧

Pythonで書かれたLINE Ads APIのサンプルコードをGAS(JavaScript)に翻訳したい

退会済みユーザー

退会済みユーザー

総合スコア0

Google Apps Script

Google Apps ScriptはGoogleの製品と第三者のサービスでタスクを自動化するためのJavaScriptのクラウドのスクリプト言語です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2022/09/12 13:27

解決したいこと

LINE広告APIドキュメントにてPythonで書かれているサンプルコードを、GAS(JavaScript)で書き直して使いたい

開発の背景

  • LINE広告のパフォーマンスレポートをスプレッドシートに自動で書き出したい
  • 他プラットフォーム(GoogleやYahoo!)の広告データ抽出も行っていることもあり、GASで書きたい

発生しているエラー

{"errors":[{"reason":"Unauthorized","message":"JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted."}]}

「JWT署名がローカルで計算された署名と一致しません。 JWTが有効である確証がないため、信頼すべきではありません」とのこと。

今動かしているソースコード

ドキュメントのPythonのコードを見て、自力でGASに翻訳してみたものが以下です。
サンプルコードは GET /v3/groups/{groupId}/children を使っていたので、 GET /v3/adaccounts/{adaccountId}/campaigns に変えています。

JavaScript

1function importAdsReport() { 2 const today = new Date(); 3 const access_key = "xxxxxxxxxxxx"; 4 const secretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; 5 const accountId = "xxxxxxxxxxx"; 6 7 const header = `{"alg": "HS256", "kid": "${access_key}", "typ": "text/plain"}`; 8 9 const hex_digest = sha256(""); 10 const content_type = ""; 11 const payload_date = Utilities.formatDate(today, 'GMT', 'yyyyMMdd'); 12 const canonical_url = `/api/v3/adaccounts/${accountId}/campaigns`; 13 const payload = `${hex_digest}\n${content_type}\n${payload_date}\n${canonical_url}`; 14 15 const inputValue = `${base64(header)}.${base64(payload)}`; 16 const signature = hmacSha256(inputValue, secretKey); 17 const calculatedSignature = `${inputValue}.${signature}`; 18 19 const request_headers = { 20 "Date": Utilities.formatDate(today, 'GMT', 'E, dd MMM yyyy HH:mm:ss z'), 21 "Authorization": `Bearer ${calculatedSignature}` 22 } 23 24 const options = { 25 "method": "GET", 26 "headers": request_headers, 27 "muteHttpExceptions": true 28 }; 29 30 const response = UrlFetchApp.fetch('https://ads.line.me' + canonical_url, options); 31} 32 33/** 34 * base64エンコード 35 * @param {string} input - エンコードしたい文字列 36 * @return {string} - base64エンコードされた文字列 37 */ 38function base64(input) { 39 return Utilities.base64Encode(input, Utilities.Charset.UTF_8) 40} 41 42/** 43 * HMAC SHA 256でハッシュ化 44 * @param {string} text - ハッシュ化する文字列 45 * @param {string} key - ハッシュ化するシークレットキー 46 * @return {string} - ハッシュ値 47 */ 48function hmacSha256(text, key) { 49 const rowHash = Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_256, text, key); 50 let txtHash = ''; 51 for (i = 0; i < rowHash.length; i++) { 52 let hashVal = rowHash[i]; 53 if (hashVal < 0) { 54 hashVal += 256; 55 } 56 if (hashVal.toString(16).length == 1) { 57 txtHash += '0'; 58 } 59 txtHash += hashVal.toString(16); 60 } 61 return txtHash; 62}; 63 64/** 65 * SHA 256でハッシュ化 66 * @param {string} input - ハッシュ化する文字列 67 * @return {string} - ハッシュ値 68 */ 69function sha256(input) { 70 const rawHash = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, input, Utilities.Charset.UTF_8); 71 let txtHash = ''; 72 for (i = 0; i < rawHash.length; i++) { 73 let hashVal = rawHash[i]; 74 if (hashVal < 0) { 75 hashVal += 256; 76 } 77 if (hashVal.toString(16).length == 1) { 78 txtHash += '0'; 79 } 80 txtHash += hashVal.toString(16); 81 } 82 return txtHash 83}

エラーの原因っぽいところ

signature が間違っているんだろうと思っています。
HTTPリクエストに渡す calculatedSignatureinputValuesignatureから成っているのですが、 inputValue はPythonで生成したものと一致していました。

Pythonのサンプルコードで

Python

1signature = hmac.new( 2 secret_key.encode(), 3 signing_input.encode(), 4 hashlib.sha256 5 ).digest()

のように書かれているところを、GASで

JavaScript

1const signature = hmacSha256(inputValue, secretKey);

としているのですが、このhmacSha256()がよくないのかなと...。

今試していること

「要するにHS256形式での電子署名生成をGAS(JavaScript)でできればいいのでは」と思い、それらしき記事を見てみたりしているのですが、結局同じエラーが返ってきてしまっています…。

誤っている箇所がお分かりになる方いらっしゃいましたら、ご指摘いただけるととてもありがたいです…。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

下記ではいかがでしょうか。

js

1function importAdsReport() { 2 const today = new Date(); 3 const access_key = "xxxxxxxxxxxx"; 4 const secretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; 5 const accountId = "xxxxxxxxxxx"; 6 7 const header = `{"alg":"HS256","kid":"${access_key}","typ":"text/plain"}`; 8 9 const hex_digest = sha256(""); 10 const content_type = ""; 11 const payload_date = Utilities.formatDate(today, 'GMT', 'yyyyMMdd'); 12 const canonical_url = `/api/v3/adaccounts/${accountId}/campaigns`; 13 const payload = `${hex_digest}\n${content_type}\n${payload_date}\n${canonical_url}`; 14 15 const inputValue = `${base64(header)}.${base64(payload)}`; 16 const signature = hmacSha256(inputValue, secretKey); 17 const calculatedSignature = `${inputValue}.${signature}`; 18 19 const request_headers = { 20 "Date": Utilities.formatDate(today, 'GMT', 'E, dd MMM yyyy HH:mm:ss z'), 21 "Authorization": `Bearer ${calculatedSignature}` 22 } 23 24 const options = { 25 "method": "GET", 26 "headers": request_headers, 27 "muteHttpExceptions": true 28 }; 29 30 const response = UrlFetchApp.fetch('https://ads.line.me' + canonical_url, options); 31} 32 33/** 34 * base64エンコード 35 * @param {string} input - エンコードしたい文字列 36 * @return {string} - base64エンコードされた文字列 37 */ 38function base64(input) { 39 return Utilities.base64Encode(input, Utilities.Charset.UTF_8) 40} 41 42/** 43 * HMAC SHA 256でハッシュ化 44 * @param {string} text - ハッシュ化する文字列 45 * @param {string} key - ハッシュ化するシークレットキー 46 * @return {string} - ハッシュ値 47 */ 48function hmacSha256(text, key) { 49 const rowHash = Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_256, text, key); 50 return Utilities.base64Encode(rowHash); 51}; 52 53/** 54 * SHA 256でハッシュ化 55 * @param {string} input - ハッシュ化する文字列 56 * @return {string} - ハッシュ値 57 */ 58function sha256(input) { 59 const rawHash = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, input, Utilities.Charset.UTF_8); 60 let txtHash = ''; 61 for (i = 0; i < rawHash.length; i++) { 62 let hashVal = rawHash[i]; 63 if (hashVal < 0) { 64 hashVal += 256; 65 } 66 if (hashVal.toString(16).length == 1) { 67 txtHash += '0'; 68 } 69 txtHash += hashVal.toString(16); 70 } 71 return txtHash 72}

変えたところ

  • header の半角スペースを削除(こちらは関係ないかもしれません。ただドキュメントのサンプルは、JSON文字列の半角スペースが削除された文字列をBASE64エンコードしたものになっていました。)

diff

1- const header = `{"alg": "HS256", "kid": "${access_key}", "typ": "text/plain"}`; 2+ const header = `{"alg":"HS256","kid":"${access_key}","typ":"text/plain"}`;

 

  • hmacSha256 関数 → バイト列をBASE64エンコードしたものを返すように修正。

こちらは、途中途中で GAS のデバッグ出力と、サンプルの python コードでのデバッグ出力とを比較して検証してみました。

python

1 signature = hmac.new( 2 secret_key.encode(), 3 signing_input.encode(), 4 hashlib.sha256 5 ).digest() # ---(1) 6 encoded_signature = encode_with_base64(signature) # ---(2) 7 token = "%s.%s.%s" % (jws_header, jws_payload, encoded_signature) # ---(3) 8

(1) は Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_256, text, key); の結果に一致しています。
また(3)は、質問者さんのGASコードを最初から読んでいくと、

js

1 const calculatedSignature = `${inputValue}.${signature}`;

に該当しています。

inputValue は 前2つ(jws_header, jws_payload)をピリオドで連結した文字列に一致しています。

したがって calculatedSignature の後ろ部分に連結している signature は、pythonサンプルコードの encoded_signature [=上記 (2) の行] に該当することになるので、
上記のように、

js

1function hmacSha256(text, key) { 2 const rowHash = Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_256, text, key); 3 return Utilities.base64Encode(rowHash); 4};

というコードにすればよいのではないでしょうか。

つまり元の GAS コードでは、HMAC_SHA_256 で得られたバイト列を Hex 文字列に変えていますが、そうではなくて、得られたバイト列を BASE64 エンコードするのが正解かと思います。

投稿2022/09/12 13:54

編集2022/09/13 13:49
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2022/09/12 14:34 編集

ご回答いただきありがとうございます...! 早速試してみましたところ、以下のようなエラーが返ってきています。 Exception: The parameters (Utilities.MacAlgorithm,String,number[]) don't match the method signature for Utilities.computeHmacSignature. 第三引数で使っているsecretKeyの形式がよくないのでしょうか...?
退会済みユーザー

退会済みユーザー

2022/09/13 12:38

修正しました。
退会済みユーザー

退会済みユーザー

2022/09/14 00:11 編集

ご指摘いただいた箇所を修正し、正しく動きました!!! 感動しています...ご丁寧な解説までありがとうございます...!! Pythonコードの意味も理解しながら、GASへの変換ができた気がします。 ほんとうにありがとうございました。
退会済みユーザー

退会済みユーザー

2022/09/13 13:26

解決してよかったです。御自分でここまで翻訳されたのはすごいと思いました。
退会済みユーザー

退会済みユーザー

2022/09/19 03:40

度々のメッセージですみません...。 こちらでいただいたご回答を参考に、おかげさまで次のステップへ進めているのですが、再び次のステップでもつまづいてしまっている箇所がありまして。。。 もし読んでいただけるお時間がありましたら、以下で投稿させていただきましたので、アドバイスいただけますととても幸いです...どうぞよろしくお願いいたします...。 https://teratail.com/questions/7te4auy5i4q1m6
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.37%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問