Last active
January 13, 2016 06:19
-
-
Save yusukemurayama/0b1f75abf237f9842ce1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// https://github.com/ChristianRiesen/base32 を利用しました。 | |
require_once 'Base32.php'; | |
use Base32\Base32; | |
class Totp { | |
/** | |
* コンストラクタ | |
* | |
* @param string $secret_key 秘密鍵 | |
* @param int $token_length 生成するトークンの長さ | |
* @param int $period トークン生成間隔 | |
* @param string $algorithm アルゴリズム(sha1, sha256, sha512) | |
*/ | |
function __construct($secret_key, $token_length = 6, $period = 30, $algorithm = 'sha1') { | |
$this->secret_key = $secret_key; | |
$this->token_length = $token_length; | |
$this->period = $period; | |
$this->algorithm = $algorithm; | |
} | |
/** | |
* トークンを生成します。 | |
* | |
* @param int clock TOTPトークン生成に使うタイムスタンプ | |
* @return string 生成したトークン | |
*/ | |
public function generate($clock = null) { | |
if ($clock === null) { | |
// clockの指定がない場合は、現在のタイムスタンプを取得します。 | |
// ※clockを指定するケースは、現在時刻より30秒前や、30秒後のトークンを取得したい場合です。 | |
$clock = time(); | |
} | |
// 秘密鍵をBase32でデコードします。 | |
$key = Base32::decode($this->secret_key); | |
# 以下、TOTPの仕様(RFC6238)を参考にトークンを生成します。 | |
# 参考: https://tools.ietf.org/html/rfc6238 | |
$moving_factor = floor($clock / $this->period); | |
$b = []; | |
while ($moving_factor > 0) { | |
$b[] = chr($moving_factor & 0xff); | |
$moving_factor >>= 8; | |
} | |
$text = str_pad(implode('', array_reverse($b)), 8, "\0", STR_PAD_LEFT); | |
$hash = hash_hmac($this->algorithm, $text, $key, true); | |
$offset = ord($hash[19]) & 0xf; | |
$token_base = (ord($hash[$offset]) & 0x7f) << 24 | |
| (ord($hash[$offset + 1]) & 0xff) << 16 | |
| (ord($hash[$offset + 2]) & 0xff) << 8 | |
| (ord($hash[$offset + 3]) & 0xff); | |
// 規定の長さを取り出します。 | |
$token = $token_base % pow(10, $this->token_length); | |
// 桁数が足りない場合は0をつめて返します。 | |
return str_pad($token, $this->token_length, 0, STR_PAD_LEFT); | |
} | |
/** | |
* トークンが正しいかをチェックします。 | |
* | |
* @param string $token チェック対象のトークン | |
* @return boolean 引数のトークンと生成したトークンが一致すればTRUE、しない場合はFALSE | |
*/ | |
public function is_valid_old($token) { | |
if (gettype($token) !== 'string') { | |
// string型でない場合はFALSEを返します。 | |
return false; | |
} | |
if (!preg_match(sprintf('/[0-9]{%d}/', $this->token_length), $token)) { | |
// 0から9がN個並んでいない場合はFALSEを返します。 | |
return false; | |
} | |
return $token === $this->generate(); | |
} | |
/** | |
* トークンが正しいかをチェックします。 | |
* | |
* @param string $token チェック対象のトークン | |
* @return boolean 引数のトークンと生成したトークンが一致すればTRUE、しない場合はFALSE | |
*/ | |
public function is_valid($token) { | |
if (gettype($token) !== 'string') { | |
// string型でない場合はFALSEを返します。 | |
return false; | |
} | |
if (!preg_match(sprintf('/[0-9]{%d}/', $this->token_length), $token)) { | |
// 0から9がN個並んでいない場合はFALSEを返します。 | |
return false; | |
} | |
$clock = time(); | |
foreach ([0, -1, 1] as $idx) { | |
// 時計のズレなどを考慮して、前後1つもチェックし、一致していればOKとします。 | |
if ($token === $this->generate($clock + $idx * $this->period)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment