TRILL開発部の石田です。
2段階認証の設定で、QRコードを読み込んで30秒ごとに変わる6桁の数字が生成される、という仕組みをよく見かけます。 今回はSwiftでその2段階認証の仕組みを実装してみました。
2段階認証 (TOTP) とは
2段階認証にはいくつか種類があり、SMS認証やGoogle Authenticatorのようなアプリ認証、YubiKeyのようなデバイス認証の3種類が一般的です。
この記事ではGoogle Authenticatorのようなアプリ認証を実際にiOSアプリとしてSwiftで実装し、中身を紐解いていきます。
Google Authenticatorのようなアプリ認証をTOTP (Time-based One-Time Password) と言うのですが、仕組みとしてはQRコードで16文字の英数字を読み取り、その英数字と現在時刻を使って6桁の数字を生成します。 外観は以下の図のようになります。
16文字の英数字と書きましたが、正確にはBase32文字列であり、自由に設定可能な文字列ではありません。
TOTPの仕組み
2段階認証用のQRコードを解析すると以下のようなURLになっています。
otpauth://totp/Twitter:@username?secret=SH2BZBC3H7DISN6Z&issuer=Twitter
QRコード形式の場合は、QRコードと16文字の英数字 (秘密鍵) が表示されるのですが、URLのsecretにあたる部分が秘密鍵となります。 この秘密鍵を使ってワンタイムパスワードを生成します。
具体的には以下のようにパスワードを生成します。
- UnixTimeを30で割った値 (小数点切り捨て) をCounterとする
- 秘密鍵とCounterを使ってHMAC-SHA-1で20byteのハッシュ値を計算する
- ハッシュ値の下位4bitから符号なし整数Offset (0〜15) を生成する
- ハッシュ値20byteのうち、Offset番目のbyteから4byteを取り出す
- 4byteの上位1bitを取り除いたデータを数値にする
- 得られた数値のうち下位6桁がワンタイムパスワードとなる
ちなみに、パスワード生成の過程でハッシュ化やデータの切り取りを行っているので、6桁の数字から秘密鍵を逆算することは不可能です。
TOTPをSwiftで実装してみる
実装にあたり、CryptoSwift と Base32 を利用しました。
TOTPのコードは以下のようになります。
import Base32 import CryptoSwift // Base32に変換 guard let key = base32Decode("XXXXXXXXXXXXXXXX") else { return } // 1. 現在時刻からカウンターを生成 let unixTime = Int(Date().timeIntervalSince1970) let timeSteps = unixTime / 30 // 8byteのデータに変換 let counter = withUnsafeBytes(of: timeSteps.bigEndian, Array.init) // 2. HMAC-SHA-1でハッシュを生成 let hash = try! HMAC(key: key, variant: .sha1).authenticate(counter) // 3. hashの下位4bitを整数に変換 let offset = Int(hash.last! & 0b00001111) // 4. hashのoffset番目のバイトから4バイト取得 var slicedHash = Array(hash[offset ... offset + 3]) // 5. 4byteの上位1bitを取り除いたデータを数値にする slicedHash[0] = slicedHash[0] & 0b01111111 let num = Data(slicedHash).withUnsafeBytes { $0.load(as: UInt32.self).bigEndian } // 6. 下位6桁を取得 let totp = String(num).suffix(6) print(totp)
普通のiOSアプリの実装ではあまり見かけない文法がありますが、文字列に秘密鍵を入力すると、QRコードを読み込んだ2段階認証のアプリと同じ値となると思います。
まとめ
Swiftで2段階認証 (TOTP) を実装しました。
普段何気なく使っている2段階認証ですが、中身を紐解くと色々と気付きがあり、またSwiftでのビット演算など普段あまり使わない文法も知れて勉強になりました。
delyでは全方面でエンジニアを積極採用中です。 興味のある方は是非お声がけください。