PluginProbe ʕ •ᴥ•ʔ
CloudSecure WP Security / 1.4.4
CloudSecure WP Security v1.4.4
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-disable-access-system-file-rules.php 11 months ago class-recovery-codes.php 4 months ago class-time-based-one-time-password.php 4 months ago class-waf-rules.php 1 year ago
class-time-based-one-time-password.php
233 lines
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit;
5 }
6
7 /**
8 * TOTPアルゴリズムの2段階認証のためのクラス
9 */
10 class CloudSecureWP_Time_Based_One_Time_Password {
11 private static $digits = 6;
12 private static $discrepancy = 1;
13
14 /**
15 * 指定されたシークレットと時点を使用してコードを計算
16 *
17 * @param string $secret
18 * @param int $time_slice
19 *
20 * @return string
21 */
22 public static function get_code( string $secret, int $time_slice ): string {
23 // 16進数をバイナリデータに変換
24 $secret_key = hex2bin( $secret );
25
26 // 時間をバイナリ文字列にパック
27 $time = chr( 0 ) . chr( 0 ) . chr( 0 ) . chr( 0 ) . pack( 'N*', $time_slice );
28 // ユーザーの秘密鍵でハッシュ
29 $hm = hash_hmac( 'SHA1', $time, $secret_key, true );
30 // 結果の最後のニップルをインデックス/オフセットとして使用
31 $offset = ord( substr( $hm, - 1 ) ) & 0x0F;
32 // 結果の4バイトを取得
33 $hash_part = substr( $hm, $offset, 4 );
34
35 // バイナリ値を切り出す
36 $value = unpack( 'N', $hash_part );
37 $value = $value[1];
38 // 32ビットのみ
39 $value = $value & 0x7FFFFFFF;
40
41 $modulo = pow( 10, self::$digits );
42
43 return str_pad( $value % $modulo, self::$digits, '0', STR_PAD_LEFT );
44 }
45
46 /**
47 * コードが正しいかどうかを検証
48 * 前後1つ分の時間スライスを許容
49 *
50 * @param string $secret
51 * @param string $code
52 * @param int $time_step 時間間隔(秒)デフォルトは30秒
53 *
54 * @return bool
55 */
56 public static function verify_code( string $secret, string $code, int $time_step ): bool {
57 $current_time_slice = floor( time() / $time_step );
58
59 if ( strlen( $code ) !== 6 ) {
60 return false;
61 }
62
63 for ( $i = - self::$discrepancy; $i <= self::$discrepancy; ++$i ) {
64 $calculated_code = self::get_code( $secret, $current_time_slice + $i );
65 if ( self::timing_safe_equals( $calculated_code, $code ) ) {
66 return true;
67 }
68 }
69
70 return false;
71 }
72
73 /**
74 * Base32をデコード
75 *
76 * @param string $secret
77 *
78 * @return bool|string
79 */
80 public static function base32_decode( $secret ) {
81 if ( empty( $secret ) ) {
82 return '';
83 }
84
85 $base32_chars = str_split( 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=' );
86 $base32_chars_flipped = array_flip( $base32_chars );
87
88 $padding_char_count = substr_count( $secret, $base32_chars[32] );
89 $allowed_values = array( 6, 4, 3, 1, 0 );
90 if ( ! in_array( $padding_char_count, $allowed_values ) ) {
91 return false;
92 }
93 for ( $i = 0; $i < 4; ++$i ) {
94 if ( $padding_char_count === $allowed_values[ $i ] &&
95 substr( $secret, - ( $allowed_values[ $i ] ) ) !== str_repeat( $base32_chars[32], $allowed_values[ $i ] ) ) {
96 return false;
97 }
98 }
99 $secret = str_replace( '=', '', $secret );
100 $secret = str_split( $secret );
101 $binary_string = '';
102 for ( $i = 0; $i < count( $secret ); $i = $i + 8 ) {
103 $x = '';
104 if ( ! in_array( $secret[ $i ], $base32_chars ) ) {
105 return false;
106 }
107 for ( $j = 0; $j < 8; ++$j ) {
108 $x .= str_pad( base_convert( @$base32_chars_flipped[ @$secret[ $i + $j ] ], 10, 2 ), 5, '0', STR_PAD_LEFT );
109 }
110 $eight_bits = str_split( $x, 8 );
111 for ( $z = 0; $z < count( $eight_bits ); ++$z ) {
112 $binary_string .= ( ( $y = chr( base_convert( $eight_bits[ $z ], 2, 10 ) ) ) || ord( $y ) === 48 ) ? $y : '';
113 }
114 }
115
116 return $binary_string;
117 }
118
119 /**
120 * タイミングセーフの等価比較
121 * http://blog.ircmaxell.com/2014/11/its-all-about-time.html.
122 *
123 * @param string $safe_string チェックする安�
124 �な値
125 * @param string $user_string ユーザーが送信した値
126 *
127 * @return bool 2つの文字列が同一かどうか
128 */
129 private static function timing_safe_equals( string $safe_string, string $user_string ): bool {
130 if ( function_exists( 'hash_equals' ) ) {
131 return hash_equals( $safe_string, $user_string );
132 }
133 $safe_len = strlen( $safe_string );
134 $user_len = strlen( $user_string );
135
136 if ( $user_len !== $safe_len ) {
137 return false;
138 }
139
140 $result = 0;
141
142 for ( $i = 0; $i < $user_len; ++$i ) {
143 $result |= ( ord( $safe_string[ $i ] ) ^ ord( $user_string[ $i ] ) );
144 }
145
146 // $resultが0のとき、同一の文字列となります...
147 return $result === 0;
148 }
149
150 /**
151 * メール認証用のコードを生成
152 *
153 * @param string $secret
154 * @param int $time_step 時間間隔(秒)
155 *
156 * @return string
157 */
158 public static function create_code_for_email( string $secret, int $time_step ): string {
159
160 // 現在の時間スライスを計算
161 $time_slice = floor( time() / $time_step );
162
163 // コードを生成
164 return self::get_code( $secret, $time_slice );
165 }
166
167 /**
168 * Base32エンコード
169 *
170 * @param string $binary バイナリデータ
171 *
172 * @return string
173 */
174 public static function base32_encode( string $binary ): string {
175 if ( '' === $binary ) {
176 return '';
177 }
178
179 $base32_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
180 $v = 0;
181 $vbits = 0;
182 $ret = '';
183
184 // 1. 5ビットずつ切り出して変換
185 for ( $i = 0, $j = strlen( $binary ); $i < $j; $i++ ) {
186 $v <<= 8;
187 $v += ord( $binary[ $i ] );
188 $vbits += 8;
189
190 while ( $vbits >= 5 ) {
191 $vbits -= 5;
192 $ret .= $base32_chars[ ( $v >> $vbits ) & 31 ];
193 }
194 }
195
196 // 2. 余ったビットの処理
197 if ( $vbits > 0 ) {
198 $v <<= ( 5 - $vbits );
199 $ret .= $base32_chars[ $v & 31 ];
200 }
201
202 // 3. RFC 4648 に基づくパディング処理(デコーダーのチェックをパスするために�
203 須)
204 // Base32は8文字(40ビット)単位でブロックを作る�
205 要がある
206 $padding = ( 8 - ( strlen( $ret ) % 8 ) ) % 8;
207 $ret .= str_repeat( '=', $padding );
208
209 return $ret;
210 }
211
212 /**
213 * ランダムな秘密鍵を生成
214 *
215 * @return array ['hex' => 16進数文字列, 'base32' => Base32文字列]
216 */
217 public static function generate_secret_key(): array {
218 // ランダムなバイナリデータを生成
219 $binary = random_bytes( 10 );
220
221 // 16進数に変換
222 $hex = bin2hex( $binary );
223
224 // Base32エンコード
225 $base32 = self::base32_encode( $binary );
226
227 return array(
228 'hex' => $hex,
229 'base32' => $base32,
230 );
231 }
232 }
233