Firebaseの認証情報を外部に移行する
Firebaseでプロダクトを作ってたけど、色々キツくなって、やはりAWSに乗せ替えたいという話をよく聞く。
EC2の上に普通にアプリケーション組んでRDSにデータを移行すれば済む話だが、Firebase Authenticationのデータ移行ができない問題にぶち当たる。
どういうことかと言うと、パスワードがハッシュ化されてるから、移行後に全員にパスワードリセットさせないといけなくなるということだ。
しかし、これはよくある勘違いで正しくない。
Firebase Authenticationのハッシュアルゴリズムやパラメータは知ることができるので、同一アルゴリズム・パラメータでハッシュ計算をしてあげれば、パスワードリセットをさせることなくパスワード認証をすることができる。
パスワード認証を一度通した後に新しいハッシュアルゴリズム・パラメータでハッシュ計算し、以降はそのハッシュ値を認証に用いれば、徐々にFirebase Authenticationの遺産を捨てていくことができる(いわゆるrehash)。
それでは、検証用プロジェクトを作成して試していく。
認証情報の取得
まず、Firebaseに保存されている認証情報を取得する。
参考資料: https://firebase.google.com/docs/cli/auth?hl=ja
$ firebase auth:export auth.json
取得された認証情報の中身はこのようになっている。ハッシュ化されたパスワードとsaltが書かれている。
{"users": [ { "localId": "Qfh40RddndYxtiapxaiTRVIWwvq1", "email": "user@example.com", "emailVerified": false, "passwordHash": "WUBKepsUdgEv8aY8xPXvwJTajSP2ZpEylHpzrh8LyrMwjXPNJ5+ZRytsNDTY8oIwF/2QrdPMwwp0sQriJvQ4Pw==", "salt": "tgNRhCeGNElNzA==", "lastSignedInAt": "1531668171000", "createdAt": "1531667480000", "providerUserInfo": [] }]}
ハッシュ値計算
上記で取得されたハッシュ値を利用してパスワード認証する。同じハッシュ値が生成できるようになれば良い。
必要な情報は全てここに書かれている。
参考資料: https://github.com/firebase/scrypt
scryptのビルド
Firebase Authenticationにおけるscryptは独自に改変したものと書かれている。
Firebase Authentication uses an internally modified version of scrypt to hash account passwords.
ビルド方法のドキュメントに従い、独自改変されたscryptをビルドする。
参考資料: https://github.com/firebase/scrypt/blob/master/BUILDING
今回はmacで試すので、新しいopensslをhomebrewで入れておく(macに標準で入っているopensslでは動かない)。
$ brew install openssl
ビルドする。すると、scryptという名前のバイナリが生成される。
$ autoreconf -i $ ./configure CPPFLAGS="-I/usr/local/opt/openssl/include" LDFLAGS="-L/usr/local/opt/openssl/lib" $ make
ハッシュパラメータの取得
FirebaseのWebコンソール上で見ることができる。
Authenticationの画面から「パスワード ハッシュ パラメータ」を選択すると、ダイアログでパラメータが表示される。
ハッシュ値の計算
取得したハッシュパラメータを利用して、パスワードをハッシュ化する。
# Params from the project's password hash parameters base64_signer_key="QaqkkQw2B4WKY2d5WFngPnySkb75gWBoYorxnygbX9ThJDG9+5V3ZP5Qx5+Nj2tVSzcRirSxbM8L3SrnnrDhBg==" base64_salt_separator="Bw==" rounds=8 memcost=14 # Params from the exported account base64_salt="tgNRhCeGNElNzA==" # The users raw text password password="password" # Generate the hash echo `./scrypt "$base64_signer_key" "$base64_salt" "$base64_salt_separator" "$rounds" "$memcost" -P <<< "$password"`
上記のシェルを実行するとハッシュ値が出力される。auth:exportで得たハッシュ値と比較すると、値が一致していることが分かる。
$ sh hash.sh WUBKepsUdgEv8aY8xPXvwJTajSP2ZpEylHpzrh8LyrMwjXPNJ5+ZRytsNDTY8oIwF/2QrdPMwwp0sQriJvQ4Pw==
注意点
Firebase Authenticationにはauth:importという機能もある。外部の認証情報(パスワードハッシュ値、アルゴリズム、パラメータ等)を取り込む機能である。
取り込んだ後に、パスワード認証が行われたタイミングでrehashされる。逆に言うと、パスワード認証が行われていない間はrehashされない。
rehashされていない状態での問題点は、auth:exportをした際に、そのユーザのパスワードハッシュ値が空になることである。
なので、外部の認証情報を過去にFirebaseに取り込んだことがあり、それを再度Firebase外に移行させる、といった場合には注意が必要になる。