• 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

88.0
/system/Encryption/Handlers/SodiumHandler.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
 * SodiumHandler uses libsodium in encryption.
21
 *
22
 * @see https://github.com/jedisct1/libsodium/issues/392
23
 * @see \CodeIgniter\Encryption\Handlers\SodiumHandlerTest
24
 */
25
class SodiumHandler extends BaseHandler
26
{
27
    /**
28
     * Starter key
29
     *
30
     * @var string|null Null is used for buffer cleanup.
31
     */
32
    protected $key = '';
33

34
    /**
35
     * List of previous keys for fallback decryption.
36
     */
37
    protected string $previousKeys = '';
38

39
    /**
40
     * Block size for padding message.
41
     *
42
     * @var int
43
     */
44
    protected $blockSize = 16;
45

46
    /**
47
     * {@inheritDoc}
48
     */
49
    public function encrypt(#[SensitiveParameter] $data, #[SensitiveParameter] $params = null)
50
    {
51
        $this->parseParams($params);
6✔
52

53
        if (empty($this->key)) {
6✔
54
            throw EncryptionException::forNeedsStarterKey();
1✔
55
        }
56

57
        // create a nonce for this operation
58
        $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); // 24 bytes
5✔
59

60
        // add padding before we encrypt the data
61
        if ($this->blockSize <= 0) {
5✔
62
            throw EncryptionException::forEncryptionFailed();
1✔
63
        }
64

65
        $data = sodium_pad($data, $this->blockSize);
4✔
66

67
        // encrypt message and combine with nonce
68
        $ciphertext = $nonce . sodium_crypto_secretbox($data, $nonce, $this->key);
4✔
69

70
        // cleanup buffers
71
        sodium_memzero($data);
4✔
72
        sodium_memzero($this->key);
4✔
73

74
        return $ciphertext;
4✔
75
    }
76

77
    /**
78
     * {@inheritDoc}
79
     */
80
    public function decrypt($data, #[SensitiveParameter] $params = null)
81
    {
82
        $this->parseParams($params);
4✔
83

84
        if (empty($this->key)) {
4✔
85
            throw EncryptionException::forNeedsStarterKey();
1✔
86
        }
87

88
        $result = false;
3✔
89

90
        try {
91
            $result = $this->decryptWithKey($data, $this->key);
3✔
92
            sodium_memzero($this->key);
1✔
93
        } catch (EncryptionException $e) {
2✔
94
            $exception = $e;
2✔
95
            sodium_memzero($this->key);
2✔
96
        }
97

98
        if ($result === false && $this->previousKeys !== '') {
3✔
NEW
99
            foreach (explode(',', $this->previousKeys) as $previousKey) {
×
100
                try {
NEW
101
                    $result = $this->decryptWithKey($data, $previousKey);
×
NEW
102
                    if (isset($result)) {
×
NEW
103
                        return $result;
×
104
                    }
NEW
105
                } catch (EncryptionException) {
×
106
                    // Try next key
107
                }
108
            }
109
        }
110

111
        if (isset($exception)) {
3✔
112
            throw $exception;
2✔
113
        }
114

115
        return $result;
1✔
116
    }
117

118
    /**
119
     * Decrypt the data with the provided key
120
     *
121
     * @param string $data
122
     * @param string $key
123
     *
124
     * @return string
125
     *
126
     * @throws EncryptionException
127
     */
128
    protected function decryptWithKey($data, #[SensitiveParameter] $key)
129
    {
130
        if (mb_strlen($data, '8bit') < (SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES)) {
3✔
131
            // message was truncated
132
            throw EncryptionException::forAuthenticationFailed();
1✔
133
        }
134

135
        // Extract info from encrypted data
136
        $nonce      = self::substr($data, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
2✔
137
        $ciphertext = self::substr($data, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
2✔
138

139
        // decrypt data
140
        $data = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
2✔
141

142
        if ($data === false) {
2✔
143
            // message was tampered in transit
UNCOV
144
            throw EncryptionException::forAuthenticationFailed(); // @codeCoverageIgnore
×
145
        }
146

147
        // remove extra padding during encryption
148
        if ($this->blockSize <= 0) {
2✔
149
            throw EncryptionException::forAuthenticationFailed();
1✔
150
        }
151

152
        $data = sodium_unpad($data, $this->blockSize);
1✔
153

154
        // cleanup buffers
155
        sodium_memzero($ciphertext);
1✔
156

157
        return $data;
1✔
158
    }
159

160
    /**
161
     * Parse the $params before doing assignment.
162
     *
163
     * @param array|string|null $params
164
     *
165
     * @return void
166
     *
167
     * @throws EncryptionException If key is empty
168
     */
169
    protected function parseParams(#[SensitiveParameter] $params)
170
    {
171
        if ($params === null) {
6✔
172
            return;
5✔
173
        }
174

175
        if (is_array($params)) {
4✔
176
            if (isset($params['key'])) {
2✔
177
                $this->key = $params['key'];
2✔
178
            }
179

180
            if (isset($params['blockSize'])) {
2✔
181
                $this->blockSize = $params['blockSize'];
2✔
182
            }
183

184
            return;
2✔
185
        }
186

187
        $this->key = (string) $params;
2✔
188
    }
189
}
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