2011年8月22日月曜日

UDIDが使えなくなりそうなので、UIIDを使えるようにしました

つい先日TechCrunchがiOS5よりUDIDの使用が非推奨になると報道し、巷はiOSでのUDIDの使用についての話題で俄然盛り上がっています。セキュリティ的によろしくないから良い変更だという声もあれば、すでに認証用として使っていてシステム改修が必要という悲観の声もあります。しかし私はどちらかというとUDIDをバリバリ使っちゃってる方陣営の人なので、セキュリティの問題については知っていても、やはりUDID相当の物が無いと不便だなぁと思ってしまうのです。そこでプログラマらしくコードで解決することにしました。

UDIDにはセキュリティの懸念があるし、もう使えなくなる。だったらもっといいIDを超簡単に生成して使えるようにすればいいじゃない。ということでUnique Installation Identifier (インストール毎ID、UIID) を生成するライブラリを早速書いてみました。
ソースコードはこちら。MITライセンスです。
https://github.com/akisute/UIApplication-UIID
昔のgistはこちら

使い方は超簡単で、
  1. アプリが一度削除されたとしても同一のUIIDを返すようにしたいのであれば、#define UIID_PERSISTENT=1して、Security.frameworkをプロジェクトに追加する
  2. UIApplication+UIID.hをimport
  3. [[UIApplication sharedApplication] uniqueInstallationIdentifier]で取れます
はいこれだけです。


■そもそもどうしてUDIDを使うのか

たいていの場合は以下のような理由です。
  • ログイン機構なしに簡易にユーザーの識別をしたいときに、毎回同じ値を返し、かつユーザー事に異なる値が取得出来る何かが必要になるので、UDIDを使う
  • UDID値の取得がいつでもどこでも可能、さらに超簡単で外部ライブラリのインストールなど面倒なことが一切無い
  • しかもAppleのドキュメントにそう使えって書いてある。・・・補足すると使ってもいいよ、ぐらいのニュアンスで、使えという風に推奨はしてないみたいです>< またUDIDのみをキーとしてユーザー情報を保存するな、とも書いてますね。


■UDIDは何がまずいの

主にまずい理由はセキュリティです。特にガラケーのかんたんログインの問題が参考になるのですが、iOSの場合は以下のようなセキュリティ問題が発生します。
  • 相手のUDIDがわかってしまえば、簡単になりすましが可能
  • UDIDの値を返すメソッドの実装を差し替えて、任意の値を返すように出来る。JailBreakしているユーザーであれば誰でも簡単に実行可能、そのためのアプリもCydiaで検索すれば転がっている
  • UDID自体、様々な方法で取得可能。アプリをインストールさせてそこ経由で集めてみたりはもちろん(その際値を取得することに対する警告すら出ない)、iTunesからでも値を確認できる
  • 上記三つの理由のため、その気になればたやすく任意のユーザーになりすませる
  • さらに恐ろしいことにUDIDは端末事に一意になるため、一度値が特定されてしまうと端末を買い換えない限りずっとなりすまされてしまう、リセットできない


■UIIDだとどうなる

Unique Installation Identifier, UIIDはUDIDと異なり「アプリのインストール基盤毎」に一意な値を返すような実装になっています。簡単にまとめると、あるアプリAが、デバイス1, 2, 3にインストールされた場合、UIIDは1, 2, 3の全てで異なる値になります。ここまではUDIDと同じですが、UDIDと違うのはデバイス1に別々のアプリA, B, Cがインストールされた場合、それぞれ異なる値になります。中身は単にCFUUIDというiOSに元からある良くできたID生成ルーチンなんですが、これにより以下のようなメリットが得られます。
  • 外部からIDの値を取得するのが困難。推測も十分に長い上に独立な値なため困難、取得も(UIID_PERSISTENT=1でビルドすればKeychainを使うので)困難です。
  • 万が一何かの間違いで流出したとしても、アプリごとに異なる値になるため他のアプリのセキュリティが犯されたりはしない。また同様の理由で他の悪意のあるアプリからUIIDを取得して攻撃することもできない。
  • プログラム的にUIIDをリセット可能。UUID_PERSISTENT=0であればアプリを消せばユーザーが任意にリセット可能。
  • Keychainを使ったメリットとして、ユーザーがデバイスを乗り換えてもiTunesのバックアップに値が保持されるため、それから復元を行った場合UIIDの値が引き継がれる。
  • 通常のアプリであればUDIDを使っていたケースのほとんどはこのUIIDでそのまま代用可能、あとは過去のUDIDとUIIDのヒモ付だけサーバー側でやってしまえば完全に乗り換えられる
  • それでもどうしてもデバイスごとに一意な値が欲しいならhttps://github.com/gekitz/UIDevice-with-UniqueIdentifier-for-iOS-5/blob/master/Classes/UIDevice+IdentifierAddition.mなど使えばよいのかなと。ただしこの実装はMACアドレスの値を使っているため、MACアドレスの値がわかってしまえばIDをたやすく生成可能で、UDIDの持つセキュリティ上の問題は残ります。


■使用上の注意

UDIDよりは問題が少ないですが、それでもこのUIIDだけで認証を行うような作りのアプリには間違ってもしないように!以下のような問題があります。
  • そもそもこの値は完全にユーザーと一意にヒモ付いた値ではありません。UIIDはあくまでアプリのインストール単位とヒモ付いているだけです。複数のデバイスを一人のユーザーが所有していたりすると完全にアウトですし、インストールされた端末が譲与された場合も対応できません。
  • プログラム的にUIIDはリセット可能なので最悪の場合でもずっとなりすまされるのは回避できるのですが、その際にヒモ付けられていたユーザー固有の情報が消えてしまいます。ログインIDとパスワードで認証をしているのであれば、ログインIDはそのままにしてパスワードだけをリセットすることで、なりすましの問題を解決しつつ、ユーザーの一意性は保ったままにできるため、万が一の際はログインIDとパスワードを使っている方が圧倒的に利便性が高まります。
  • 以上の理由により、UIIDはあくまで簡易的・一時的にユーザー認証をする時に使用し(たとえばユーザーが購入したアプリ内課金の商品をアプリが消されるまでの間だけ履歴として持っておいたり、ゲームのランキング等で匿名だけれど点数ランキングに参加できるようにしたり)、正式で完全なサービスはログインIDとパスワードによって提供するべきです。そうすることで一人のユーザーが複数のデバイスでサービスを使えるようになります。さらにはAndroidとも連携できたりしますしお得です。


■っていうか

全部高木先生が一年前に言っている通りになっちゃってるじゃないすか!っていうか私が作った物もこの高木先生がおっしゃってる「アプリ専用の(セキュアな)独自IDを生成してそれを保存して使う」というものの実装にすぎません。しかしまったく、せっかく警告してもらっても、人間実際に問題に直面しなければなかなか手をつけないものですね><

しかし、しかしですね、あえてここで一言、エンジニアとして申させていただきたい。

私、エンジニアが欲しいのはセキュリティ上正しい実装の方法だとか、概念だとか、ましてやどこそこのログイン方法はいけてないから直せや、などという文章でもないのです。我々が欲しいのは、「すでに実装されている、セキュリティ上正しくて、猿でも理解できて、1分で組み込めて、どのような環境でも動き、ユーザーが会員登録なんて面倒極まりないことをしなくても済むユーザー認証の手段」なんです。要するに、
// 何か良くわからんけどこのトークンをHTTPS経由でPOSTして認証しておけば超スーパー確実かつセキュアで猿でも実装できてハッピーになる
[[NSAuthentication sharedUser] authenticationToken]
↑コレが今すぐ欲しいんですよ、我々エンジニアというのは。そうすれば誰だってUDIDを使って認証するみたいな面倒くさいことするわけ無いじゃないですか。頼まれたってやりませんよ。

私はユーザー認証をしたいだけなんです、それも可能な限り楽に。口で何と批判しようが、正しい方法を教えようが、世の中は決して変わらないと思います。みんな楽をしたいから。なのでAppleには是非UDIDを廃止するこの機会にぜひ上記のような何かをUIKitなりFoundationなりに組み込んで欲しいですね。こういうところも、良いプラットフォーマーの責務の一部じゃないかなと。

8 コメント:

hogera さんのコメント...

危ないとわかっているけど,楽だから,金が掛からないから,納期が迫っているからコレでやっちまおう,というのはどう考えてもエンジニアの態度として正しくないと思いますが.

akisute さんのコメント...

>hogeraさん
全くおっしゃる通りです!危ないとわかってるならそんな物使わせないというのが、危ないというのを唯一判断できる立場にあるエンジニアの責務ですね。いくら上司やお客さんから要望で出されても鵜呑みにして実装するだけのような奴になってはいけないです。

で、じゃあなんでこんな記事を書いたのかと言えはそれでもやっぱり人間って楽をするほうに折れてしまうものだと思ったからなのです。例えば、

・本当は普通に会員登録するシステムを作りたかったのだけれど、そうすると会員登録でのドロップ率が9割以上になってユーザーが集まらない。サービスの運営が成り立たなくて自分の会社が潰れて死にそう。かんたんログインさん助けて。
・勢いで仕事を取ったのはいいけど、誰もまともな認証システムが作れない。ヤバいこのままでは大変なことに。あ、UDIDとかあるじゃんこれ使えば認証できるんじゃね俺天才hahaha
・納期2日前になって突然使用変更とか言われて会員サービスとの連携が必要になった。ふざけんな!まともに実装する時間ない。ノウキハゼッタイとか相手の担当者の人が言ってる。ヤバいどうしよう。よしこうなったら少々セキュリティ的に問題あるかもしれないがUDIDを使って・・・

とかこういう場面になっちゃったら普通楽する方に折れるよね、と。

実際私も弱いほうの人間ですから、そういう方向に流れる人たちを批難することがとてもできないのです。 おそらく私も同じ立場で同じように追い詰められれば同じように悪い方法に逃げるでしょうし。そこで大体これまでと同じ用途で使えるけど、より安全な実装を作って公開してすぐに使えるようにして、さぁみんなで楽しようじゃん、と思ってやってみた次第です。ほんとはプラットフォーム側がこういうのを整備してくれれば嬉しいんですけれど。たとえばモダンなWebアプリケーションフレームワークで、テンプレート出力値のサニタイズが自動的に行われる(安全側に倒されている)のが当たり前、みたいな感じが理想かなーと。

どうでしょうか?ちょっと内容がセキュリティのことを考えない類の人を擁護するみたいになっちゃったのが良くないですかね?>< すみません、そういうつもりは毛頭ないのです。単に一番簡単で誰でも真っ先にやる実装が一番セキュアなのが一番いいよね、と言いたいのです。

hogera さんのコメント...

>普通楽する方に折れるよね
一般的にはそのとおりだと思います.挙げられている場面でそのような行動を取るかは同意できませんが.
絶対安全と言い切れる実装はそんなにないので,予期できる危険性とのトレードオフだとは思います.どこで天秤を傾けるかがエンジニアの良心かと.

解決策を提示されている方に対して失礼かとは思いましたが,元エントリの最後の部分がどうしても気になってしまい,コメントしました.失礼しました.

akisute さんのコメント...

>hogeraさん
すんません、たとえが突飛で>< それからおっしゃるとおり、予期できる危険性と利便性によるトレードオフが判断できるだけの知識がやはり開発者に求められますね。知識はあるけど車輪の再発明とかしたくねぇ!楽しようぜ!ってのが良いかなと。知識がないと良い実装を用意しても結局わざわざ間違って悪い使い方をしちゃう、なんてことがありそうですしね・・・Webアプリケーションフレームワークの例だと、わざわざ「便利だから」とかいう勘違いした理由で全部のサニタイズを切ってテンプレートに出力するみたいな使い方をするとか。

あー、例の最後の段落については私の書き方が良くないところが多分にありますのでむしろこちらがごめんなさい><

匿名 さんのコメント...

> 要するにあるアプリAが、デバイス1, 2, 3にインストールされた場合、UIIDは1, 2, 3の全てで異なる値になります

それはUDIDでも同じではないでしょうか?

akisute さんのコメント...

おを!すみませんこれじゃUDIDと同じですね。ということで説明書き直しておきます。突っ込みありがとうございます!

naruse さんのコメント...

こんにちは。
使わせていただこうかなと思ったんですが、このコードのライセンスは何になるのでしょうか。

akisute さんのコメント...

> naruseさん
本当だ、すんません!ライセンス表記が漏れてた!!MITにする予定です。今からgitと本文に追記します。ご指摘ありがとうございました!