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

codeigniter4 / CodeIgniter4 / 20411118016

21 Dec 2025 02:17PM UTC coverage: 84.514% (-0.02%) from 84.53%
20411118016

Pull #9853

github

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

43 of 53 new or added lines in 4 files covered. (81.13%)

11 existing lines in 3 files now uncovered.

21518 of 25461 relevant lines covered (84.51%)

196.83 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
     * List of previous keys for fallback decryption.
60
     */
61
    protected string $previousKeys = '';
62

63
    /**
64
     * Whether the cipher-text should be raw. If set to false, then it will be base64 encoded.
65
     */
66
    protected bool $rawData = true;
67

68
    /**
69
     * Encryption key info.
70
     * This setting is only used by OpenSSLHandler.
71
     *
72
     * Set to 'encryption' for CI3 Encryption compatibility.
73
     */
74
    public string $encryptKeyInfo = '';
75

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

84
    /**
85
     * {@inheritDoc}
86
     */
87
    public function encrypt($data, $params = null)
88
    {
89
        // Allow key override
90
        if ($params !== null) {
6✔
91
            $this->key = is_array($params) && isset($params['key']) ? $params['key'] : $params;
5✔
92
        }
93

94
        if (empty($this->key)) {
6✔
95
            throw EncryptionException::forNeedsStarterKey();
1✔
96
        }
97

98
        // derive a secret key
99
        $encryptKey = \hash_hkdf($this->digest, $this->key, 0, $this->encryptKeyInfo);
5✔
100

101
        // basic encryption
102
        $iv = ($ivSize = \openssl_cipher_iv_length($this->cipher)) ? \openssl_random_pseudo_bytes($ivSize) : null;
5✔
103

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

106
        if ($data === false) {
5✔
UNCOV
107
            throw EncryptionException::forEncryptionFailed();
×
108
        }
109

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

112
        // derive a secret key
113
        $authKey = \hash_hkdf($this->digest, $this->key, 0, $this->authKeyInfo);
5✔
114

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

117
        return $hmacKey . $result;
5✔
118
    }
119

120
    /**
121
     * {@inheritDoc}
122
     */
123
    public function decrypt($data, $params = null)
124
    {
125
        // Allow key override
126
        if ($params !== null) {
8✔
127
            $this->key = is_array($params) && isset($params['key']) ? $params['key'] : $params;
4✔
128
        }
129

130
        if (empty($this->key)) {
8✔
UNCOV
131
            throw EncryptionException::forNeedsStarterKey();
×
132
        }
133

134
        $result = false;
8✔
135

136
        try {
137
            $result = $this->decryptWithKey($data, $this->key);
8✔
138
        } catch (EncryptionException $e) {
2✔
139
            $exception = $e;
2✔
140
        }
141

142
        if ($result === false && $this->previousKeys !== '') {
8✔
NEW
UNCOV
143
            foreach (explode(',', $this->previousKeys) as $previousKey) {
×
144
                try {
NEW
UNCOV
145
                    $result = $this->decryptWithKey($data, $previousKey);
×
NEW
146
                    if ($result !== false) {
×
NEW
147
                        return $result;
×
148
                    }
NEW
149
                } catch (EncryptionException) {
×
150
                    // Try next key
151
                }
152
            }
153
        }
154

155
        if (isset($exception)) {
8✔
156
            throw $exception;
2✔
157
        }
158

159
        return $result;
6✔
160
    }
161

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

177
        $hmacLength = $this->rawData
8✔
178
            ? $this->digestSize[$this->digest]
6✔
179
            : $this->digestSize[$this->digest] * 2;
2✔
180

181
        $hmacKey  = self::substr($data, 0, $hmacLength);
8✔
182
        $data     = self::substr($data, $hmacLength);
8✔
183
        $hmacCalc = \hash_hmac($this->digest, $data, $authKey, $this->rawData);
8✔
184

185
        if (! hash_equals($hmacKey, $hmacCalc)) {
8✔
186
            throw EncryptionException::forAuthenticationFailed();
2✔
187
        }
188

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

191
        if ($ivSize = \openssl_cipher_iv_length($this->cipher)) {
6✔
192
            $iv   = self::substr($data, 0, $ivSize);
6✔
193
            $data = self::substr($data, $ivSize);
6✔
194
        } else {
UNCOV
195
            $iv = null;
×
196
        }
197

198
        // derive a secret key
199
        $encryptKey = \hash_hkdf($this->digest, $key, 0, $this->encryptKeyInfo);
6✔
200

201
        return \openssl_decrypt($data, $this->cipher, $encryptKey, OPENSSL_RAW_DATA, $iv);
6✔
202
    }
203
}
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