Powered by

Bouncy CastleはPGP暗号化の最適解なのか【JJUG CCC Fall 2025】

article-0175_top.jpg

2025年11月15日(土)に開催されたJavaコミュニティイベント「JJUG CCC 2025 Fall」にて、GMOペイメントゲートウェイ株式会社(GMO-PG)のエンジニア 前川 篤史が登壇しました。

セッションのテーマは「Bouncy Castle はPGP暗号化の最適解なのか」。公共料金向け決済サービス「マルチビリングパッケージ」におけるファイル連携オプションとして、PGPによるファイル暗号化機能をどのように実装したのかを紹介しました。

講演では、そもそもPGPとは何かという基礎から、ハイブリッド暗号方式やデジタル署名の仕組みを整理したうえで、JavaでPGPを扱う際の実装パターンを解説。外部プロセスとしてGPGコマンドを呼び出す方式と、JavaライブラリであるBouncy Castleを利用する方式を比較しながら、運用前提・データサイズ・性能要件ごとに「最適解」がどう変わるのかを、実案件の事例とあわせて共有しました。さらに、大容量ファイルでのテスト不足から見つかった「しくじり」や、加盟店様との連携テストで得られた学びについても触れています。

本記事では、JJUG CCC 2025 Fallにおける前川の講演内容をもとに、セッションの流れに沿ってポイントを整理した書き起こしレポートをお届けします。

はじめに

はい。ご紹介にあずかりました、GMOペイメントゲートウェイ(GMO-PG)の前川と申します。よろしくお願いいたします。

「Bouncy CastleはPGP 暗号化の最適解なのか」というテーマでお話しします。テーマとしては少し専門的で、狭い領域の話かもしれませんが、本日はお集まりいただきありがとうございます。

まず目次をご紹介します。はじめにのあと、「そもそもPGPとは何か」という基本の話から入り、続いて「Javaで何を実装すべきか」、最後に「今回どのように実装したか」という順で進めていきます。今日はPGPという名前だけでも覚えて帰っていただければ嬉しいです。

まず最初に、PGPをご存じの方はどれくらいいらっしゃるでしょうか。あまり知られていない技術だと思いますので、まずはPGPがどういう技術なのか、その概要から説明していきます。

登壇者について

登壇者について

改めて自己紹介です。前川と申します。2022年にGMO-PGに入社し、現在エンジニア4年目になります。入社以来、公共料金系の決済サービスである「マルチビリングパッケージ」の開発チームに所属しています。

これまでは保守開発が中心でしたが、今回は暗号化処理の新規開発を任せていただきました。暗号技術はもともと詳しい方ではなかったのですが、この機会に一から学ばせてもらい、今は数学も改めて勉強し直したいと考えているところです。

本日が初登壇で、かなり緊張しているのですが、よろしくお願いいたします。

このセッションで得られること

このセッションで得られることは大きく二つあります。

一つ目は、PGPという技術と、その基盤となるハイブリッド暗号方式やデジタル署名など、暗号技術の仕組みを学べることです。

二つ目は、JavaでPGPを利用する際に、ライブラリなどの実装方法をどのように選定したのか、GMO-PGでの意思決定プロセスを知っていただけることです。これはPGPに限らず、一般的な技術選定の基準としても参考にしていただける内容だと思います。

また、Bouncy CastleやGPGコマンドを使った実装の具体例も交えながら、ミスしやすいポイントや、今回の実装での「しくじり」についてもご紹介します。

セッションの前提となるサービス・案件

セッションの前提となるサービス・案件

ここからは、今回のセッションの前提となるサービスと案件について説明します。

私は普段、「マルチビリングパッケージ」という決済サービスの開発と運用に携わっています。マルチビリングパッケージは、電気・ガス・水道などの公共料金事業者向けに、定期的な利用料金の徴収を支援するサービスです。クレジットカード決済、口座振替、振込票払いといった決済手段を一括で提供しています。

マルチビリングパッケージ

こちらがマルチビリングパッケージのご利用の流れです。右上には電気やガスのメーターを検針する方のイラストがあり、そこから得られた使用料の料金計算結果が加盟店様に連携されます。その情報をもとに、加盟店様は中央にいるGMO-PGへ請求依頼を行います。

GMO-PGは、その請求依頼をカード会社や銀行へ連携し、最終的な請求結果を加盟店様へ返却します。これが大まかな処理の流れです。

加盟店とGMO-PGの接続方法としては、API、ファイル連携、管理画面の3つを提供しています。このうち 加盟店とGMO-PGのファイル連携において、一部の加盟店向けオプション機能として、PGPによるファイル暗号化の仕組みを新たに開発しました。

ここまでが前提の説明で、ここから「そもそもPGPとは何か」という話に入っていきます。

PGPって何?

PGP とは何か

PGPとは何か

PGPは、Pretty Good Privacy(プリティ・グッド・プライバシー) の略で、「かなり良いプライバシー」という意味です。電子メールやファイルに対して暗号化と署名を行える暗号ソフトとして利用されてきました。

PGPは、共通鍵暗号やハイブリッド暗号方式、デジタル署名をサポートしており、鍵管理の仕組みも備えています。

PGPの成り立ち

PGPの歴史を簡単にご紹介します。誕生は1991年で、開発者は Phil Zimmermann(フィル・ジマーマン)です。当時はデジタル通信がどんどん普及し、個人間のメール通信が本格化していた時代でした。

当時の暗号技術は、民間向けというよりも、政府や軍事用途のものが主流でした。その中でアメリカでは、政府による個人間のメール通信の盗聴・傍受が社会問題になっていました。PGP は、そうした政府の監視から身を守るための自衛手段として、民間から生まれたソフトウェアです。

当時、アメリカ政府は暗号ソフトを武器扱いとして輸出を禁止していましたが、ジマーマン氏はソースコードを「書籍」として出版することで合法的に国外へ広め、そこから世界的にPGPが普及していきました。

その後1998年には、PGPの仕様がOpenPGPとしてRFCに公開され、標準規格化されます。以降、さまざまなOpenPGP実装が公開されるようになりました。代表的なものとしては、

  • GnuPG(GPG)
  • 今回のテーマでもある Bouncy Castle

などがあります。

PGPの成り立ち

ここまでで、よく似た言葉がいくつか登場したので整理しておきます。

  • PGP:ジマーマン氏が作った元のソフトウェア名
  • OpenPGP:PGPを標準化した規格の名前
  • GPG(GnuPG):OpenPGPの代表的な実装
  • Bouncy Castle:Javaで利用できるOpenPGP実装のひとつ

今回のスライドでは、区別が不要な箇所では、これらをまとめてPGPと呼ぶことがあります。

なぜ PGPを使うのか

なぜ PGPを使うのか

では、なぜ PGPを使うのかについて説明します。

たとえば SSHの場合、サーバーとサーバーの間の「通信経路」は暗号化されますが、サーバーに保存されたデータは平文のままです。万が一サーバー自体が侵害されてしまうと、データが漏洩する可能性が残ってしまいます。

なぜ PGPを使うのか2

一方で PGPは、「通信データ本体」を暗号化します。サーバーに保持されている間も、受信者が復号するまでは暗号化された状態のままです。そのため、いわゆるエンドツーエンド暗号化を実現できます。

似たような技術は他にも存在しますが、規格として広く知られ、実務で使われているものは多くありません。

なぜ PGPを使うのか3

主要な暗号プロトコルをざっくり分類すると、次のようなイメージになります。

  • OpenPGP 、S/MIME:通信データそのものを暗号化
  • SSH 、SSL/TLS:通信経路を暗号化

S/MIMEはメール通信に特化したプロトコルで、今回のように「ファイルを汎用的に暗号化する」用途にはあまり向きません。こうした背景から、ファイル暗号化の規格としては、現在でもPGPが主要な選択肢になっています。言い換えると、ファイル暗号化に関してはPGPだけでほとんどのニーズを満たせてしまう、というのが実情だと思います。

さらに、OpenPGPの最新版 RFCは2024年に改訂されたばかりで、新しい暗号アルゴリズムにも対応し続けています。PGPが今なお現役の暗号化手段として利用されていることの、ひとつの証拠と言えるでしょう。

Javaで何を実装しないといけないのか?

処理フローの全体像

処理フローの全体像

ここから、今回のテーマである「Javaで何を実装しないといけないのか」という具体的な話に入ります。

PGPでは、デジタル署名やハイブリッド暗号方式といった基本的な暗号技術が用いられています。まずはその仕組みをおさらいしたうえで、どのように Javaの実装へ落とし込んでいくのかを説明します。

PGPの処理全体は、一見すると複雑なフローに見えますが、大きくは4つの処理に分けられます。

  • デジタル署名を作成する
  • 署名付きデータを暗号化する
  • 受信者側で暗号データを復号する
  • 復号後のデータに対して署名を検証する

この4つのブロックが、そのままJavaでメソッドを実装していくときの単位になってきます。

デジタル署名を作る&署名を検証する

デジタル署名を作る&署名を検証する

まずは、上側の「デジタル署名」と「署名検証」の部分から説明します。

デジタル署名の目的は、「署名した本人が確かに作ったデータであること」と「途中で改ざんされていないこと」を確認することです。
暗号化が機密性(第三者に内容を知られない)を担保するものに対し、デジタル署名は真正性・完全性(本人が作成し、改ざんされていない)を保証します。両者は異なる目的を持っています。

署名の作成手順は次のとおりです。まず、送信者の手元に平文データがあります。これをハッシュ関数でハッシュ化し、ハッシュ値を得ます。そのハッシュ値に対して、送信者の秘密鍵を使って数学的な演算処理を行うことで、デジタル署名が生成されます。

生成したデジタル署名と平文データを結合したものが「署名付きデータ」です。この署名付きデータを、後続の暗号化処理に渡し、暗号データとして送信します。

受信者側では、まず暗号化されたデータを復号し、署名付きデータを取り出します。その後、署名と平文データに分解します。

デジタル署名を作る&署名を検証する2

送信者の公開鍵を使って署名に対して演算処理を行うと、送信時に作成されたハッシュ値を復元することができます。一方で受信者は、平文データを同じハッシュ関数で再度ハッシュ化します。同じハッシュ関数、同じ元データであれば、二つのハッシュ値は一致するはずです。

両者を比較して正しく一致すれば、「データは途中で改ざんされていない」と判断できます。このプロセスを「署名検証」と呼びます。これにより、「このデータは本当にこの送信者が作ったものだ」と確認できるわけです。

特別なことをしているわけではなく、PGPは一般的なデジタル署名の仕組みをそのまま利用しています。

データの暗号化&復号(ハイブリッド暗号方式)

データの暗号化&復号(ハイブリッド暗号方式)

続いて、下側の「暗号化」と「復号」の仕組みを見ていきます。PGPはTLSなどと同様にハイブリッド暗号方式を採用しており、共通鍵暗号と公開鍵暗号を組み合わせています。

暗号化の流れは次のようになります。まず、先ほどの手順で署名付きデータを作成します。次に、乱数生成器から「セッション鍵」と呼ばれる共通鍵を生成します。このセッション鍵は、同じ鍵で暗号化と復号の両方ができるものです。

生成したセッション鍵を使って、署名付きデータを暗号化します。ここまでで「セッション鍵で暗号化された署名付きデータ」ができます。

さらに、事前に受信者からもらっている公開鍵を使って、そのセッション鍵自体を暗号化します。つまり、

  • 公開鍵で暗号化されたセッション鍵
  • セッション鍵で暗号化された署名付きデータ

この 2 つを結合し、最終的に相手に送る暗号データとします。

受信者側では、この暗号データを分解し、暗号化されたセッション鍵と、セッション鍵で暗号化された署名付きデータに分けます。セッション鍵側は、自分の秘密鍵を使って復号します。これにより、復号に使える状態の共通鍵(セッション鍵)が取り出せます。

そのセッション鍵を使って暗号データを復号すると、署名付きデータが元の形で出てきます。その後は先ほど説明したとおり、署名検証を行います。

データの暗号化&復号(ハイブリッド暗号方式)2

なぜこのような仕組みになっているかというと、公開鍵暗号は共通鍵暗号に比べて処理時間がはるかに長くかかるからです。一般に、公開鍵暗号は共通鍵暗号の約1000倍くらいの時間がかかるとも言われています。

そのため、データサイズの大きい本体データは高速な共通鍵暗号で暗号化し、サイズの小さい鍵だけを公開鍵で暗号化する、という構成が取られています。これがハイブリッド暗号方式の一般的な考え方であり、PGPもこれに従っています。

ここまでの署名作成、暗号化、復号、署名検証という一連の流れは複雑に見えますが、構造としては標準的な暗号技術に沿ったものです。PGPだからといって特別な処理が出てくるわけではありません。技術自体が特別というよりは、プロトコルとして確立されていて、システム間で互換性を取りやすいことが、現代で PGPを使う意義なのかなと考えています。

小休止:暗号初心者に役立った書籍

ここで少し小休止として、暗号技術を学ぶうえで役立った書籍を紹介します。

私はこの案件に入るまでは暗号技術の初心者でしたが、リテラシーを身につけるのに役立った本が2 冊あります。

1冊目は、サイモン・シンさんという有名な科学ジャーナリストが書いた『暗号解読』です。上下巻になっていて、古代のシーザー暗号から、最近の量子暗号まで、暗号の歴史をたどるような内容になっています。純粋な読み物としてもとても面白い本です。

2冊目は『暗号技術入門』です。なんとなく暗号の意義や背景が分かってきたところで、「それぞれの技術についてもう少し解像度を上げて理解したい」というときに読むと、とてもおすすめです。具体的に「どういうフローで暗号化が進んでいくのか」といった話も載っているので、今回の開発の設計を考えるうえでも非常に役に立ちました。

Javaでどうやって実装したのか

実装方式の全体像と結論

実装方式の全体像と結論

ここからは、「JavaでPGPをどう実装したのか」についてお話しします。

先ほどご紹介した処理フローを、PGPの規格に基づいて実装していくにあたり、大きく2つの実装方式を比較しました。

  • GnuPG(GPG)を外部プロセスとして呼び出す方式
  • Bouncy CastleというJavaライブラリを使う方式

「どちらが最適解なのか」という問いに対して、結論は次のとおりです。

運用の前提やデータサイズ、性能要件によって最適解は変わる。

今回 GMO-PGで採用したのはBouncy Castleですが、それは「あくまで今回の案件においては」という前提付きの結論です。以降で、その理由を説明していきます。

GPGとは・GPGコマンドをプログラムから呼び出す実装

GPGとは・GPGコマンドをプログラムから呼び出す実装

まず、GPGから説明します。

GPG(Gnu Privacy Guard)は、OpenPGPのオープンソース実装で、「グヌー PG」とも呼ばれます。PGPのオープンソース版ソフトウェアであり、OpenPGPに準拠しています。Linux、macOS、Windowsの各OSに対応しています。

GPGとは

たとえば Linuxで、先ほどのようなやや複雑な処理フローをGPGコマンドで実行しようとすると、署名と暗号化を1行のコマンドで実行することができます。復号と署名検証も同様に1行で実行可能です。コマンドだけで見ると、かなりシンプルに扱えるのがGPGの利点です。

実装方法①GPGコマンドをプログラム内から実行

これをJavaから使う場合は、暗号化対象のファイルを取得し、暗号化コマンド文字列を組み立てて外部プロセスとして実行し、必要に応じてエラー処理をする、という流れになります。Linuxコマンドを呼び出すだけ、と言ってしまえばそれだけなので、実装コストは非常に低く済みます。

さらに、このGPGを呼び出す実装は、私の隣のチームでも長年運用されており、その意味でも安心感がありました。

実装方法①GPGコマンドをプログラム内から実行2

一方で、コマンドを直接叩く方式にはデメリットもあります。1つ目は「独立性が低い」ことです。外部プロセスに依存しているため、アプリケーション側から見るとブラックボックスに近い形になってしまいます。

具体的には、ローカルで動かそうとしたときに「そもそも GPGがインストールされていないのでコマンドが叩けない」といった問題が起こります。また、JUnitでテストコードを書く際には、GPGと同じような動きをするモックを用意する必要があり、テストコード側が膨らんでしまうという問題もあります。

もう1つが「実行環境ごとの挙動差分」です。サーバーごとにインストールされている GPGのバージョンが違うと、サポートされている暗号アルゴリズムも微妙に異なります。その結果、「検証環境では動いたのに本番では動かない。なぜ?」といった事態が起きかねません。本番側のGPGを入れ直したりする手間が発生する可能性もあります。

実装方法①GPGコマンドをプログラム内から実行3

さらに、コマンド方式のデメリットとして「エラーハンドリングのしづらさ」があります。先ほどのような実装だと、「コマンドを実行した結果、暗号化されたファイルがちゃんと出力されているかどうか」という二択でしかエラーを判定できず、エラー時も一律で同じメッセージを出すような作りになりがちです。

何か不具合があったときに、デバッグに時間をかけられるのであればまだよいのですが、今回のように毎日加盟店様とやり取りするファイルを扱う場合は、すぐに原因を特定できた方が望ましいです。Javaの例外としてエラー理由を細かく取得できない、という点もデメリットだと考えました。

実装方法①GPGコマンドをプログラム内から実行4

こうした理由から、「外部プロセスに頼らないJavaライブラリはないか」という観点で候補を探し、その中で浮かび上がってきたのが Bouncy Castleでした。

Bouncy Castleとは・Bouncy Castleによる実装

実装方法②Bouncy Castleを使う

Bouncy Castleは、Javaアプリケーション単体でPGP処理を実装できるライブラリです。暗号プロトコルとしてはS/MIMEなどにも対応していますが、特にOpenPGP周りに定評があります。

Java Bouncy Castleを使ってPGP処理を実装すると、GPGコマンド方式に比べてコードはかなり長くなります。PGPのプロトコルに沿って、一つひとつの処理をJava上で組み立てていく必要があるためです。

実装方法②Bouncy Castleを使う2

たとえば PGPの署名付きデータには、

  • 署名のメタ情報のパケット
  • 本体データそのもののパケット
  • 署名自体のパケット

といった構造があり、Javaのプログラム上では、それぞれのパケットを順番に作成して、ファイルに書き出していきます。

本体データを書き出しながら逐次ハッシュ計算を行う処理など、PGPの内部構造を理解していないとイメージしづらい部分もありました。私はこの実装をするまで、「ハッシュ計算はデータをすべて読み込んでから最後に一気に行うもの」というイメージを持っていたのですが、実際にはストリーム処理のように逐次で計算できることを知り、良い学びになりました。

このように、PGPのパケット構造やRFCの仕様を参照しながら実装していく必要があるため、実装コストは正直高めです。ただ、処理がすべてJavaアプリケーション内で完結するため、外部プロセスに依存しないという大きなメリットがあります。実行環境による差異の影響を受けにくく、テストもしやすくなります。

JUnitでの単体テストも書きやすく、ローカル環境でも安定して動作させることができるようになります。エラーハンドリングも、コマンド方式のように「ファイルができたかどうか」だけで判断するのではなく、「なぜエラーになったのか」をJavaの例外として取得できるため、原因を即座に特定しやすくなります。

日次運用が前提のファイル連携では、この点は非常に大きな利点でした。

処理速度の比較

処理速度はGPGコマンドの方が早い

一方で、性能面では GPGコマンドの方が優れています。小さなファイルサイズであればどちらもそれほど差はありませんが、1GBのファイルを処理するケースで比較すると、今回の検証環境では GPGコマンドの方がBouncy Castleよりも、最大で8倍程度速いという結果が得られたケースもありました。

今回の案件では、扱うファイルは数KB〜数十KB程度で、処理性能に対する要求もそれほど厳しくありませんでした。そのため、性能面よりも運用性やテストのしやすさを優先し、Bouncy Castleを採用しました。

逆に、毎日ギガバイト級のファイルを処理するような案件であれば、GPGコマンドの方が適している、という判断になると思います。時と場合によって、実装の最適解が変わる、というのが結論です。

実装のしくじりと学び

実装のしくじり:十分大きなファイルサイズでのテストが必要

ここからは、今回の実装で起きた「しくじり」と、そこから得られた学びについてお話しします。

一つ目は、「十分大きなファイルサイズでのテストが必要だった」という点です。実運用で扱うファイルサイズはせいぜい数キロバイトだったため、単体テストでもその2〜3倍ぐらいのサイズでテストしておけば十分だろう、と考えていました。実際、その範囲ではバグも出ておらず、問題なく動作していました。

実装のしくじり:十分大きなファイルサイズでのテストが必要2

ところが、個人的な興味本位で「ギガバイト級のサイズだとどれくらい時間がかかるのか」を試したところ、署名検証の一番最後の段階でエラーが発生してしまいました。

原因は、署名付きデータ構造の読み込み処理を正しく実装できていなかったことです。署名付きデータは先ほど触れたように、メタ情報、本体データ、署名パケットという複数のパケットで構成されています。署名検証では、それぞれのパケットに対応するオブジェクトを順番に作成していく必要があります。

実装のしくじり:十分大きなファイルサイズでのテストが必要3

本体データパケットを最後まで読み切る前に署名パケットのオブジェクトを作成しようとしてしまうと、パケットの境目が分からず、パケットの先頭位置を正しく認識できないためエラーになってしまいます。

小さなファイルでうまく動いていた理由は、Bouncy Castle側のデフォルトバッファサイズに収まっていたためです。バッファ内でパケットの境界も含めて一度に読み込めていたので、エラーが表面化しなかった、という事情がありました。

実装のしくじり:十分大きなファイルサイズでのテストが必要4

こうした経験から、ファイルサイズは実運用よりも十分大きなものを用意してテストしておくことが重要だと学びました。

実装のしくじりを踏まえて:接続先との連携テストは大事

もう一つの学びは、「接続先との連携テストは早めに行うべき」ということです。今回自分たちがつまずいたポイントは、そのまま接続先の加盟店様側でも発生しうる問題です。早めに連携テストを行っておけば、双方の実装の相性や境界条件を早期に確認でき、運用開始後のトラブルを減らすことができます。この点も、今回の案件を通じて強く意識するようになりました。

おわりに

最後になりますが、ここまでお読みいただきありがとうございました。今回のセッションをきっかけに、暗号技術、とくに PGP周りに少しでも興味を持っていただければ嬉しく思います。

PGPの実装は、運用の前提や性能要件によって最適解が変わってきます。ファイルサイズが十分に小さいケースでは Bouncy Castleのようなライブラリ実装が扱いやすい一方で、大容量ファイルを高速に処理したいケースではGPGコマンドの方が適している、というように、状況に応じた選択が重要です。

今回ご紹介した仕組みや判断ポイントが、みなさんが暗号化処理をシステムに組み込む際の一助となれば幸いです。

(by あなたのとなりに、決済を 編集チーム)

※本コンテンツ内容の著作権は、GMOペイメントゲートウェイ株式会社に属します。


features03_mainv.png

エンジニアイベント書き起こし記事一覧はこちら

recruit_btn.png

採用情報はこちら

feature01_btn.png

パートナーインタビュー記事一覧はこちら

PAGETOP

Copyright (C) 1995 GMO Payment Gateway, Inc. All Rights Reserved.