Androidにおけるセキュリティ設計と動作(後編)
※この記事は、書籍『Android Security 安全なアプリケーションを作成するために』の第3章の内容を、ThinkIT向けに特別にオンラインで公開しているものです。詳しくは記事末尾の書籍紹介欄をご覧ください。
今回は、前回の「第3回 Androidにおけるセキュリティ設計と動作(前編)」に続いて、アンドロイドのセキュリティ関連の設計と動作について技術的な観点から見ていきます。
3.5 固有識別子
多くのWebアプリケーションでは、ユーザがログインIDとパスワードを入力してサーバにログインし、そのユーザのデータにアクセスします。ユーザがアカウントを作成したり、入力ボックスにパスワードを入力したりするのは非常に手間がかかります。このためWebの世界では、Cookieを利用することで、実際にはログインしていなくてもログインしているかのように扱うケースがあります。たとえばAmazonでは、過去の履歴情報をCookieに保存することで、Cookieの情報に基づいてユーザを識別し、ユーザに表示する画面を変更しています。ユーザが実際にログインするのは商品を購入する段階になったときです。このようにセキュアにする必要がある部分でのみログインを要求することで、ユーザの利便性を向上させているのです。
サーバ側でユーザを区別したいが、ユーザにとってアカウントを作成する必然性がない、という場合は、プログラム上で何らかの重複しないグローバル識別子を作成する必要があります。
本節では、この一意な識別子について見ていきます。
3.5.1 識別子の生成
代表的な例は、UUID(Universally Unique Identifier)を生成することです。
リスト3-4 UUIDの生成例※2
public class Installation { private static String sID = null; private static final String INSTALLATION = "INSTALLATION"; public synchronized static String id(Context context) { if (sID == null) { File installation = new File(context.getFilesDir(), INSTALLATION); try { if (!installation.exists()) writeInstallationFile(installation); sID = readInstallationFile(installation); } catch (Exception e) { throw new RuntimeException(e); } } return sID; } private static String readInstallationFile(File installation) throws IOException { RandomAccessFile f = new RandomAccessFile(installation, "r"); byte[] bytes = new byte[(int) f.length()]; f.readFully(bytes); f.close(); return new String(bytes); } private static void writeInstallationFile(File installation) throws IOException { FileOutputStream out = new FileOutputStream(installation); String id = UUID.randomUUID().toString(); out.write(id.getBytes()); out.close(); } }
※2 参照:Android Developers Blog「Identifying App Installations」
アプリケーションが最初に実行されるときに、上記のようなコードを使ってUUIDを生成し、ファイルに保存します。次回以降は、アプリケーションの起動時にこのファイルからUUIDを取り出すことで、アプリケーションがアンインストールされるまで同じIDを使用できます。アプリケーションを一度アンインストールしてから再度インストールした場合、最初のIDとは異なるIDが生成されますが、IDの値が変化しても通常は問題ありません。問題となる場合は、一般に、ユーザIDとパスワードによる認証を行う必要があるでしょう。
リスト3-4はアンドロイド上でUUIDを生成する例ですが、サーバ側で生成したUUIDを使用することもできます。
3.5.2 ハードウェアに関する識別子
- IMEI
- IMEI(International Mobile Equipment Identifier)は、端末識別番号です。IMEIは携帯電話固有の番号であり、生産国、メーカー名などの組み合わせによる15桁の数字で構成されています。
IMEIを取得するには、READ_PHONE_STATEパーミッションが必要です。
リスト3-5 IMEIの取得例
TelephonyManager telephonyManager = (TelephonyManager)getSystemService(TELEPHONY_SERVICE); // GSMのIMEIにはREAD_PHONE_STATEが必要 String deviceId = telephonyManager.getDeviceId(); Log.v(TAG, "deviceId(IMEI)=" + deviceId); // GSMのIMEI/SVにはREAD_PHONE_STATEが必要 String deviceSoftwareVersion = telephonyManager.getDeviceSoftwareVersion(); Log.v(TAG, "deviceSoftwareVersion(IMEI/SV)=" + deviceSoftwareVersion);
リスト3-6 出力例※3
10-24 14:20:53.173: V/Activity(11558): deviceId(IMEI)=254049043147024 10-24 14:20:53.173: V/Activity(11558): deviceSoftwareVersion(IMEI/SV)=00
IMEIをユーザと紐付けすることは、以下の理由により、あまり意味を持ちません。
- ユーザが新しい携帯電話に変えるとIMEIが変化する。
- 同じユーザが携帯電話を2つ持っていることがある。
- 携帯電話が中古品として販売され、他のユーザが使用することがある。
また、IMEIは携帯電話に付加される番号です。アンドロイドOSは携帯電話だけでなく、フォトフレーム、データ通信契約が必要ないタブレット、その他さまざまなデバイスで動いています。こうした携帯電話ではないデバイスでIMEIを取得すると、固定値が返されたり、値がなかったりします。また、端末によってはバグがあり、ゼロやアスタリスク(*)を返すこともあります。
さらに、IMEIを取得するには、READ_PHONE_STATEパーミッションが必要です。このパーミッションは電話番号の取得も可能にするパーミッションです。作成するアプリケーションが通信を行い、かつREAD_PHONE_STATEパーミッションがある場合は、電話番号をサーバに送って収集していると疑われる可能性があります。
IMEIを取得するかどうかについては、十分に検討した上で決定してください。
※3 これ以降、出力例に表示されている値は一部変更を加えています。