新着記事

Viewing posts from September, 2021

実務経験0 入社一年目のエンジニアが任されたセキュリティの話

2021年春に入社しました、情報システム部の清瀬です。

TORICOの情報システム部では、セキュリティの向上を上半期の目標に掲げておりました。上半期も終わりに近づいてきたということで、新卒で入社した私がどんなセキュリティ向上のために携わってきた業務を記事にしようとおもいます。多くのシステムでは Django を使用しているためコード部分は Django 前提の話になります。

1. インフラレベルでの対応

AWS WAF の導入

  • SQLインジェクション対策
  • クロスサイトスクリプティング
  • その他、包括的な対策

TORICOでは一部の社内用アプリを除き、全てのサービスをAWSでデプロイしています。それら全てのサーバを悪意のあるリクエストから守るために、AWS WAF を導入しました。

AWS WAF は2019年に大幅アップデートされ、今までのものは WAF Classic と名称を変更しました。新しいWAFの最大のメリットは、るAWSによって作られたルールを簡単に導入できる点です。例えば AWSManagedRlesCommonRuleSet では、OWASPに記載された主要な脆弱性・リスク10個をカバーしてくれています。クロスサイトスクリプティングや悪意のあるボットを排除してくれるルールをボタン一つで導入できるため、包括的なセキュリティ対策をすることができます。もちろん、今までのようにオリジナルのルールを作ることができます。そのため、社内からのアクセスには一部のルールを緩和させたりなどもできるようになっています。

余談ですが、WAFの導入の検証を担当したのは、まだ私がインターン1週間くらいの時でした。検証のために簡易的な Django アプリを Ec2 にデプロイしてWAFの導入でクロスサイトスクリプティングやSQLインジェクションがきちんとブロックされるのかを検証しました。インターンなのにこんな重要そうな仕事をさせていただけるのかと驚いたのを今でも覚えています。

過去に使用していた IP アドレスのルーティングを解除

これはセキュリティとは少し違う話かもしれませんが、ドメインの整理などもしました。一部の未使用のドメインが、既に手放してある IP アドレスと結びついたまま放置されていました。もし IP アドレスが悪意のあるサイトに使われていた場合、最悪ユーザーを悪意のあるサイトに誘導してしまうということも起こり得ます。そうならないためにも、不要なドメインを整理しました。

2. アプリレベルでの対応

サービスのAdmin ・社内アプリ を社内からのみ許可する

  • 不正なアクセス対策
  • 個人情報の保護

こちらは Django の Middleware を使って対応しました。サブネットマスクの表現方法などが複数あるため IP を取り扱うのは面倒くさそうだなと思っていましたが、python には ipaddress という便利なモジュールが標準で搭載されています。このモジュールを使うことで簡単に実装することができました。このタスクは上記の WAF でも実装できますが、1つのALBで複数の社内アプリを制御していたためアプリレベルで対応することにしました。アプリレベルのため、各アプリに適した制限を導入することも簡単にできます。

ログインセッションキーを適切な設定にする

  • セッションハイジャック対策
  • CSRF対策

大事なセッションを管理してくれるクッキーにはセキュリティ対策は欠かせません。TORICOの全てのサイトで、その大切なCookieの設定が適切になるように対策をしました。具体的には HttpOnly Secure SameSite=Lax  の3つの設定です。これらの設定を簡単に説明すると、

HttpOnly: JavaScript からセッションにアクセスできないようにする。設定されていなかった場合、JS 経由で セッション情報が漏洩する危険がある。

Secure: https 通信でのみ セッションを送るようにする。設定されていなかった場合、暗号化されない Http 通信でセッションが送られてしまう危険がある。

SameSite: Strict, Lax, None の3つの設定がある。Lax の場合、別オリジンからの POST の時はセッションを送らない。None の場合、例えばECサイトの届け先を変更するフォームのある偽サイトからユーザーのセッションを使ってデータが送信されてしまう。

Django では 2系以上であれば settings で設定することができます。1系の場合は SameSite を設定することができません。クッキーセッションの注意点として、あまりにも厳しい設定にした場合、サービスがうまく動かなくなる可能性があります。特に SameSite は決済部分でエラーがでることがあるそうです。

個人情報出力時に追跡IDとログを残す

  • 個人情報漏洩時の漏洩元の究明

TORICOでは 商品の配送などの過程で、お客様の住所をCSVに出力することがあります。もしそういったデータが社外に流出した時、どのデータが流出したのかを追いやすいように追跡IDをそれぞれのファイルに追記するようにしました。ログにはリクエストの情報を全てを保存しているため、いつ・誰が・どの検索ワードで CSV を出力したのかを追うことができます。 Django には DB へのログ出力機能はないため、DBに出力する場合には自作する必要があります。

3. データベースの対応

重要なフィールドの暗号化

  • 個人情報の保護
  • データ漏洩時の影響を最小限にする

重要なフィールドを可逆暗号化することで、万が一情報漏洩した時でもその影響を最小限に留められるようにしました。可逆暗号は Django のオリジナルのフィールドを作ることで簡単に対応することができました。具体的な実装方法についてはこちらの記事で書きました。また、一部の古い社内アプリではアカウントのパスワードがハッシュ化されていなかったため、 bcrypt という方法で不可逆暗号化もしました。

ソルトとペッパーの役割の違いなどが曖昧だったため、とても勉強になったタスクだったなと思っています。

最後に

上半期に行った主要なセキュリティ対策を紹介していきました。セキュリティは深刻な障害の原因になるため、普段のコーディングから気をつけていきたいと思います。

PMS&ISMS後編 Pマーク認定、ISO27001規格認証でのコンサルを選ぶポイントは、これだ・・・。


株式会社TORICO情報システム部の四斗邊です。

PMS、ISMS関連の後編です。
今回はP
マーク認証、ISO27001認証を取得する際のコンサルを選ぶポイントについて話したいと思います。

まずTORICOのブログをご覧になられている人ならご存知だと思いますが、TORICOは漫画のECサイトをメインに、デジタルコンテンツ配信、イベント事業行っている会社です。

従業員は150(20216月現在) を超え、事業規模の拡大に伴いPマーク認証、ISO27001認証を取得する流れになりました。

そんな私は入社早々にして取得するためのPJに参加したわけですが、今まで経験した会社に存在した規定などは、実はPマーク認証、ISO27001認証に基づいて行われていたんだなということがわかり、今まで関わった仕事を振り返り立ち返り興味深く関わることができたと実感しています。

ちなみにPMS
Pマーク認証とISMSISO27001認証の違いや説明については、前編をご覧ください。

作業内容は、今まで無法地帯だった箇所にメスを入れ、規律やルールを作成することが多かったと思います。具体的には、管理ルールの定まっていなかったPCの扱いについて、退勤時に施錠を徹底することなどです。

作成時は、これで良いのかと自問する不安の中での取得となっていました。

最終的には、「あ、これで良かったんだ」とある程度の経験と知識は蓄えることができたかなと思います。

さて前置きが長くなりました。

実はPマーク認証、ISO27001認証取得を経験した人が会社内に数名いると独力で取れるぐらいの取得しやすい認証なのですが、これを未経験者のみだけで挑戦すると、「何から手をつけていいのか」、「どうやったらいいのかわからない」ということが多すぎます。そんなときは、コンサルに頼んだほうが近道です。

弊社はもちろんコンサルに依頼することになりました。

ただコンサルを選ぶ際には以下の注意点を考慮されることを強くおすすめします。



1 Google Driveをつかってくれ!


弊社が担当していただいたコンサルは、情報保護の観点からかコンサル独自のアプリケーション、クラウドサービスを提供する会社でした。

その結果、保存する形式としてはofficeフォーマットでやり取りすることがほとんどでした。弊社はどちらかというとofficeなどを使わず共同で同時に編集作業できる点からGoogleのグループウェアを業務に多く取り入れている会社でしたので心底やりづらいです。

一旦編集したファイルをofficeファイルに変換しなければいけないことや、最新版であることが分かりづらいこと、共同編集できないなどGoogleのグループウェアの恩恵を受けられず効率が悪いと何度思ったか。

またクラウドストレージもコンサル独自のものがあったりと、Google Driveで共有しても対応してくれなかったというのがあるので、できれば事前にGoogle Driveなどグループウェアを共同で作業できるなどの事前確認は必要です。



2 リスクマネジメントは、一回マクロなしでやりませんか?


ISMS構築で一番大事なポイントといってもいいリスクマネジメント。

この作業を抜いではないといっても過言ではありません。

この作業を簡単に噛み砕くと「情報資産を特定して、リスクを把握して、驚異を数値化する」業務である。

この「作業の流れ」については、コンサルから説明を受けたので言葉では理解できるのですが、コンサルを交えて実践となった際に、事前に弊社の情報資産を予めピックアップした情報を渡していたので、書式に基づいて処理の流れを見せてくれるのかと思いきや、その部分は割愛されマクロでの処理の流れを説明されるという。

その場では、特に不明な点はないと思われたのですが、後々実際リスクマネジメントを回す際に見返すと「何やってんだ、この作業」となってしまいました。

そのため、できる限りマクロを交えず、実際の業務として落とし込めるまでコンサル立会のもと行うことを強くおすすめします。

特に書式やルールについては、コンサル提供の書式でもいいですが、コンサルと相談して独自の物を作って作業や管理のしやすいように改良することを強くおすすめします。

その場で頑なに拒んでくるコンサルは、実際のISMS構築やPMS構築の実務よりも審査を通ることのみを考えているコンサルではないかなと思われますので、注意が必要です。

 


3 審査期間の情報を豊富に持っているか?


弊社のコンサルでよかった点として、審査期間の情報を豊富に持っていた点があげられます。

実はPマーク認証、ISO27001認証の審査機関は1つではありません。

その審査機関の特徴といった情報を持っていることもコンサルの選ぶポイントと言えます。

大まかに以下の点があげられる。
① 審査の難易度
② 審査において指摘される傾向のあるポイントや過去や例
③ 審査にかかる費用

これらのデータや経験を持っているコンサルはなかなか頼りになるのではないかなと思います。

以上3つのポイントをご説明しました。

これらを踏まえてPマーク認証、ISO27001認証関連のコンサルを選ぶ際の参考にしてください。

前編と後編とかなり文字数多めで書きました。なかなかのボリュームとなっておりますのでお時間あるときにみていただけると大変嬉しく思います。
また、ネタがあればブログを書きたいと思います!

PMS&ISMS前編 PMSとISMSの違いについて


はじめまして株式会社TORICO情報システム部の四斗邊です。

いきなりですが、PMSとISMSについてお話したいと思います。

なぜこの話をしようと思ったかといいますと、株式会社TORICOでは、2021年1月にPマーク認証を取得、2021年2月にISMS認証(別名ISO/IEC 27001)を取得したからです!

この機会に、TORICOを知っていただこうと思い技術ブログとして若干逸れる内容ですが情報系のブログ記事としてPMS及びPマークとISMSに関する内容を前編と後編2回に分けてお話ししたいと思います。

まず、前編としてPMSおよびPマークとISMSの違いについて、後編はこれら認証取得時のコンサルの選ぶポイントをお伝えできればと思います。

それでは、前編スタート


1 PMS
Pマークについて


さっそくですがPMSとは「Personal information protection Management Systems」の略で個人情報保護マネジメントとも呼ばれています。

PMSは、個人情報保護を目的とした継続的な組織の設立や運営管理方法の制定と言った仕組みのことを指し「システム」と書かれていますが機械やプログラミングで構築したソフトウェアなのではありません。

具体的な仕組みとして継続的な個人情報管理台帳の作成と更新、漏洩リスクへの対策、漏洩時の対応など、実際に運用したPMSを評価する会議体を実施するなどがあげられます。

PMSによる個人情報の適切な取り扱いができるか審査を経て、JIS Q 15001規格に定義された個人情報保護マネジメントを実施する適合事業者と認められるとPマーク認証を取得できます。

ただ、Pマーク認証を取得していないからと言って企業には個人情報保護マネジメントが無いということでなく、審査を得ていない場合や、JIS Q 15001規格に沿っていない独自の個人情報保護マネジメントの構築している場合が企業には考えられます。


2 ISMSISO27001について


ISMSとは「information security management system」の略で情報セキュリティマネジメントシステムとも呼ばれています。

ISMSは企業が保有する情報資産を保護する目的のため継続的な組織の設立や運営、管理方法の制定など仕組みを意味します。PMS同様、「システム」と書かれていますが機械やプログラミングで構築したソフトウェアなのではありません。

具体的な仕組みとして、継続的な情報資産の作成と更新、情報を「機密性」「完全性」「可用性」ごとにリスクを特定(リスクアセスメント)し、リスクに合わせて適切に管理改善していくことです。

ちなみにISO27001は、ISMSの国際規格を指します。

別名ISO/IEC 27001、JISQ27001とも呼ばれています。

Pマーク同様、ISMS構築後、審査を得て適合事業者と認められるとISO27001認証を取得できます。

ちなみに、PMSとISMSは個人情報の部分において重複します。

ISMSは全ての企業情報を保護対象としているため上位互換のように表現されますが、あくまで企業の情報資産の一部として個人情報を扱うISMSに対して、個人情報保護を重要視している点で差があります。具体的としては、PMS(JIS Q 15001)では個人情報の目的や取得方法の特定や個人情報の開示、個人情報を第三者へ提供する場合のルールなど規格内に盛り込まれています(問い合わせページとかでやたら同意を求めてくるこれです。)

また、Pマーク認証は法人単位、ISO27001認証は部門やセクション単位で取得できるようです。


3 メリットについて


TORICOではPマーク認証とISO27001認証を取得する過程で既にメリットはありました。

取得に伴い実施したリスクアセスメントや個人情報の洗い出しによって日常の業務において到底意識しない部分に目を向ける機会になったからです。また、それまで関わりのなかった部署に対しても協力していただく機会もあったので全社的に危機意識を培うチャンスとなったと思います。

またリスクを未然に防ぐためにPMSとISMS構築が一役買いました。

これまで個人情報や企業の情報など保管期間を明確に決めずに保存していたり、バックアップを取らなかった部分については改めるきっかけになったと思います。

本来のメリットとしては、公的機関への入札時などPマーク認証とISO27001認証を取得が条件になっているようで、入札時のメリットがあるようです。

またPマークやISO27001認証を取得している企業から取引の際、信用の目安になり、比較的に取引しやすいなどもメリットに挙げられます。

デメリットは、業務フローが増えたことと、書式などのフォーマットが増える点でしょうか。

以上、PMSとISMSの違いについてお話ししました。

後編に続きます。

参考文献


システム管理者の会 

第4回 プライバシーマーク(Pマーク)と他の認証制度の統合


帝国データバンクネットコミュニケーション

ismsとiso27001は何が違うのですか?

WORKーPJ

ISMSとプライバシーマーク(PMS)とは?概要と相違点、どちらを取得するべきか

エンジニアの知らないISBNの話

本には商品判別のためのバーコードがついています。
いわゆるISBNコード(JANコード)です。
ECショップでは本を取り扱うために本の書誌を記録するテーブルを作成しますが、その際にint型でいいだろう? ユニーク制約でいいか?とおもってはいけないません。

先に結論を書いてしまうと

  • 文字が含まれる可能性がある
  • 先頭に0が来る可能性がある
  • ユニークではない

char型にして、ユニーク制約をつけないほうがよい理由

1. 文字「X」が含まれる可能性がある

ISBNコードには10桁と13桁があります。
10桁ISBNコードは9桁+チェックデジット1桁で構成されます。
古い体系ですが、一部で使うことがあり、チェックデジットに文字「X」がはいる可能性があります。
これはチェックデジットがモジュラス11、ウェイト10-2という方法で算出されるためです。
計算方法をphpで書くと

/**
 * ISBN10桁のチェックディジットを返す
 */
function getCheckDigit10($jan9)
{
    $sum = 0;
    $cnt = 0;
    for ($i = 10; ; $i--) {
        if ($cnt > 10) {
            break;
        }
        $sum += substr($jan9, $cnt, 1) * $i;
        $cnt++;
    }
    $remainder = $sum % 11;
    if ($remainder == 0) {
        return 0;
    }
    $check_digit = 11 - $remainder;
    if ($check_digit == 10) {
        $check_digit = 'X';
    }
    return $check_digit;
}

2. 先頭に0がつく可能性がある

ISBNコードと書きましたが、実際にバーコードになっているのはJANコードです。
そしてJANコードは先頭が「0」になる可能性があります。
本を売るショップとして取り扱いそうなJANコードは3種類。

  1. 一般商品JANコード
    45,または49で始まる12桁+チェックデジット1桁の13桁JANコード。
    45と49は日本に割り振られた国コードなので固定。
    雑誌やグッズなどに使われています。
  2. 書籍JANコード
    9784で始まる12桁+チェックデジット1桁の13桁JANコード。
    978は世界共通の書籍コード
    4は日本に割り振られた国コードなので固定。
    いわゆる13桁ISBNコード。
    コミックや書籍などに使われています。
    バーコードは2段になっており、上段は書籍JANコード、分類・価格のコード。
  3. インストアコード
    02、04、20〜29で始まる12桁+チェックデジット1桁の13桁JANコード。
    ポイントカード、会員証やレジ袋などの管理・販売用に使われる、小売店が独自につくって使用してよいJANコード。
    20〜29始まりのJANコードを使用すればいい話だが、02、04も準備しておくに越したことはない。

JANコードのチェックデジットはモジュラス10、ウエイト3-1という方法で算出されるため0〜9の数字になります。

/**
 * ISBN13桁のチェックディジットを返す
 */
function getCheckDigit13($jan12)
{
    $sum_odd = 0;
    $sum_even = 0;
    for ($i = 11; $i >= 0; $i--) {
        if ($i % 2) {
            $sum_odd += substr($jan12, $i, 1);
        } else {
            $sum_even += substr($jan12, $i, 1);
        }
    }
    $sum = $sum_odd * 3 + $sum_even;
    $division = $sum % 10;
    return (10 - $division) % 10;
}

3. 同じJANコードが使われる

JANコードは同じコードが存在するのでユニークではありません。

  1. 書籍JANコードの場合
    「今現在販売中の本」と限定すれば書籍JANコードはユニークになります。
    ただし、本が絶版になったあと、そのJANコードは別の本に付番されます。
    そのため、今までに発売したすべての本のJANコードだと同じJANコードをもつ本が複数存在します。
  2. 雑誌の一般商品JANコードの場合
    雑誌に付番されている雑誌コードから一般商品JANコードが作成されます。
    その際に西暦の下1桁を使用しているため、10年ごとに同じコードになります。

以上がISBNコード(JANコード)をint型、ユニーク制約にしては行けない理由でした。
10桁と13桁のどちらも記録できるようにしてあると便利かもしれません。

海外回線からのブラウザリクエストを日本のオフィスから SOCK5プロキシを使って行う

海外回線からリクエストされた時に応答を変えるようなサービスで、実際の海外からアクセスした時のレスポンスを日本国内のPCから試したい場合は、SOCKS プロキシを使うと便利です。

実際に起動した、自分のコントロールしているサーバを使います。怪しげな VPN サービスを使わなくていいため安全です。

 SSHログインできる海外のサーバを用意する

海外リージョンで起動している AWS の EC2 などを使います。SSHでログインできるようになっており、HTTP関係のアウトバウンド通信がブロックされていなければOKです。

使っているPCで、 -D オプションをつけて SSHログインする

$ ssh -D 10080 user@example.com

これを行うと、普通にSSHのセッションが開始されますが、そのPCの 10080 ポートで SOCKS v5 プロキシが使えるようになってます。

SOCKS プロキシの接続先を、localhost:10080 にする

システム環境設定 から、プロキシの設定を行います。

Firefox はブラウザ内に固有のプロキシ設定を持っており、そこで特別な設定を行うこともできますが、デフォルトでは OS のプロキシ設定を使うようになっています。

接続を確認する

確認くんhttps://httpbin.org/ip を見て、接続元IPアドレスが変わっていることを確認できます。

Selenium でプロキシ設定済みのブラウザを起動する

システム環境設定を開いてプロキシの設定を変更するのは面倒ですが、Selenium を使えばプロキシ設定済みのブラウザインスタンスを起動できます。
( 127.0.0.1 となっている箇所は localhost でも同じです。)

Pythonスクリプトの例

Chrome

from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=socks5://127.0.0.1:10080")
options.add_argument('--proxy-bypass-list=""')
driver = webdriver.Chrome(options=options)
driver.get('about:blank')

Firefox

from selenium import webdriver
profile = webdriver.FirefoxProfile()
profile.set_preference('network.proxy.type', 1)
profile.set_preference('network.proxy.socks', '127.0.0.1')
profile.set_preference('network.proxy.socks_port', 10080)
profile.set_preference('network.proxy.no_proxies_on', '')
driver = webdriver.Firefox(firefox_profile=profile)
driver.get('about:blank')

ターミナル(シェル)でプロキシ設定済みの Chrome を起動する

open -a "Google Chrome" --args --proxy-server="socks5://127.0.0.1:10080" --proxy-bypass-list=""

Google Chrome や Chromium の場合、コマンドラインオプションでプロキシの設定を行うこともできます。ただし、一度起動してしまうと、プロセスを完全終了しないと設定がクリアされないので戻す時が若干面倒です。

Amazon Pay Checkout v2 API の署名 (RSA-SHA256 (RS256) + RSA PSS Padding) を Python で行う

Amazon Pay の API クライアントを書く際、Amazon のAPIサーバに送信するリクエストに、RSA-SHA256, RSA PSS パディングを使って署名を作り、リクエストに含めて送信する必要があります。

Java や Node はクライアントライブラリがあったので、それを使って簡単に署名できたのですが、弊社 TORICO ではサーバサイドは主に Python を使っており、既存のクライアントライブラリは無かったため、Node のライブラリを参考に署名コードを書きました。

Amazon Pay のAPI

今回は、Amazon Pay の Checkout v2 API を使う必要がありました。

https://developer.amazon.com/ja/docs/amazon-pay/intro.html

リクエストヘッダに含める署名については、
この CV2 (Checkout V2) の 署名リクエストのページに解説があります。

このページを見ていくと、「署名を計算します」の箇所に

署名を計算するには、ステップ2で作成した署名する文字列に秘密鍵を使用して署名します。 SHA256ハッシュとソルト長20のRSASSA-PSSアルゴリズムを使用します。結果をBase64エンコードして、この手順を完了します。 RSASSA-PSSを使用して計算されたすべての署名は、入力が同じであっても一意であることに注意してください。

とありますので、この「 SHA256ハッシュとソルト長20のRSASSA-PSSアルゴリズム」を Pythonでコーディングする必要があります。

ところで、この最後の「 入力が同じであっても一意であることに注意してください」は、「〜一意でない」の間違いじゃないですかね。英語版ページは調べてませんが。

Amazon Pay Scratchpad

https://pay-api.amazon.jp/tools/scratchpad/index.html

検証リクエストを発行できるサイトが用意されていますので、署名ロジックの確認に使うと良さそうです。
私は、今回の開発中は存在を知らなかったので、使っていません。

Node.js の場合

node.js の場合は、クライアントライブラリは
@amazonpay/amazon-pay-api-sdk-nodejs があり、これを使うと署名を含めたAPIリクエストが一発で行えます。

ヘッダの署名を行っているコードは

https://github.com/amzn/amazon-pay-api-sdk-nodejs/blob/master/src/clientHelper.js#L142 このあたりで、
「 SHA256ハッシュとソルト長20のRSASSA-PSSアルゴリズム」のコードは
https://github.com/amzn/amazon-pay-api-sdk-nodejs/blob/master/src/clientHelper.js#L84 ここです。

Python の場合

上記 node.js 相当のコードを書くわけですが、hmac ライブラリには相当のコードはありません。

RSA や パディングの基礎的なロジックは cryptography に入っており、実際の使い方は PyJWT がいい感じになっているので、PyJWT を参考にします。

https://github.com/jpadilla/pyjwt/blob/master/jwt/algorithms.py#L232

PSS パディングはここです。

https://github.com/pyca/cryptography/blob/main/src/cryptography/hazmat/primitives/asymmetric/padding.py#L19

この PSS の第一引数の _mgf ってなんだ、と思いましたが、同モジュール中にある
MGF1(RSAAlgorithm.SHA256())
を入れたら動きました。

署名部分のコード

署名部分のコードはこのようになります。

import base64
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_pem_private_key

private_key = b'''-----BEGIN PRIVATE KEY-----
MIIEvQIB....
....N/Qn4=
-----END PRIVATE KEY-----'''

string_to_sign = 'AMZN-PAY-RSASSA-PSS\nxxxxxxxxxxxxxxxxxxxxxxxx'

key = load_pem_private_key(private_key, password=None)

signature = key.sign(
string_to_sign.encode('utf-8'),
padding.PSS(padding.MGF1(hashes.SHA256()), 20),
hashes.SHA256())

signature = base64.b64encode(signature).decode('utf-8')

後になって気づきましたが、PyJWT に RSAPSSAlgorithm という、今回の用途にぴったりなクラスがあったので、これを使うともうちょっとシンプルなコードになるかもしれません。

署名元の文字列の生成も含めたコード

checkoutSessions を行うコードはこのような感じです。

node.js のコードを参考にした箇所がいくつかあり、それらは実際には使われない、不要なコードになってます。

import base64
import datetime
from hashlib import sha256

import requests
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_pem_private_key

private_key = b'''-----BEGIN PRIVATE KEY-----
MIIEv....
....N/Qn4=
-----END PRIVATE KEY-----'''

config = {
'publicKeyId': 'SANDBOX-AEXXXXXXXXXXXX',
'privateKey': private_key,
'region': 'jp',
'sandbox': True,
}

constants = {
'SDK_VERSION': '2.1.4', 'API_VERSION': 'v2', 'RETRIES': 3,
'API_ENDPOINTS': {'na': 'pay-api.amazon.com', 'eu': 'pay-api.amazon.eu', 'jp': 'pay-api.amazon.jp'},
'REGION_MAP': {'na': 'na', 'us': 'na', 'de': 'eu', 'uk': 'eu', 'eu': 'eu', 'jp': 'jp'},
'AMAZON_SIGNATURE_ALGORITHM': 'AMZN-PAY-RSASSA-PSS',
}

checkoutSessionId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

options = {
'method': "GET",
'urlFragment': f"/v2/checkoutSessions/{checkoutSessionId}",
'headers': {},
'payload': ''
}

pay_date = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')

headers = {
'x-amz-pay-region': config['region'],
'x-amz-pay-host': 'pay-api.amazon.jp',
'x-amz-pay-date': pay_date,
'content-type': 'application/json',
'accept': 'application/json',
'user-agent': 'amazon-pay-api-sdk-nodejs/2.1.4 (JS/14.15.1; darwin)',
}

lowercase_sorted_header_keys = list(sorted(headers.keys(), key=lambda x: x.lower()))
signed_headers = ';'.join(lowercase_sorted_header_keys)

canonical_request = [
options['method'],
options['urlFragment'],
'', # GETパラメータだが一旦無し
] + [
f'{h}:{headers[h]}' for h in lowercase_sorted_header_keys
] + [
'', # 空行入れる
signed_headers,
sha256(options['payload'].encode('utf-8')).hexdigest()
]

canonical_request_bytes = ('\n'.join(canonical_request)).encode('utf-8')

string_to_sign = constants['AMAZON_SIGNATURE_ALGORITHM'] + '\n' + sha256(canonical_request_bytes).hexdigest()

key = load_pem_private_key(config['privateKey'], password=None)

signature = key.sign(
string_to_sign.encode('utf-8'),
padding.PSS(padding.MGF1(hashes.SHA256()), 20),
hashes.SHA256())

signature = base64.b64encode(signature).decode('utf-8')

headers['authorization'] = \
f"{constants['AMAZON_SIGNATURE_ALGORITHM']} " \
f"PublicKeyId={config['publicKeyId']}, " \
f"SignedHeaders={signed_headers}, " \
f"Signature={signature}"

response = requests.get(
f"https://pay-api.amazon.jp{options['urlFragment']}",
headers=headers
)
print(response)
print(response.json())
Search