• 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

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

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

33
    /**
34
     * Whether to fall back to previous keys when decryption fails.
35
     */
36
    protected bool $previousKeysFallbackEnabled = false;
37

38
    /**
39
     * List of previous keys for fallback decryption.
40
     *
41
     * @var list<string>
42
     */
43
    protected array $previousKeys = [];
44

45
    /**
46
     * Block size for padding message.
47
     *
48
     * @var int
49
     */
50
    protected $blockSize = 16;
51

52
    /**
53
     * {@inheritDoc}
54
     */
55
    public function encrypt($data, $params = null)
56
    {
57
        $this->parseParams($params);
6✔
58

59
        if (empty($this->key)) {
6✔
60
            throw EncryptionException::forNeedsStarterKey();
1✔
61
        }
62

63
        // create a nonce for this operation
64
        $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); // 24 bytes
5✔
65

66
        // add padding before we encrypt the data
67
        if ($this->blockSize <= 0) {
5✔
68
            throw EncryptionException::forEncryptionFailed();
1✔
69
        }
70

71
        $data = sodium_pad($data, $this->blockSize);
4✔
72

73
        // encrypt message and combine with nonce
74
        $ciphertext = $nonce . sodium_crypto_secretbox($data, $nonce, $this->key);
4✔
75

76
        // cleanup buffers
77
        sodium_memzero($data);
4✔
78
        sodium_memzero($this->key);
4✔
79

80
        return $ciphertext;
4✔
81
    }
82

83
    /**
84
     * {@inheritDoc}
85
     */
86
    public function decrypt($data, $params = null)
87
    {
88
        $this->parseParams($params);
4✔
89

90
        if (empty($this->key)) {
4✔
91
            throw EncryptionException::forNeedsStarterKey();
1✔
92
        }
93

94
        try {
95
            $result = $this->decryptWithKey($data, $this->key);
3✔
96
            sodium_memzero($this->key);
1✔
97
        } catch (EncryptionException $e) {
2✔
98
            $result    = false;
2✔
99
            $exception = $e;
2✔
100
            sodium_memzero($this->key);
2✔
101
        }
102

103
        if ($result === false && $this->previousKeysFallbackEnabled && $this->previousKeys !== []) {
3✔
NEW
104
            foreach ($this->previousKeys as $previousKey) {
×
105
                try {
NEW
106
                    $result = $this->decryptWithKey($data, $previousKey);
×
NEW
107
                    if (isset($result)) {
×
NEW
108
                        return $result;
×
109
                    }
NEW
110
                } catch (EncryptionException) {
×
111
                    // Try next key
112
                }
113
            }
114
        }
115

116
        if (isset($exception)) {
3✔
117
            throw $exception;
2✔
118
        }
119

120
        return $result;
1✔
121
    }
122

123
    /**
124
     * Decrypt the data with the provided key
125
     *
126
     * @param string $data
127
     * @param string $key
128
     *
129
     * @return string
130
     *
131
     * @throws EncryptionException
132
     */
133
    protected function decryptWithKey($data, $key)
134
    {
135
        if (mb_strlen($data, '8bit') < (SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES)) {
3✔
136
            // message was truncated
137
            throw EncryptionException::forAuthenticationFailed();
1✔
138
        }
139

140
        // Extract info from encrypted data
141
        $nonce      = self::substr($data, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
2✔
142
        $ciphertext = self::substr($data, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
2✔
143

144
        // decrypt data
145
        $data = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
2✔
146

147
        if ($data === false) {
2✔
148
            // message was tampered in transit
UNCOV
149
            throw EncryptionException::forAuthenticationFailed(); // @codeCoverageIgnore
×
150
        }
151

152
        // remove extra padding during encryption
153
        if ($this->blockSize <= 0) {
2✔
154
            throw EncryptionException::forAuthenticationFailed();
1✔
155
        }
156

157
        $data = sodium_unpad($data, $this->blockSize);
1✔
158

159
        // cleanup buffers
160
        sodium_memzero($ciphertext);
1✔
161

162
        return $data;
1✔
163
    }
164

165
    /**
166
     * Parse the $params before doing assignment.
167
     *
168
     * @param array|string|null $params
169
     *
170
     * @return void
171
     *
172
     * @throws EncryptionException If key is empty
173
     */
174
    protected function parseParams($params)
175
    {
176
        if ($params === null) {
6✔
177
            return;
5✔
178
        }
179

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

185
            if (isset($params['blockSize'])) {
2✔
186
                $this->blockSize = $params['blockSize'];
2✔
187
            }
188

189
            return;
2✔
190
        }
191

192
        $this->key = (string) $params;
2✔
193
    }
194
}
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