Rhythm & Biology

Engineering, Science, et al.

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の画面から「パスワード ハッシュ パラメータ」を選択すると、ダイアログでパラメータが表示される。 f:id:aka_mythosil:20180716103034p:plain f:id:aka_mythosil:20180716103041p:plain

ハッシュ値の計算

取得したハッシュパラメータを利用して、パスワードをハッシュ化する。

# 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外に移行させる、といった場合には注意が必要になる。