Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save asufana/daaa0477df93a5a2870c to your computer and use it in GitHub Desktop.
Save asufana/daaa0477df93a5a2870c to your computer and use it in GitHub Desktop.
ソフトウェア2段階認証

ソフトウェア2段階認証

ソフトウェアトークン生成器による2段階認証(Two-factor authentication)の仕様と実装について。

2段階認証?2要素認証?

Googleではこの認証機能を 2-Step Verification と呼んでいる。一般的には Two-factor authentication、あるいは Multi-factor authentication と呼ばれている様子。

和訳の定義はまちまちだが、

  • パスワード認証後に、追加認証キーを求めるものを「2段階認証」
  • パスワードと同時に追加認証キーを求めるものを「2要素認証」

と呼んでいる様子。一応使い分けられているらしい。

2段階認証の選択肢

2段階認証の主な提供種別。

  • SMS
  • Email
  • 電話
  • ハードウェアトークン
  • ソフトウェアトークン

サービス提供者とすれば、SMSは通知コストがかかり、また100%到達しないことを考えると、ソフトウェアトークンでの対応が望ましい。ただし利用者にリテラシーを求めるため難しい。

またソフトウェアトークン時は機種変更時の秘密鍵紛失対応コストがかかると思われる(後述)

ソフトウェアトークン生成による利用手順

iPhoneアプリなどソフトウェアトークンを用いた2段階認証の手順を示す。

参考:http://www.ajaxtower.jp/gmail/2step-verify/index5.html

  • 当該サイトにログオン
  • 2段階認証設定画面からQRコードを取得
  • iPhoneのソフトウェアトークンを起動し、QRコードを読み込み
  • 認証APに表示されるワンタイムパスワード(6桁の数字列)を当該サイトに入力

どのように認証するのか

  • 安全な方法でサーバ側とクライアント側で同じ秘密鍵を共有する
  • 共有した秘密鍵と現在時刻を用いて、クライアントがワンタイムパスワードを生成する
  • サーバ側は、同じ計算方法でパスワードを生成し、クライアントから渡された値と一致するかどうか確認する

どのようにセキュリティを担保するのか

秘密鍵と時刻を用いてワンタイムパスワードを生成する方法は、RFC4226 で仕様化されているため、セキュリティを担保する要素は、秘密鍵の授受方法と保存方法のみ。

授受方法

  • 秘密鍵文字列をQRコード化して画面表示し、授受する
  • QRコードの読み取りにはスマートフォンなどのカメラ機能が必要なため、当該スマートフォン上ですべて処理できる
  • 文字列で受け渡すとメールなどで授受されるため、漏洩する可能性が高くなる

保持方法

  • クライアントから秘密鍵を取り出せないようにすることで、当該スマートフォンのみでワンタイムパスワードを生成できるようにする
  • エクスポートできない
  • 復元時にデータを復元しない

運用リスク

  • 当該スマートフォンを紛失する、あるいは機種変更などで秘密鍵が失われるとログオンできなくなる
  • 対処方法としては、
  • 機種変更前に当該サイトでデバイス変更処理を行う
  • 当該サイトがSMSでの2段階認証にも対応しているようであれば、設定しておく
  • バックアップコードを保持しておく
  • 秘密鍵文字列、あるいはQRコードをカメラで撮っておく
  • サービス提供者に個人認証することで解除してもらう

秘密鍵の例

QRコードをデコードすると以下の文字列が設定されている

  • otpauth://totp/github.com/asufana?issuer=GitHub&secret=hc5qkbe7lj7crunq
  • otpauth://(パスワードの生成方式)/(ユーザを一意に特定するURL)?secret=(秘密鍵文字列)

#このように秘密鍵を公開しても、この鍵で2段階認証を設定していなければリスクはない(サーバが提示する秘密鍵は設定毎に更新されるため)

ワンタイムパスワード生成の仕様

以下の計算式で生成できる。

  • ユーザとサーバしか知らない秘密鍵 + カウンタ値 = ワンタイムパスワード

ユーザとサーバしか知らない秘密鍵

  • クライアントとサーバとで共有する
  • 一般的にはサーバが随時提供する秘密鍵を、クライアント側が保持する形で共有する

カウンタ値の生成

  • HOTP:回数ベース RFC4226

  • クライアントはパスワードを生成した回数を、サーバは認証処理を行った回数を保持し、その値を一致させる

  • 回数がずれる場合を考慮し、カウンタを同期させる機能が必要

  • TOTP:時刻ベース RFC6238

  • 一定時間毎に現在時刻を用いてパスワードを生成する

  • クライアントとサーバがそれぞれ正しい時刻を設定していなければならない

ソフトウェアトークン器

カウンタ値の生成と、ワンタイムパスワードの生成ロジックは仕様化されているため、ソフトウェアトークン器は汎用的に提供することができる。

汎用トークン対応サイト

一般的なサービス事業者は、6桁/30秒でカウンタ生成している。

2段階認証のサーバサイド実装

時刻ベースTOTP仕様での実装

1. 秘密鍵の生成

  • Base32で20文字以上(推奨)の文字列を生成

2. タイムコードの生成

  • UNIX時間(エポック)をインターバル秒で割る
  • インターバル秒は多くのサービスサイトで30秒で設定されている
  • 要は30秒間同じタイムコードを生成する

3. パスワードの生成

  • シークレットキーとタイムコードからハッシュ関数とビット演算を行いワンタイムパスワード文字列を生成する

Javaでの実装例

参考:https://github.com/kamranzafar/libotp

テストコード

OTP.generate("12345678", "" + System.currentTimeMillis(), 6, "totp")

パスワード生成コード

   /**
    * @param key 秘密鍵
    * @param time 現在時刻
    * @param returnDigits ワンタイムパスワード文字数
    * @param crypto ハッシュアルゴリズム
    */
	public static String generateTOTP(String key, String time, String returnDigits, String crypto) {
       //パスワード文字数
		int codeDigits = Integer.decode(returnDigits).intValue();
		String result = null;

    //これよくわからない。。
		// Using the counter
		// First 8 bytes are for the movingFactor
		// Compliant with base RFC 4226 (HOTP)
		while (time.length() < 16)
			time = "0" + time;

		//バイト列に変換
		byte[] msg = hexStr2Bytes(time);
		byte[] k = hexStr2Bytes(key);
		
		//HMACダイジェストの取得
		byte[] hash = hmac_sha(crypto, k, msg);

		//最後のバイトをOxfでマスク
		int offset = hash[hash.length - 1] & 0xf;
		
		//オフセットしてビット演算
		int binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16)
				| ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);

       //10のパスワード桁数分の乗数で割り、余りを得る
		int otp = binary % ((int) Math.pow(10, codeDigits));

       //余りがパスワードになる(桁不足していたら0で埋める)
		result = Integer.toString(otp);
		while (result.length() < codeDigits) {
			result = "0" + result;
		}
		return result;
	}

その他の言語実装例:Perl実装 Python実装 Ruby実装

その他

2段階認証を提供しているサイト一覧

Linuxサーバ認証にワンタイムパスワードを利用する

ソーシャルエンジニアリングによるクラック

2段階認証を適切に設定していたとしても、サービス提供会社の運用が甘ければどうしようもない。

エンタープライズでの2段階認証鍵の管理

ソフトウェアトークンであれば、USBキーやFeliCaカードなど物理デバイスなしでの認証強化が可能。鍵管理を一元化できればエンタープライズ環境でも活用できるのでは?

課題は鍵管理。各個人で管理させるには展開と紛失時対応コストが大きくなる。

  • Bookmarkletを使ってQRコードを読み取り
  • その秘密鍵をサーバサイドに配置したトークン生成器に渡す
  • サーバサイドでワンタイムパスワードを生成
  • 個人ポータルなどと組み合わせたらいい感じになりそう
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment