中安拓也のブログ

プログラミングについて書くブログ

iOSのFace ID, Touch IDを使用したログイン機能を設計する - Part2

はじめに

以前、iOSのFace ID, Touch IDを使用したログイン機能を設計する - 中安拓也のブログ という記事を書いたんだけど、実装してて辛かったり、そもそもセキュリティ的にマズそうだったので、色々と設計を変えた。

前回の設計

前回した設計なんですが、だいたいこういう流れで、Face ID, Touch IDを実施するつもりでした。

初回ログイン

  1. UDIDを発行して、iOSデバイスのキーチェーンに格納
  2. ユーザーがログイン画面にメールアドレスとパスワードを入力してログイン
  3. この時、発行したUDIDもサーバーに送る
  4. 「UDID」と「メールアドレス・パスワード」を関連付けてDBに保存
  5. これで次回以降は、Face ID, Touch IDによる認証が有効になる

Face ID, Touch IDによるログイン

  1. Face ID or Touch IDを実行して成功する
  2. iOSデバイスのキーチェーンからUDIDを取り出す
  3. UDID+固定文字列をHMAC-SHA256形式でハッシュ化
  4. ハッシュと現在時刻を連結させたものを、AES256で暗号化、暗号化した結果をBASE64Encodeするこれをシグネチャとする
  5. シグネチャをサーバーに送る
  6. サーバー側でBASE64Decode、AES復号化、送られてきた現在時刻から10分以上経過していないことを確認する
  7. ハッシュが一致することを確認する
  8. UDIDが正しく登録されているか確認する
  9. 認証処置完了、認証トークンを発行する

前回の設計を実際に実装してみてキツかった点

サーバサイド(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 認証が成功したら、メールアドレスとパスワードを投げる設計に変更した。
通常のログイン方式でも、盗聴対策してないわけだし、ジェイルブレイクについては、端末を持っているユーザの自己責任という考え方で、設計変更については落着した。