Completed
Push — master ( a2ea1e...f35e79 )
by Marc
15s queued 12s
created

OTPAuthenticate::stringCompare()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 11
rs 9.4285
cc 3
eloc 5
nc 2
nop 2
1
<?php
2
3
/**
4
 * OTPAuthenticate
5
 * @package OTPAuthenticate
6
 * @copyright (c) Marc Alexander <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace OTPAuthenticate;
13
14
use Base32\Base32;
15
16
class OTPAuthenticate
17
{
18
	/** int verification code modulus */
19
	const VERIFICATION_CODE_MODULUS = 1e6;
20
21
	/** int Secret length */
22
	protected $secret_length;
23
24
	/** int code length */
25
	protected $code_length;
26
27
	/** \Base32\Base32 */
28
	protected $base32;
29
30
	/**
31
	 * Constructor for OTPAuthenticate
32
	 *
33
	 * @param int $code_length Code length
34
	 * @param int $secret_length Secret length
35
	 */
36 16
	public function __construct($code_length = 6, $secret_length = 10)
37
	{
38 16
		$this->code_length = $code_length;
39 16
		$this->secret_length = $secret_length;
40
41 16
		$this->base32 = new Base32();
42 16
	}
43
44
	/**
45
	 * Generates code based on timestamp and secret
46
	 *
47
	 * @param string $secret Secret shared with user
48
	 * @param int $counter Counter for code generation
49
	 * @param string $algorithm Algorithm to use for HMAC hash.
50
	 *			Defaults to sha512. The following hash types are allowed:
51
	 *				TOTP: sha1, sha256, sha512
52
	 *				HOTP: sha1
53
	 *
54
	 * @return string Generated OTP code
55
	 */
56 14
	public function generateCode($secret, $counter, $algorithm = 'sha512')
57
	{
58 14
		$key = $this->base32->decode($secret);
59
60 14
		if (empty($counter))
61
		{
62 1
			return '';
63
		}
64
65 13
		$hash = hash_hmac($algorithm, $this->getBinaryCounter($counter), $key, true);
66
67 13
		return str_pad($this->truncate($hash), $this->code_length, '0', STR_PAD_LEFT);
68
	}
69
70
	/**
71
	 * Check if supplied TOTP code is valid
72
	 *
73
	 * @param string $secret Secret to use for comparison
74
	 * @param int $code Supplied TOTP code
75
	 * @param string $hash_type Hash type
76
	 *
77
	 * @return bool True if code is valid, false if not
78
	 */
79 5
	public function checkTOTP($secret, $code, $hash_type = 'sha512')
80
	{
81 5
		$time = $this->getTimestampCounter(time());
82
83 5
		for ($i = -1; $i <= 1; $i++)
84
		{
85 5
			if (hash_equals($code, $this->generateCode($secret, $time + $i, $hash_type)) === true)
86
			{
87 3
				return true;
88
			}
89
		}
90
91 2
		return false;
92
	}
93
94
	/**
95
	 * Check if supplied HOTP code is valid
96
	 *
97
	 * @param string $secret Secret to use for comparison
98
	 * @param int $counter Current counter
99
	 * @param int $code Supplied HOTP code
100
	 * @param string $hash_type Hash type
101
	 *
102
	 * @return bool True if code is valid, false if not
103
	 */
104 5
	public function checkHOTP($secret, $counter, $code, $hash_type = 'sha512')
105
	{
106 5
		return hash_equals($code, $this->generateCode($secret, $counter, $hash_type));
107
	}
108
109
	/**
110
	 * Truncate HMAC hash to binary for generating a TOTP code
111
	 *
112
	 * @param string $hash HMAC hash
113
	 *
114
	 * @return int Truncated binary hash
115
	 */
116 13
	protected function truncate($hash)
117
	{
118 13
		$truncated_hash = 0;
119 13
		$offset = ord(substr($hash, -1)) & 0xF;
120
121
		// Truncate hash using supplied sha1 hash
122 13
		for ($i = 0; $i < 4; ++$i)
123
		{
124 13
			$truncated_hash <<= 8;
125 13
			$truncated_hash  |= ord($hash[$offset + $i]);
126
		}
127
128
		// Truncate to a smaller number of digits.
129 13
		$truncated_hash &= 0x7FFFFFFF;
130 13
		$truncated_hash %= self::VERIFICATION_CODE_MODULUS;
131
132 13
		return $truncated_hash;
133
	}
134
135
	/**
136
	 * Get binary version of time counter
137
	 *
138
	 * @param int $counter Timestamp or counter
139
	 *
140
	 * @return string Binary time counter
141
	 */
142 13
	protected function getBinaryCounter($counter)
143
	{
144 13
		return pack('N*', 0) . pack('N*', $counter);
145
	}
146
147
	/**
148
	 * Get counter from timestamp
149
	 *
150
	 * @param int $time Timestamp
151
	 *
152
	 * @return int Counter
153
	 */
154 7
	public function getTimestampCounter($time)
155
	{
156 7
		return floor($time / 30);
157
	}
158
159
	/**
160
	 * Generate secret with specified length
161
	 *
162
	 * @param int $length
163
	 *
164
	 * @return string
165
	 */
166 2
	public function generateSecret($length = 10)
167
	{
168 2
		$strong_secret = false;
169
170
		// Try to get $crypto_strong to evaluate to true. Give it 5 tries.
171 2
		for ($i = 0; $i < 5; $i++)
172
		{
173 2
			$secret = openssl_random_pseudo_bytes($length, $strong_secret);
174
175 2
			if ($strong_secret === true)
176
			{
177 2
				return $this->base32->encode($secret);
178
			}
179
		}
180
181 1
		return '';
182
	}
183
}
184