1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
<?php
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2018 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace OTPHP;
use Assert\Assertion;
final class HOTP extends OTP implements HOTPInterface
{
/**
* HOTP constructor.
*
* @param string|null $label
* @param string|null $secret
* @param int $counter
* @param string $digest
* @param int $digits
*/
public function __construct($label = null, $secret = null, $counter = 0, $digest = 'sha1', $digits = 6)
{
parent::__construct($label, $secret, $digest, $digits);
$this->setCounter($counter);
}
/**
* @param int $counter
*/
private function setCounter($counter)
{
Assertion::integer($counter, 'Counter must be at least 0.');
Assertion::greaterOrEqualThan($counter, 0, 'Counter must be at least 0.');
$this->setParameter('counter', $counter);
}
/**
* {@inheritdoc}
*/
public function getCounter()
{
return $this->getParameter('counter');
}
/**
* @param int $counter
*/
private function updateCounter($counter)
{
$this->setCounter($counter);
}
/**
* {@inheritdoc}
*/
public function getProvisioningUri()
{
return $this->generateURI('hotp', ['counter' => $this->getCounter()]);
}
/**
* If the counter is not provided, the OTP is verified at the actual counter.
*
* {@inheritdoc}
*/
public function verify($otp, $counter = null, $window = null)
{
Assertion::string($otp, 'The OTP must be a string');
Assertion::nullOrInteger($counter, 'The counter must be null or an integer');
Assertion::greaterOrEqualThan($counter, 0, 'The counter must be at least 0.');
Assertion::nullOrInteger($window, 'The window parameter must be null or an integer');
if (null === $counter) {
$counter = $this->getCounter();
} elseif ($counter < $this->getCounter()) {
return false;
}
return $this->verifyOtpWithWindow($otp, $counter, $window);
}
/**
* @param null|int $window
*
* @return int
*/
private function getWindow($window)
{
if (null === $window) {
$window = 0;
}
return abs($window);
}
/**
* @param string $otp
* @param int $counter
* @param int $window
*
* @return bool
*/
private function verifyOtpWithWindow($otp, $counter, $window)
{
$window = $this->getWindow($window);
for ($i = $counter; $i <= $counter + $window; $i++) {
if ($this->compareOTP($this->at($i), $otp)) {
$this->updateCounter($i + 1);
return true;
}
}
return false;
}
}