JavaでGoogle Authenticatorアプリ(以下アプリ)用のQRコードをZXing(ゼブラクロッシングらしい)を使って作成する方法。
ネットで調べても日本語の情報がヒットしないので、記事に残しておきます。
このあたりを参考にしました。
https://remotestance.com/blog/2940/
https://github.com/google/google-authenticator/wiki/Key-Uri-Format
http://weblabo.oscasierra.net/java-zxing-3/
まず実際にQRコードを描画する部分のコードを載せます。
String contents = "otpauth://totp/" + serviceName + ":" + userId+
"?secret=" + secretKey +
"&issuer=" + issuer +
"&algorithm=SHA1" +
"&digits=6" +
"&period=30";
BarcodeFormat format = BarcodeFormat.QR_CODE;
int width = 320;
int height = 320;
Hashtable hints = new Hashtable();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
QRCodeWriter writer = new QRCodeWriter();
BitMatrix bitMatrix = writer.encode(contents, format, width, height, hints);
BufferedImage image = MatrixToImageWriter.toBufferedImage(bitMatrix);
コード中に唐突に登場している変数の説明。
serviceName : サービスの名前
userId:ユーザを識別する名前
issuer:発行者名
secretKey:鍵文字列
上3つはキーの名前を作成するために使い、4つめのsecretKeyがseedの文字列です。
例えば、serviceNameをTokyo、IssuerをJapan、userIdをChuoとすると、アプリに表示されるキーの名前は、「Japan(Tokyo:Chuo)」となります。
ただ表示されるだけでなく、アプリで同じ名前のQRコードを読み取ると前に保存したものが勝手に上書きされてしまうので、別サービスで同じ名前を使わないように注意が必要です。
algorithm、digits、periodは指定してもしなくても動作は変わりません。今後互換アプリが登場しても仕様が変わるわけもなく、QRコードをすっきりさせたい人は消したほうがいいかも。
secreteKeyにはbase32エンコードした鍵文字列をセットします。
Google Authenticatorを使うということは、当然サーバー側でOTP(ワンタイムパスワード)を作成する処理が入るので、それとの整合性を考えなくてはいけません。
Javaで実装する場合、おそらくOTP生成に一番つかわれるのがこれ
https://tools.ietf.org/html/rfc6238
でしょう。
RFC6238に含まれるjavaコード部分をTOTP.javaに丸ごとコピーしてTOTPクラスを作成したとしてのOTP作成部分のコード例をあげます。
String seed = DatatypeConverter.printHexBinary(SecureRandom.getSeed(20));
Date date = new Date();
long T0 = 0;
long X = 30;
long testTime = date.getTime()/1000;
long T = (testTime - T0)/X;
String steps = Long.toHexString(T).toUpperCase();
while (steps.length() < 16) steps = "0" + steps;
String otp = TOTP.generateTOTP(seed, steps, "6");
アプリはSHA1にしか対応していないので、seedの長さは20byte(16進数で40文字)、呼び出す関数はSHA1用のものです。
このseedをどこかに保存しておきます。
そしてseedから鍵文字列を作成するコードがこんなかんじ。
byte byte_seed[] = DatatypeConverter.parseHexBinary(seed);
String secretKey = new Base32().encodeToString(byte_seed);
でてきたsecretKeyをQRコード用文字列のsecretKeyとして使います。