PluginProbe ʕ •ᴥ•ʔ
CloudSecure WP Security / 1.1.0
CloudSecure WP Security v1.1.0
1.4.10 1.4.9 trunk 0.9.0 1.0.2 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.3.1 1.3.10 1.3.11 1.3.12 1.3.13 1.3.14 1.3.15 1.3.16 1.3.17 1.3.18 1.3.19 1.3.2 1.3.20 1.3.21 1.3.22 1.3.23 1.3.24 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8
cloudsecure-wp-security / modules / lib / class-time-based-one-time-password.php
cloudsecure-wp-security / modules / lib Last commit date
class-time-based-one-time-password.php 2 years ago
class-time-based-one-time-password.php
152 lines
1 <?php
2
3 /**
4 * タイムベースドワンタイムパスワードアルゴリズムの2段階認証のためのクラス
5 */
6 class CloudSecureWP_Time_Based_One_Time_Password {
7 private static $digits = 6;
8
9 /**
10 * 指定されたシークレットと時点を使用してコードを計算
11 *
12 * @param string $secret
13 * @param int|null $time_slice
14 *
15 * @return string
16 */
17 public static function get_code( string $secret, int $time_slice = null ): string {
18 if ( $time_slice === null ) {
19 $time_slice = floor( time() / 30 );
20 }
21
22 $secret_key = self::base32_decode( $secret );
23
24 // 時間をバイナリ文字列にパック
25 $time = chr( 0 ) . chr( 0 ) . chr( 0 ) . chr( 0 ) . pack( 'N*', $time_slice );
26 // ユーザーの秘密鍵でハッシュ
27 $hm = hash_hmac( 'SHA1', $time, $secret_key, true );
28 // 結果の最後のニップルをインデックス/オフセットとして使用
29 $offset = ord( substr( $hm, - 1 ) ) & 0x0F;
30 // 結果の4バイトを取得
31 $hash_part = substr( $hm, $offset, 4 );
32
33 // バイナリ値を切り出す
34 $value = unpack( 'N', $hash_part );
35 $value = $value[1];
36 // 32ビットのみ
37 $value = $value & 0x7FFFFFFF;
38
39 $modulo = pow( 10, self::$digits );
40
41 return str_pad( $value % $modulo, self::$digits, '0', STR_PAD_LEFT );
42 }
43
44 /**
45 * コードが正しいかどうかを検証
46 * $discrepancy*30 秒前から今から $discrepancy*30 秒までのコードを受け�
47 �れます。
48 *
49 * @param string $secret
50 * @param string $code
51 * @param int $discrepancy 30 秒単位で許容される時間のずれ (8 は前後 4 分を意味します)
52 * @param int|null $current_time_slice
53 *
54 * @return bool
55 */
56 public static function verify_code( string $secret, string $code, int $discrepancy = 1, int $current_time_slice = null ): bool {
57 if ( $current_time_slice === null ) {
58 $current_time_slice = floor( time() / 30 );
59 }
60
61 if ( strlen( $code ) !== 6 ) {
62 return false;
63 }
64
65 for ( $i = - $discrepancy; $i <= $discrepancy; ++$i ) {
66 $calculated_code = self::get_code( $secret, $current_time_slice + $i );
67 if ( self::timing_safe_equals( $calculated_code, $code ) ) {
68 return true;
69 }
70 }
71
72 return false;
73 }
74
75 /**
76 * Base32をデコード
77 *
78 * @param $secret
79 *
80 * @return bool|string
81 */
82 protected static function base32_decode( $secret ) {
83 if ( empty( $secret ) ) {
84 return '';
85 }
86
87 $base32_chars = str_split( 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=' );
88 $base32_chars_flipped = array_flip( $base32_chars );
89
90 $padding_char_count = substr_count( $secret, $base32_chars[32] );
91 $allowed_values = array( 6, 4, 3, 1, 0 );
92 if ( ! in_array( $padding_char_count, $allowed_values ) ) {
93 return false;
94 }
95 for ( $i = 0; $i < 4; ++$i ) {
96 if ( $padding_char_count === $allowed_values[ $i ] &&
97 substr( $secret, - ( $allowed_values[ $i ] ) ) !== str_repeat( $base32_chars[32], $allowed_values[ $i ] ) ) {
98 return false;
99 }
100 }
101 $secret = str_replace( '=', '', $secret );
102 $secret = str_split( $secret );
103 $binary_string = '';
104 for ( $i = 0; $i < count( $secret ); $i = $i + 8 ) {
105 $x = '';
106 if ( ! in_array( $secret[ $i ], $base32_chars ) ) {
107 return false;
108 }
109 for ( $j = 0; $j < 8; ++$j ) {
110 $x .= str_pad( base_convert( @$base32_chars_flipped[ @$secret[ $i + $j ] ], 10, 2 ), 5, '0', STR_PAD_LEFT );
111 }
112 $eight_bits = str_split( $x, 8 );
113 for ( $z = 0; $z < count( $eight_bits ); ++$z ) {
114 $binary_string .= ( ( $y = chr( base_convert( $eight_bits[ $z ], 2, 10 ) ) ) || ord( $y ) === 48 ) ? $y : '';
115 }
116 }
117
118 return $binary_string;
119 }
120
121 /**
122 * タイミングセーフの等価比較
123 * http://blog.ircmaxell.com/2014/11/its-all-about-time.html.
124 *
125 * @param string $safe_string チェックする安�
126 �な値
127 * @param string $user_string ユーザーが送信した値
128 *
129 * @return bool 2つの文字列が同一かどうか
130 */
131 private static function timing_safe_equals( string $safe_string, string $user_string ): bool {
132 if ( function_exists( 'hash_equals' ) ) {
133 return hash_equals( $safe_string, $user_string );
134 }
135 $safe_len = strlen( $safe_string );
136 $user_len = strlen( $user_string );
137
138 if ( $user_len !== $safe_len ) {
139 return false;
140 }
141
142 $result = 0;
143
144 for ( $i = 0; $i < $user_len; ++$i ) {
145 $result |= ( ord( $safe_string[ $i ] ) ^ ord( $user_string[ $i ] ) );
146 }
147
148 // $resultが0のとき、同一の文字列となります...
149 return $result === 0;
150 }
151 }
152