Skip to content

Commit 280c4e3

Browse files
committed
Rework of HMAC Encryption config properties.
Improved tests Fixed bug with isEncrypted check Cleaned up Documentation to reflect above changes
1 parent 20bc9f9 commit 280c4e3

8 files changed

Lines changed: 153 additions & 143 deletions

File tree

UPGRADING.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,17 @@ protected function redirectToDeniedUrl(): RedirectResponse
5353
#### Config\AuthToken
5454

5555
If you are using the HMAC authentication you need to update the encryption settings in **app/Config/AuthToken.php**.
56-
You will need to update and set the encryption key `$hmacEncryptionKey`. This should be set using **.env** and/or system
56+
You will need to update and set the encryption key `$hmacEncryption['key']`. This should be set using **.env** and/or system
5757
environment variables. Instructions on how to do that can be found in the
5858
[Setting Your Encryption Key](https://codeigniter.com/user_guide/libraries/encryption.html#setting-your-encryption-key)
5959
section of the CodeIgniter 4 documentation and in [HMAC SHA256 Token Authenticator](./docs/references/authentication/hmac.md#hmac-secret-key-encryption).
6060

61-
You also may wish to adjust the default Driver `$hmacEncryptionDriver` and the default Digest `$hmacEncryptionDigest`,
61+
You also may wish to adjust the default Driver `$hmacEncryption['driver']` and the default Digest `$hmacEncryption['digest']`,
6262
these currently default to `'OpenSSL'` and `'SHA512'` respectively.
6363

6464
#### Encrypt Existing Keys
6565

66-
After updating the `$hmacEncryptionKey` value, you will need to run `php spark shield:hmac encrypt` in order to encrypt
66+
After updating the `$hmacEncryption['key']` value, you will need to run `php spark shield:hmac encrypt` in order to encrypt
6767
any existing HMAC tokens. This only needs to be run if you have existing unencrypted HMAC secretKeys in stored in the
6868
database.
6969

docs/guides/api_hmac_keys.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,13 @@ $user->revokeAllHmacTokens();
9191
## HMAC Secret Key Encryption
9292

9393
The HMAC Secret Key is stored encrypted. Before you start using HMAC, you will need to set/override the encryption key
94-
`$hmacEncryptionKey` in **app/Config/AuthToken.php**. This should be set using **.env** and/or system environment variables.
94+
`$hmacEncryption['key']` in **app/Config/AuthToken.php**. This should be set using **.env** and/or system environment variables.
9595
Instructions on how to do that can be found in the
9696
[Setting Your Encryption Key](https://codeigniter.com/user_guide/libraries/encryption.html#setting-your-encryption-key)
9797
section of the CodeIgniter 4 documentation.
9898

99-
You will also be able to adjust the default Driver `$hmacEncryptionDriver` and the default Digest
100-
`$hmacEncryptionDigest`, these default to `'OpenSSL'` and `'SHA512'` respectively.
99+
You will also be able to adjust the default Driver `$hmacEncryption['driver']` and the default Digest
100+
`$hmacEncryption['digest']`, these default to `'OpenSSL'` and `'SHA512'` respectively.
101101

102102
See [HMAC SHA256 Token Authenticator](../references/authentication/hmac.md#hmac-secret-key-encryption) for additional
103103
details on setting these values.

docs/references/authentication/hmac.md

Lines changed: 35 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -160,54 +160,49 @@ if ($user->hmacTokenCant('forums.manage')) {
160160
## HMAC Secret Key Encryption
161161

162162
The HMAC Secret Key is stored encrypted. Before you start using HMAC, you will need to set/override the encryption key
163-
`$hmacEncryptionKey` in **app/Config/AuthToken.php**. This should be set using .env and/or system environment variables.
163+
`$hmacEncryption['key']` in **app/Config/AuthToken.php**. This should be set using .env and/or system environment variables.
164164
Instructions on how to do that can be found in the
165165
[Setting Your Encryption Key](https://codeigniter.com/user_guide/libraries/encryption.html#setting-your-encryption-key)
166166
section of the CodeIgniter 4 documentation.
167167

168-
You will also be able to adjust the default Driver `$hmacEncryptionDriver` and the default Digest
169-
`$hmacEncryptionDigest`, these default to `'OpenSSL'` and `'SHA512'` respectively. All three properties (`$hmacEncryptionKey`,
170-
`$hmacEncryptionDriver`, and `$hmacEncryptionDigest`) are set in array format (see below).
168+
You will also be able to adjust the default Driver `$hmacEncryption['driver']` and the default Digest
169+
`$hmacEncryption['digest']`, these default to `'OpenSSL'` and `'SHA512'` respectively. All three properties (`$hmacEncryption['key']`,
170+
`$hmacEncryption['driver']`, and `$hmacEncryption['digest']`) are set in array format (see below).
171171

172172
```php
173-
public $hmacEncryptionKey = [
174-
'k1' => 'hex2bin:923dfab5ddca0c7784c2c388a848a704f5e048736c1a852c862959da62ade8c7',
173+
public array $hmacEncryption [
174+
'key' => [
175+
'k1' => 'hex2bin:923dfab5ddca0c7784c2c388a848a704f5e048736c1a852c862959da62ade8c7',
176+
],
177+
'driver' => ['k1' => 'OpenSSL'],
178+
'digest' => ['k1' => 'SHA512'],
179+
'currentKey' => 'k1',
180+
'deprecatedKey' => null
175181
];
176-
177-
public $hmacEncryptionDriver = [
178-
'k1' => 'OpenSSL',
179-
];
180-
181-
public $hmacEncryptionDigest = [
182-
'k1' => 'SHA512',
183-
];
184-
185-
public string $hmacKeyIndex = 'k1';
186182
```
187183

188184
When it is time to update your encryption keys you will need to add an additional key to the above arrays. Then adjust
189-
the `$hmacKeyIndex` to point at the new key and adjust `$hmacDeprecatedKeyIndex` to point at the old key. After the new
190-
encryption key is in place, run `php spark shield:hmac reencrypt` to re-encrypt all existing keys with the new encryption
191-
key.
185+
the `$hmacEncryption['currentKey']` to point at the new key and adjust `$hmacEncryption['deprecatedKey']` to point at the
186+
old key. After the new encryption key is in place, run `php spark shield:hmac reencrypt` to re-encrypt all existing keys
187+
with the new encryption key.
192188

193189
```php
194-
public $hmacEncryptionKey = [
195-
'k1' => 'hex2bin:923dfab5ddca0c7784c2c388a848a704f5e048736c1a852c862959da62ade8c7',
196-
'k2' => 'hex2bin:451df599363b19be1434605fff8556a0bbfc50bede1bb33793dcde4d97fce4b0',
190+
public array $hmacEncryption [
191+
'key' => [
192+
'k1' => 'hex2bin:923dfab5ddca0c7784c2c388a848a704f5e048736c1a852c862959da62ade8c7',
193+
'k2' => 'hex2bin:451df599363b19be1434605fff8556a0bbfc50bede1bb33793dcde4d97fce4b0',
194+
],
195+
'driver' => [
196+
'k1' => 'OpenSSL',
197+
'k2' => 'OpenSSL',
198+
],
199+
'digest' => [
200+
'k1' => 'SHA512',
201+
'k2' => 'SHA512'
202+
],
203+
'currentKey' => 'k2',
204+
'deprecatedKey' => 'k1'
197205
];
198-
199-
public $hmacEncryptionDriver = [
200-
'k1' => 'OpenSSL',
201-
'k2' => 'OpenSSL',
202-
];
203-
204-
public $hmacEncryptionDigest = [
205-
'k1' => 'SHA512',
206-
'k2' => 'SHA512',
207-
];
208-
209-
public string $hmacKeyIndex = 'k2';
210-
public string $hmacDeprecatedKeyIndex = 'k1';
211206
```
212207

213208
```shell
@@ -218,11 +213,11 @@ You can (and should) set these values using environment variable and/or the `.en
218213
the values as JSON strings:
219214

220215
```dotenv
221-
authtoken.hmacEncryptionKey = '{"k1":"hex2bin:923dfab5ddca0c7784c2c388a848a704f5e048736c1a852c862959da62ade8c7","k2":"hex2bin:451df599363b19be1434605fff8556a0bbfc50bede1bb33793dcde4d97fce4b0"}'
222-
authtoken.hmacEncryptionDriver = '{"k1":"OpenSSL","k2":"OpenSSL"}'
223-
authtoken.hmacEncryptionDigest = '{"k1":"SHA512","k2":"SHA512"}'
224-
authtoken.hmacKeyIndex = k2
225-
authtoken.hmacDeprecatedKeyIndex = k1
216+
authtoken.hmacEncryption.key = '{"k1":"hex2bin:923dfab5ddca0c7784c2c388a848a704f5e048736c1a852c862959da62ade8c7","k2":"hex2bin:451df599363b19be1434605fff8556a0bbfc50bede1bb33793dcde4d97fce4b0"}'
217+
authtoken.hmacEncryption.driver = '{"k1":"OpenSSL","k2":"OpenSSL"}'
218+
authtoken.hmacEncryption.digest = '{"k1":"SHA512","k2":"SHA512"}'
219+
authtoken.hmacEncryption.currentKey = k2
220+
authtoken.hmacEncryption.deprecatedKey = k1
226221
```
227222

228223
Depending on the set length of the Secret Key and the type of encryption used, it is possible for the encrypted value to

phpunit.xml.dist

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,9 @@
9494
<env name="COMPOSER_DISABLE_XDEBUG_WARN" value="1"/>
9595

9696
<!-- Default HMAC encryption key -->
97-
<env name="authtoken.hmacEncryptionKey" value="{&quot;k1&quot;:&quot;hex2bin:178ed94fd0b6d57dd31dd6b22fc601fab8ad191efac165a5f3f30a8ac09d813d&quot;,&quot;k2&quot;:&quot;hex2bin:b0ab85bd0320824c496db2f40eb47c8712a6dfcfdf99b805988e22bdea6b9203&quot;}"/>
98-
<env name="authtoken.hmacEncryptionDriver" value="{&quot;k1&quot;:&quot;OpenSSL&quot;,&quot;k2&quot;:&quot;OpenSSL&quot;}"/>
99-
<env name="authtoken.hmacEncryptionDigest" value="{&quot;k1&quot;:&quot;SHA512&quot;,&quot;k2&quot;:&quot;SHA512&quot;}"/>
97+
<env name="authtoken.hmacEncryption.key" value="{&quot;k1&quot;:&quot;hex2bin:178ed94fd0b6d57dd31dd6b22fc601fab8ad191efac165a5f3f30a8ac09d813d&quot;,&quot;k2&quot;:&quot;hex2bin:b0ab85bd0320824c496db2f40eb47c8712a6dfcfdf99b805988e22bdea6b9203&quot;}"/>
98+
<env name="authtoken.hmacEncryption.driver" value="{&quot;k1&quot;:&quot;OpenSSL&quot;,&quot;k2&quot;:&quot;OpenSSL&quot;}"/>
99+
<env name="authtoken.hmacEncryption.digest" value="{&quot;k1&quot;:&quot;SHA512&quot;,&quot;k2&quot;:&quot;SHA512&quot;}"/>
100100

101101
<!-- Database configuration -->
102102
<env name="database.tests.strictOn" value="true"/>

src/Authentication/HMAC/HmacEncrypter.php

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,19 @@ public function __construct(bool $deprecatedKey = false)
4747
$authConfig = config('AuthToken');
4848
$config = new Encryption();
4949

50+
// // identify which encryption key should be used
51+
// $this->keyIndex = $deprecatedKey ? $authConfig->hmacDeprecatedKeyIndex : $authConfig->hmacKeyIndex;
52+
//
53+
// $config->key = $authConfig->hmacEncryptionKey[$this->keyIndex];
54+
// $config->driver = $authConfig->hmacEncryptionDriver[$this->keyIndex];
55+
// $config->digest = $authConfig->hmacEncryptionDigest[$this->keyIndex];
56+
5057
// identify which encryption key should be used
51-
$this->keyIndex = $deprecatedKey ? $authConfig->hmacDeprecatedKeyIndex : $authConfig->hmacKeyIndex;
58+
$this->keyIndex = $deprecatedKey ? $authConfig->hmacEncryption['deprecatedKey'] : $authConfig->hmacEncryption['currentKey'];
5259

53-
$config->key = $authConfig->hmacEncryptionKey[$this->keyIndex];
54-
$config->driver = $authConfig->hmacEncryptionDriver[$this->keyIndex];
55-
$config->digest = $authConfig->hmacEncryptionDigest[$this->keyIndex];
60+
$config->key = $authConfig->hmacEncryption['key'][$this->keyIndex];
61+
$config->driver = $authConfig->hmacEncryption['driver'][$this->keyIndex];
62+
$config->digest = $authConfig->hmacEncryption['digest'][$this->keyIndex];
5663

5764
$this->authConfig = $authConfig;
5865
// decrypt secret key so signature can be validated
@@ -102,7 +109,16 @@ public function encrypt(string $rawString): string
102109
*/
103110
public function isEncrypted(string $string): bool
104111
{
105-
return str_starts_with($string, '$b6$' . $this->keyIndex . '$');
112+
return (bool) preg_match('/^\$b6\$/', $string);
113+
}
114+
115+
/**
116+
* Check if the string already encrypted
117+
*/
118+
public function isEncryptedWithSetKey(string $string): bool
119+
{
120+
return (bool) preg_match('/^\$b6\$' . $this->keyIndex . '\$/', $string);
121+
// return str_starts_with($string, '$b6$' . $this->keyIndex . '$');
106122
}
107123

108124
/**

src/Commands/Hmac.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ class Hmac extends BaseCommand
2525
*
2626
* @var string
2727
*/
28-
protected $description = 'Encrypt/Decrypt secretKey for HMAC tokens. The reencrypt command should be used when
29-
rotating the encryption keys. The encrypt command should only be run on existing raw secret keys (extremely rare).';
28+
protected $description = 'Encrypt/Decrypt secretKey for HMAC tokens.';
3029

3130
/**
3231
* the Command's usage
@@ -38,6 +37,9 @@ class Hmac extends BaseCommand
3837
shield:hmac reencrypt
3938
shield:hmac encrypt
4039
shield:hmac decrypt
40+
41+
The reencrypt command should be used when rotating the encryption keys.
42+
The encrypt command should only be run on existing raw secret keys (extremely rare).
4143
EOL;
4244

4345
/**
@@ -147,7 +149,7 @@ public function decrypt(): void
147149

148150
$that = $this;
149151

150-
$uIdModel->where('type', 'hmac_sha256')->chunk(
152+
$uIdModel->where('type', 'hmac_sha256')->orderBy('id')->chunk(
151153
100,
152154
static function ($identity) use ($uIdModelSub, $encrypter, $that): void {
153155
if (! $encrypter->isEncrypted($identity->secret2)) {
@@ -179,10 +181,10 @@ public function reEncrypt(): void
179181

180182
$that = $this;
181183

182-
$uIdModel->where('type', 'hmac_sha256')->chunk(
184+
$uIdModel->where('type', 'hmac_sha256')->orderBy('id')->chunk(
183185
100,
184186
static function ($identity) use ($uIdModelSub, $decrypter, $encrypter, $that): void {
185-
if (! $decrypter->isEncrypted($identity->secret2)) {
187+
if (! $decrypter->isEncryptedWithSetKey($identity->secret2)) {
186188
$that->write('id: ' . $identity->id . ', not re-encrypted, skipped.');
187189

188190
return;

src/Config/AuthToken.php

Lines changed: 39 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -74,107 +74,64 @@ class AuthToken extends BaseConfig
7474

7575
/**
7676
* --------------------------------------------------------------------
77-
* HMAC encryption key
77+
* HMAC encryption Keys
7878
* --------------------------------------------------------------------
79-
* Key to be used when encrypting HMAC Secret Key for storage.
79+
* This sets the key to be used when encrypting a user's HMAC Secret Key.
8080
*
81-
* This is an array of keys which will facilitate key rotation. Valid
81+
* 'key' is an array of keys which will facilitate key rotation. Valid
8282
* keyTitles must include only [a-zA-z0-9_] and should be kept to a
8383
* max of 8 characters.
8484
*
85-
* The valid and old/deprecated keys are identified using $hmacKeyIndex
86-
* and $hmacDeprecatedKeyIndex.
87-
*
88-
* @param array<string, string>|string $hmacEncryptionKey ['keyTitle' => 'keyValue']
89-
*
90-
* @see https://codeigniter.com/user_guide/libraries/encryption.html
91-
*/
92-
public $hmacEncryptionKey = [
93-
'k1' => '',
94-
];
95-
96-
/**
97-
* --------------------------------------------------------------------
98-
* HMAC encryption driver
99-
* --------------------------------------------------------------------
100-
* Driver to be used when encrypting HMAC Secret Key for storage.
101-
*
102-
* Available drivers:
103-
* OpenSSL
104-
* Sodium
85+
* 'driver' is used when encrypting HMAC Secret Key for storage.
86+
* Available drivers:
87+
* - OpenSSL
88+
* - Sodium
10589
*
10690
* This is an array of drivers values. The keys MUST match and correlate
107-
* to the $hmacEncryptionKey array keys.
108-
*
109-
* @param array<string, string>|string $hmacEncryptionDriver ['keyTitle' => 'driverValue']
91+
* to the 'key' array keys.
11092
*
111-
* @see https://codeigniter.com/user_guide/libraries/encryption.html
112-
*/
113-
public $hmacEncryptionDriver = [
114-
'k1' => 'OpenSSL',
115-
];
116-
117-
/**
118-
* --------------------------------------------------------------------
119-
* HMAC encryption digest
120-
* --------------------------------------------------------------------
121-
* Digest to be used when encrypting HMAC Secret Key for storage.
122-
*
123-
* e.g. 'SHA512' or 'SHA256'. Default value is 'SHA512'.
93+
* 'digest' is used when encrypting HMAC Secret Key for storage.
94+
* e.g. 'SHA512' or 'SHA256'. Default value is 'SHA512'.
12495
*
12596
* This is an array of digest values. The keys MUST match and correlate
126-
* to the $hmacEncryptionKey array keys.
97+
* to the 'key' array keys.
98+
*
99+
* The valid and old/deprecated keys are identified using 'currentKey'
100+
* and 'deprecatedKey'.
127101
*
128-
* @param array<string, string>|string $hmacEncryptionDigest ['keyTitle' => 'digestValue']
102+
* 'deprecatedKey' reflects which 'key' is recently deprecated. This
103+
* is required and used when rotating keys. Effectively, this is the
104+
* index selector for the old key.
129105
*
130106
* @see https://codeigniter.com/user_guide/libraries/encryption.html
131107
*/
132-
public $hmacEncryptionDigest = [
133-
'k1' => 'SHA512',
108+
public array $hmacEncryption = [
109+
'key' => ['k1' => ''],
110+
'driver' => ['k1' => 'OpenSSL'],
111+
'digest' => ['k1' => 'SHA512'],
112+
'currentKey' => 'k1',
113+
'deprecatedKey' => '',
134114
];
135115

136-
/**
137-
* --------------------------------------------------------------------
138-
* HMAC encryption key selector
139-
* --------------------------------------------------------------------
140-
* This identifies which encryption key {$hmacEncryptionKey} is active
141-
* and valid.
142-
*/
143-
public string $hmacKeyIndex = 'k1';
144-
145-
/**
146-
* --------------------------------------------------------------------
147-
* HMAC encryption key deprecated selector
148-
* --------------------------------------------------------------------
149-
* This identifies which encryption key {$hmacEncryptionKey} is
150-
* recently deprecated. This is required and used when rotating keys.
151-
* Effectively, this is the index selector for the old key.
152-
*/
153-
public string $hmacDeprecatedKeyIndex = '';
154-
155116
/**
156117
* AuthToken Config Constructor
157118
*/
158119
public function __construct()
159120
{
160121
parent::__construct();
161122

162-
if (is_string($this->hmacEncryptionKey)) {
163-
$array = json_decode($this->hmacEncryptionKey, true);
164-
if (is_array($array)) {
165-
$this->hmacEncryptionKey = $array;
166-
}
167-
}
168-
if (is_string($this->hmacEncryptionDriver)) {
169-
$array = json_decode($this->hmacEncryptionDriver, true);
170-
if (is_array($array)) {
171-
$this->hmacEncryptionDriver = $array;
172-
}
173-
}
174-
if (is_string($this->hmacEncryptionDigest)) {
175-
$array = json_decode($this->hmacEncryptionDigest, true);
176-
if (is_array($array)) {
177-
$this->hmacEncryptionDigest = $array;
123+
$overwriteHmacEncryptionFields = [
124+
'key',
125+
'driver',
126+
'digest',
127+
];
128+
129+
foreach ($overwriteHmacEncryptionFields as $fieldName) {
130+
if (is_string($this->hmacEncryption[$fieldName])) {
131+
$array = json_decode($this->hmacEncryption[$fieldName], true);
132+
if (is_array($array)) {
133+
$this->hmacEncryption[$fieldName] = $array;
134+
}
178135
}
179136
}
180137
}
@@ -189,12 +146,12 @@ public function __construct()
189146
protected function initEnvValue(&$property, string $name, string $prefix, string $shortPrefix): void
190147
{
191148
switch ($name) {
192-
case 'hmacEncryptionKey':
193-
case 'hmacEncryptionDriver':
194-
case 'hmacEncryptionDigest':
149+
case 'hmacEncryption.key':
150+
case 'hmacEncryption.driver':
151+
case 'hmacEncryption.digest':
195152
// if attempting to set property from ENV, first set to empty string
196153
if ((bool) $this->getEnvValue($name, $prefix, $shortPrefix)) {
197-
$property = '';
154+
$property = null;
198155
}
199156
}
200157

0 commit comments

Comments
 (0)