ワンタイムパスワードの生成
JSRSASIGNライブラリを利用してjavascriptで
Google Authenticatorなどメジャーのワンタイムパスワードアプリと互換性ある
ワンタイムパスワードの生成方法を紹介いたします。
ワンタイムパスワードとは
ワンタイムパスワードとは、認証を行うためのご利用時に都度変更される1回限りのパスワードです。
ワンタイムパスワードの仕組み
時刻同期方式とチャレンジアンドレスポンス方式があります。
アルゴリズムはタイムベース (TOTP、RFC 6238 に準拠)
またはカウンターベース (HOTP、RFC 4226 に準拠)があります。
HOTPは、HMACを使用したカウンターベースのワンタイムパスワードを生成するアルゴリズムになっている。
TOTPは、HOTPのカウンター値を現在のエポック時刻にしている特殊のHOTPです。
GoogleやFacebookなど二段階認証に時刻同期方式のワンタイムパスワード(TOTP)が使われています。
エンドユーザーとサーバー間を共有シークレットを持つ、お互いの時刻を同期し、
シークレットと時刻を基にパスワードが生成されるのです。
実装
TOTP = HOTP(シークレット, エポック時刻)
HOTP(K, C) = Truncate(HMAC(K, C)) & 0x7FFFFFFF
# シークレットは事前にサーバーから提供されたもの
# HMACはRFC2104で、TruncateのマナーはRFC4226でそれぞれ定義されている
# 剰余オペレーターを通して、指定桁数になるよう整形
# ハッシュ関数HMAC-SHA-1がデファクト、HMAC-SHA-256 or HMAC-SHA-512が使われる場合もあります。
ソースコード
<script type="text/javascript" src="jsrsasign-all-min.js"></script>
<script type="text/javascript">
function totp() {
// 共有シークレット Google AuthenticatorはBase32文字列、SlinkPassはBase64文字列。
// ワンタイムパスワードの時間間隔 Google Authenticatorは30秒、SlinkPassは60秒。
// ワンタイムパスワードの桁数 Google Authenticatorは6桁、 SlinkPassは8桁。
// 現在のエポック時刻を基にカウンタ計算
var secret = base32tohex('Google共有シークレット');
// var secret = b64tohex('Secioss共有シークレット');
var digits = 6;
var period = 30;
var epoch = new Date().getTime() / 1000;
// カウンタとシークレットを十六進に変換、8バイトとのカウンター値を取得
var count = parseInt(epoch / period);
var arrCnt = new Array(8);
for (i = arrCnt.length - 1; i >= 0; i--) {
arrCnt[i] = dec2hex(count & 0xff);
count >>= 8;
}
var hexCnt = CryptoJS.enc.Hex.parse(arrCnt.join(''));
var hexSrt = CryptoJS.enc.Hex.parse(secret);
// HMAC-SHA1でハッシュ値を生成
var hexHmac = CryptoJS.HmacSHA1(hexCnt, hexSrt).toString(CryptoJS.enc.Hex);
// ハッシュ値の20バイト目の下位4ビットを取り出しオフセット値とする
var arrHmac = new Array();
for (i = 0; i < hexHmac.length; i = i + 2) {
arrHmac.push(hex2dec(hexHmac.substr(i, 2)));
}
var offset = arrHmac[arrHmac.length - 1] & 0xf;
// オフセット値をハッシュ値のバイト列に当てはめ、そこから31ビット取り出す
var truncate = hex2dec(hexHmac.substr(offset * 2, 8)) & 0x7fffffff;
// 出力する桁数に合わせて切り詰める
var otp = truncate % Math.pow(10, digits);
// 桁数足りなかったら、前頭に0を補足してさい。
while (otp.toString().length < digits) {
otp = '0' + otp;
}
return otp;
}
// ライブラリ
function hex2dec(h) { return parseInt(h, 16); }
function dec2hex(d) { return ('0' + (Number(d).toString(16))).slice(-2); }
function leftpad(str, len, pad) {
if (len + 1 >= str.length) {
str = Array(len + 1 - str.length).join(pad) + str;
}
return str;
}
function base32tohex(base32) {
var base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
var bits = "";
var hex = "";
for (var i = 0; i < base32.length; i++) {
var val = base32chars.indexOf(base32.charAt(i).toUpperCase());
bits += leftpad(val.toString(2), 5, '0');
}
for (var i = 0; i+4 <= bits.length; i+=4) {
var chunk = bits.substr(i, 4);
hex = hex + parseInt(chunk, 2).toString(16) ;
}
return hex;
}
</script>
|
その他
QRコードはGoogle APIで生成してくれます。
https://chart.googleapis.com/chart?cht=qr&chs=180x180&chld=L|0&&chl=<URLエンコードしたURI>
URI
Google Authenticator の場合、otpauth://totp/username/?secret=<Base32シークレット>&issuer=<認証先>&digits=6
SlinkPass の場合、 slinkotp://totp/username/?secret=<Base64シークレット>&issuer=<認証先>&digits=8