Yahoo! JAPANが提供するYahoo!ウォレットのAndroidアプリを開発している二階堂 遍です。
近年、街中の至る所でフリーWi-Fiを利用できるようになり、ユーザーがリッチコンテンツを屋外で楽しむ機会が増えています。ただし不用意にフリーWi-Fiに接続していると「通信内容を第三者に覗き見される」というリスクがあることも忘れてはいけません。
Yahoo!ウォレットではクレジットカードなどセキュアなユーザー情報を扱うため、通信データを覗き見されないようにHTTPS通信を利用しています。またYahoo! JAPANでは、全社的に常時SSL(AOSSL)を行っており、ユーザーの情報が外部に漏れないように配慮・対策を進めています。
Androidアプリにおいて、暗号化しない通常のHTTPで通信すると、覗き見によってAPI通信用のトークンが盗まれ、APIを通してアプリ利用者の個人情報などが不正に入手されるといった危険性があります。
グーグルもこうした課題に対処すべく、Android N(7.0)からユーザーがインストールしたルート証明書を信頼しない設定に変更したほか、Network Security Configurationを追加して、意図しないHTTP通信の抑止や信頼するルート証明書の設定が可能になりました。
こうしたセキュリティアップデートを見る限り、ユーザーの通信の安全性を担保することが、Androidアプリ開発者に求められている時代になったと強く感じています。
この記事では、Androidアプリとサーバー間でHTTPS通信するメリットと、より安全にHTTPS通信するための対策について説明します。
HTTPS通信にすることのメリット
HTTPS通信の主な役割は次の二つです。
接続先の検証
通信内容の暗号化
「接続先が正しいことと、通信内容を暗号化することで、通信が覗き見されないことを担保できますよ」というのがHTTPS通信の大きなメリットです。ここでは、この2点「接続先の検証」と「通信内容の暗号化」について解説します。
接続先の検証
HTTPS通信では、接続先のホスト名とSSL証明書のCommonNameを比較することで、接続先が正しいことを検証します。
例えば、CommonNameに「www.example.org」が設定されている接続先に、ブラウザーで「https://www.example.org/」でアクセスすると正常に画面が表示されますが、同じ接続先に「https://93.184.216.34/」のようにIPアドレスでアクセスすると証明書エラーの警告が出ます。
このように、接続先のホスト名とSSL証明書のCommonNameが一致するか検証し、一致する場合のみ通信させるようになっています。
通信内容の暗号化
HTTPS通信が暗号化されているか確認するために、通信デバッグでよく使われているCharlesを用いて、HTTPとHTTPSの通信を覗いてみましょう。
以下の構成で、Charlesを起動しているMacを経由して「www.example.org」にアクセスします。
HTTP通信とHTTPS通信でプロキシして通信内容を確認したところ、下図のようにHTTPSで通信している時は、通信内容が暗号化されていて解読できません。「HTTPSなんだから通信を覗き見できなくて当たり前」と思いがちですが、本当にそうでしょうか?
実はCharlesにはSSL通信の中身をみる機能があります。CharlesのSSLProxyの設定をONにすると、下の画像のように「https://www.example.org/」にアクセスしているにもかかわらず、通信の中身が見られてしまいます。
簡単に仕組みを説明すると、以下の通りです。
- Android端末にCharlesのルート証明書をインストール
- CharlesはAndroidアプリからリクエストを受けると、リクエスト先のホスト名のSSL証明書を発行
- Android端末にはCharlesのルート証明書がインストールしてあり、Androidアプリでは(2)で発行したSSL証明書は正当なものとして受け入れ
- Android端末<->Charles間でHTTPS通信が成立するため、Charlesで通信の中身を確認することが可能に
HTTPS通信を覗き見するには、ユーザーにルート証明書をインストールさせる必要があります。逆に言えば、アプリ側で信頼する証明書を制限することで、このような覗き見からユーザーを守ることができるのです。
通信の覗き見への対策は?
では実際に、意図しないHTTP通信の抑止と、信頼する証明書を制限する方法を説明します。
意図しないHTTP通信の抑止
「通信をすべてHTTPSにしたから大丈夫」と考えていても外部のAPIを利用している場合、意図せずHTTPへリダイレクトされてしまい、通信内容が丸見えになってしまう可能性があります。
Android M(6.0)では、AndroidManifest.xmlに
<application
android:usesCleartextTraffic="false"
... 略>
...
</application>
と書くことでHTTPを許可しない設定が可能です。
またAndroid N以降では、Network Security Configurationで、信頼するルート証明書の設定など、HTTP通信に関わるさまざまな設定が可能になりました。これらの設定を正しく行うことで、意図しないHTTP通信を抑止できます。
例えば、信頼するルート証明書は以下のように設定できます。
1. AndroidManifest.xmlにNetwork Security Configurationの設定ファイルを読み込むように指定
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<application android:networkSecurityConfig="@xml/network_security_config"
... >
...
</application>
</manifest>
2. res/xml/network_security_config.xmlにexample.orgドメインのSSL証明書の検証には、trusted_rootsを使うように設定
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">example.org</domain>
<trust-anchors>
<certificates src="@raw/trusted_roots"/>
</trust-anchors>
</domain-config>
</network-security-config>
3. res/raw/trusted_rootsに、PEM形式かDER形式のルート証明書を配置
信頼する証明書の制限
前述の通りHTTPS通信であっても、Android端末にルート証明書をインストールさせることで通信内容を覗き見できます。
ただしAndroid N(7.0)からユーザーが端末に追加インストールしたルート証明書は信頼しなくなりました。そのため、7.0以降はCharlesを用いた通信内容の覗き見ができなくなりました。なおデバック時は、信頼するルート証明書を設定すればCharlesで通信内容を確認できます。
Android M(6.0)以前の端末については、Pinningによって通信先のSSL証明書がアプリ側で意図したものかチェックするか、信頼するルート証明書を制限することでユーザーを覗き見から守ることができます。
OkHttpでも、Pinningの実装とルート証明書の制限の実装が可能なので参考にしてみてください。ただしAndroidアプリでこれらを実装すると、サーバーサイドのSSL証明書を更新する際に通信できなくなる可能性があるため、サーバーサイドのエンジニアと調整する必要があるのでご注意ください。
まとめ
この記事では、HTTPS通信を覗き見されないための対策について説明しました。単にHTTPSにするだけでなく、Pinningや信頼するルート証明書を制限することで、より安全に通信できることが分かって頂けたかと思います。
AndroidでもAndroid Nから、ユーザーがインストールしたルート証明書は信頼しなくなり、Network Security Configurationの設定が追加されたことで、プラットフォームのセキュリティに対する意識が高まっているように思います。アプリ開発者もこうした環境に合わせて、ユーザーファーストなセキュリティ環境の構築を考えねばなりません。
Androidエンジニアとして、引き続きセキュリティー情報をキャッチアップして、安全に通信できるアプリを開発していきたいですね。
著者紹介
二階堂 遍(にかいどう あまね)
ヤフー 決済金融カンパニー開発本部開発1部開発2
2012年にヤフーへ入社。「Yahoo!ポイント」や「Yahoo!カード」「Yahoo!ウォレット」といった決済システムのバックエンド開発・運用に従事。2016年より「Yahoo!ウォレット」のAndroidアプリ開発を担当している。