• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

codeigniter4 / CodeIgniter4 / 20410659201

21 Dec 2025 01:38PM UTC coverage: 84.51% (-0.02%) from 84.53%
20410659201

Pull #9853

github

web-flow
Merge 68e05ba3b into 23b64e61a
Pull Request #9853: feat(encryption): Add previous keys fallback feature

43 of 54 new or added lines in 4 files covered. (79.63%)

3 existing lines in 2 files now uncovered.

21518 of 25462 relevant lines covered (84.51%)

196.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.61
/system/Encryption/Handlers/OpenSSLHandler.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of CodeIgniter 4 framework.
7
 *
8
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace CodeIgniter\Encryption\Handlers;
15

16
use CodeIgniter\Encryption\Exceptions\EncryptionException;
17

18
/**
19
 * Encryption handling for OpenSSL library
20
 *
21
 * @see \CodeIgniter\Encryption\Handlers\OpenSSLHandlerTest
22
 */
23
class OpenSSLHandler extends BaseHandler
24
{
25
    /**
26
     * HMAC digest to use
27
     *
28
     * @var string
29
     */
30
    protected $digest = 'SHA512';
31

32
    /**
33
     * List of supported HMAC algorithms
34
     *
35
     * @var array [name => digest size]
36
     */
37
    protected array $digestSize = [
38
        'SHA224' => 28,
39
        'SHA256' => 32,
40
        'SHA384' => 48,
41
        'SHA512' => 64,
42
    ];
43

44
    /**
45
     * Cipher to use
46
     *
47
     * @var string
48
     */
49
    protected $cipher = 'AES-256-CTR';
50

51
    /**
52
     * Starter key
53
     *
54
     * @var string
55
     */
56
    protected $key = '';
57

58
    /**
59
     * Whether to fall back to previous keys when decryption fails.
60
     */
61
    protected bool $previousKeysFallbackEnabled = false;
62

63
    /**
64
     * List of previous keys for fallback decryption.
65
     *
66
     * @var list<string>
67
     */
68
    protected array $previousKeys = [];
69

70
    /**
71
     * Whether the cipher-text should be raw. If set to false, then it will be base64 encoded.
72
     */
73
    protected bool $rawData = true;
74

75
    /**
76
     * Encryption key info.
77
     * This setting is only used by OpenSSLHandler.
78
     *
79
     * Set to 'encryption' for CI3 Encryption compatibility.
80
     */
81
    public string $encryptKeyInfo = '';
82

83
    /**
84
     * Authentication key info.
85
     * This setting is only used by OpenSSLHandler.
86
     *
87
     * Set to 'authentication' for CI3 Encryption compatibility.
88
     */
89
    public string $authKeyInfo = '';
90

91
    /**
92
     * {@inheritDoc}
93
     */
94
    public function encrypt($data, $params = null)
95
    {
96
        // Allow key override
97
        if ($params !== null) {
6✔
98
            $this->key = is_array($params) && isset($params['key']) ? $params['key'] : $params;
5✔
99
        }
100

101
        if (empty($this->key)) {
6✔
102
            throw EncryptionException::forNeedsStarterKey();
1✔
103
        }
104

105
        // derive a secret key
106
        $encryptKey = \hash_hkdf($this->digest, $this->key, 0, $this->encryptKeyInfo);
5✔
107

108
        // basic encryption
109
        $iv = ($ivSize = \openssl_cipher_iv_length($this->cipher)) ? \openssl_random_pseudo_bytes($ivSize) : null;
5✔
110

111
        $data = \openssl_encrypt($data, $this->cipher, $encryptKey, OPENSSL_RAW_DATA, $iv);
5✔
112

113
        if ($data === false) {
5✔
UNCOV
114
            throw EncryptionException::forEncryptionFailed();
×
115
        }
116

117
        $result = $this->rawData ? $iv . $data : base64_encode($iv . $data);
5✔
118

119
        // derive a secret key
120
        $authKey = \hash_hkdf($this->digest, $this->key, 0, $this->authKeyInfo);
5✔
121

122
        $hmacKey = \hash_hmac($this->digest, $result, $authKey, $this->rawData);
5✔
123

124
        return $hmacKey . $result;
5✔
125
    }
126

127
    /**
128
     * {@inheritDoc}
129
     */
130
    public function decrypt($data, $params = null)
131
    {
132
        // Allow key override
133
        if ($params !== null) {
8✔
134
            $this->key = is_array($params) && isset($params['key']) ? $params['key'] : $params;
4✔
135
        }
136

137
        if (empty($this->key)) {
8✔
138
            throw EncryptionException::forNeedsStarterKey();
×
139
        }
140

141
        try {
142
            $result = $this->decryptWithKey($data, $this->key);
8✔
143
        } catch (EncryptionException $e) {
2✔
144
            $result    = false;
2✔
145
            $exception = $e;
2✔
146
        }
147

148
        if ($result === false && $this->previousKeysFallbackEnabled && $this->previousKeys !== []) {
8✔
NEW
149
            foreach ($this->previousKeys as $previousKey) {
×
150
                try {
NEW
151
                    $result = $this->decryptWithKey($data, $previousKey);
×
NEW
152
                    if ($result !== false) {
×
NEW
153
                        return $result;
×
154
                    }
NEW
155
                } catch (EncryptionException) {
×
156
                    // Try next key
157
                }
158
            }
159
        }
160

161
        if (isset($exception)) {
8✔
162
            throw $exception;
2✔
163
        }
164

165
        return $result;
6✔
166
    }
167

168
    /**
169
     * Decrypt the data with the provided key
170
     *
171
     * @param string $data
172
     * @param string $key
173
     *
174
     * @return false|string
175
     *
176
     * @throws EncryptionException
177
     */
178
    protected function decryptWithKey($data, $key)
179
    {
180
        // derive a secret key
181
        $authKey = \hash_hkdf($this->digest, $key, 0, $this->authKeyInfo);
8✔
182

183
        $hmacLength = $this->rawData
8✔
184
            ? $this->digestSize[$this->digest]
6✔
185
            : $this->digestSize[$this->digest] * 2;
2✔
186

187
        $hmacKey  = self::substr($data, 0, $hmacLength);
8✔
188
        $data     = self::substr($data, $hmacLength);
8✔
189
        $hmacCalc = \hash_hmac($this->digest, $data, $authKey, $this->rawData);
8✔
190

191
        if (! hash_equals($hmacKey, $hmacCalc)) {
8✔
192
            throw EncryptionException::forAuthenticationFailed();
2✔
193
        }
194

195
        $data = $this->rawData ? $data : base64_decode($data, true);
6✔
196

197
        if ($ivSize = \openssl_cipher_iv_length($this->cipher)) {
6✔
198
            $iv   = self::substr($data, 0, $ivSize);
6✔
199
            $data = self::substr($data, $ivSize);
6✔
200
        } else {
UNCOV
201
            $iv = null;
×
202
        }
203

204
        // derive a secret key
205
        $encryptKey = \hash_hkdf($this->digest, $key, 0, $this->encryptKeyInfo);
6✔
206

207
        return \openssl_decrypt($data, $this->cipher, $encryptKey, OPENSSL_RAW_DATA, $iv);
6✔
208
    }
209
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc