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

codeigniter4 / CodeIgniter4 / 20455813739

23 Dec 2025 08:38AM UTC coverage: 84.514% (-0.02%) from 84.53%
20455813739

Pull #9853

github

web-flow
Merge 6a8694fed into 0bd3e098f
Pull Request #9853: feat(encryption): Add previous keys fallback feature

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

9 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
use SensitiveParameter;
18

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

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

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

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

59
    /**
60
     * List of previous keys for fallback decryption.
61
     */
62
    protected string $previousKeys = '';
63

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

135
        $result = false;
8✔
136

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

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

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

160
        return $result;
6✔
161
    }
162

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

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

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

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

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

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

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

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