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