Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
### Added

- `Algorithm` enum for OTP hashing algorithms
- `OTP` constructor now accepts `Secret|string` for convenience

### Changed

- `OTP::getHOTP()` and `OTP::getTOTP()` now accept `Algorithm` enum instead of string
- `OTP::ALGORITHM_*` constants are deprecated; use `Algorithm` enum directly
- Modernized codebase to use PHP 8.2+ features (readonly properties, typed properties, constructor property promotion)

### Removed

Expand All @@ -18,12 +20,12 @@
Before:
```php
$code = \Firehed\Security\HOTP($secret, $counter);
$code = \Firehed\Security\TOTP($secret, ['digits' => 8]);
$code = \Firehed\Security\TOTP($secret, ['digits' => 8, 'algorithm' => 'sha256']);
```

After:
```php
$otp = new \Firehed\Security\OTP($secret);
$code = $otp->getHOTP($counter);
$code = $otp->getTOTP(digits: 8);
$code = $otp->getTOTP(digits: 8, algorithm: Algorithm::SHA256);
```
13 changes: 7 additions & 6 deletions src/OTP.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Firehed\Security;

use LengthException;
use SensitiveParameter;

use function assert;
use function floor;
Expand All @@ -26,18 +27,17 @@ class OTP
/** @deprecated Use Algorithm::SHA512 instead */
public const ALGORITHM_SHA512 = Algorithm::SHA512;

/** @var Secret */
private $secret;
private readonly Secret $secret;

/**
* Note: Google Authenticator's keys are base32-encoded, and must be decoded
* to binary before being used as a Secret. Be cautious about storage of
* key material to avoid format mangling, and ensure that key material is
* kept protected at rest and unique to each user.
*/
public function __construct(Secret $secret)
public function __construct(#[SensitiveParameter] Secret|string $secret)
{
$this->secret = $secret;
$this->secret = $secret instanceof Secret ? $secret : new Secret($secret);
}

/**
Expand All @@ -61,7 +61,8 @@ public function getHOTP(int $counter, int $digits = 6, Algorithm $algorithm = Al
);
}

if (strlen($this->secret->reveal()) < (128 / 8)) {
$key = $this->secret->reveal();
if (strlen($key) < (128 / 8)) {
throw new LengthException(
'Key must be at least 128 bits long (160+ recommended)'
);
Expand All @@ -70,7 +71,7 @@ public function getHOTP(int $counter, int $digits = 6, Algorithm $algorithm = Al
$counter = pack('J', $counter); // Convert to 8-byte string

// 5.3 Step 1: Generate hash value
$hash = hash_hmac($algorithm->value, $counter, $this->secret->reveal(), true);
$hash = hash_hmac($algorithm->value, $counter, $key, true);

$dbc = self::dynamicTruncate($hash);
// 5.3 Step 3: Compute HOTP value
Expand Down
21 changes: 1 addition & 20 deletions src/Secret.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,32 +24,18 @@
*/
final class Secret
{
/**
* Obfuscated value
* @var string
*/
private $value;
private readonly string $value;

/**
* @param string $string The secret to obscure
*/
public function __construct(#[SensitiveParameter] string $string)
{
$this->value = $this->mask($string, SecretKey::getKey());
}

/**
* @return string The original secret
*/
public function reveal(): string
{
return $this->mask($this->value, SecretKey::getKey());
}

/**
* @return string A hardcoded string, "<secret>", so that the actual secret
* is not accidentally revealed.
*/
public function __toString(): string
{
return '<secret>';
Expand All @@ -63,11 +49,6 @@ public function __debugInfo(): array
return ['secret' => '<secret>'];
}

/**
* @param string $string The string to obfuscate or deobfuscate
* @param string $noise The mask
* @return string The obfuscated or deobfuscated string
*/
private function mask(#[SensitiveParameter] string $string, #[SensitiveParameter] string $noise): string
{
$result = '';
Expand Down
3 changes: 1 addition & 2 deletions src/SecretKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
*/
final class SecretKey
{
/** @var string */
private static $key;
private static ?string $key = null;

// Private constructor: only access is allowed through getKey()
private function __construct()
Expand Down