はじめに
以前、iOSのFace ID, Touch IDを使用したログイン機能を設計する - 中安拓也のブログ という記事を書いたんだけど、実装してて辛かったり、そもそもセキュリティ的にマズそうだったので、色々と設計を変えた。
前回の設計
前回した設計なんですが、だいたいこういう流れで、Face ID, Touch IDを実施するつもりでした。
初回ログイン
- UDIDを発行して、iOSデバイスのキーチェーンに格納
- ユーザーがログイン画面にメールアドレスとパスワードを入力してログイン
- この時、発行したUDIDもサーバーに送る
- 「UDID」と「メールアドレス・パスワード」を関連付けてDBに保存
- これで次回以降は、Face ID, Touch IDによる認証が有効になる
Face ID, Touch IDによるログイン
- Face ID or Touch IDを実行して成功する
- iOSデバイスのキーチェーンからUDIDを取り出す
- UDID+固定文字列をHMAC-SHA256形式でハッシュ化
- ハッシュと現在時刻を連結させたものを、AES256で暗号化、暗号化した結果をBASE64Encodeするこれをシグネチャとする
- シグネチャをサーバーに送る
- サーバー側でBASE64Decode、AES復号化、送られてきた現在時刻から10分以上経過していないことを確認する
- ハッシュが一致することを確認する
- UDIDが正しく登録されているか確認する
- 認証処置完了、認証トークンを発行する
前回の設計を実際に実装してみてキツかった点
サーバサイド(Java)で復号化するのがキツイ
フロントエンド(Angular/TypeScript)で暗号化するのは、crypt-jsを使うことで簡単にできたんだけど、サーバサイド(Spring/Java)で復号のコードを書くのが、量多くて辛い。(参考サイト)
暗号化と復号化に使用する共通鍵について考慮してなかった
ハッシュ化したりAESで暗号化するには、共通鍵が必要になるわけだけど、共通鍵にどのような仕組みを使うか考えてなかった。固定のパスフレーズを共通鍵にすると、それ自体がセキュリティホールになるし、どうしよ...
前回の設計のセキュリティ的にまずい点
UDIDじゃなくて、UUIDだよね?
端末固有の番号であるUDID(Unique Device Identifier)については、Cordovaのプラグインを使用して、取得する予定だったけど、そもそもUDIDは、本アプリから以外でも入手可能な番号だから、認証で使うのはまずいよね。。。せめて、UUID(Universally Unique Identifier)にしよう...
前回の設計のおさらい
UUIDじゃなくて、UDIDを使っているのは単に自分の勘違いなので、無視するとしても、前回の設計では下記の点を念頭に置いていました。
- ジェイルブレイク対策
- Keychainにただメールアドレスとパスワードを保存する方式だと、Keychainのセキュリティが破られた時に不安なので、わざわざUUIDを暗号化してから、Keychainに入れる方式にしていた
- 盗聴対策
- 通信途中にデータを奪われても、暗号化されているから安心!
今回の設計
前回の設計は、セキュリティ的には優れていたにせよ、実装が大変なので、単純にiOSのKeychainにメールアドレスとパスワードを格納して、Face ID/Touch ID 認証が成功したら、メールアドレスとパスワードを投げる設計に変更した。
通常のログイン方式でも、盗聴対策してないわけだし、ジェイルブレイクについては、端末を持っているユーザの自己責任という考え方で、設計変更については落着した。