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

codeigniter4 / CodeIgniter4 / 20714927937

05 Jan 2026 12:09PM UTC coverage: 85.469% (+0.001%) from 85.468%
20714927937

Pull #9870

github

web-flow
Merge 9be08e1af into e88e03a74
Pull Request #9870: feat: encryption key rotation

35 of 42 new or added lines in 4 files covered. (83.33%)

27 existing lines in 1 file now uncovered.

21863 of 25580 relevant lines covered (85.47%)

203.8 hits per line

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

72.73
/system/Encryption/KeyRotationDecorator.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;
15

16
use CodeIgniter\Encryption\Exceptions\EncryptionException;
17
use SensitiveParameter;
18

19
/**
20
 * Key Rotation Decorator
21
 *
22
 * Wraps any EncrypterInterface implementation to provide automatic
23
 * fallback to previous encryption keys during decryption. This enables
24
 * seamless key rotation without requiring re-encryption of existing data.
25
 */
26
class KeyRotationDecorator implements EncrypterInterface
27
{
28
    /**
29
     * @param EncrypterInterface $innerHandler The wrapped encryption handler
30
     * @param list<string>       $previousKeys Array of previous encryption keys
31
     */
32
    public function __construct(
33
        private readonly EncrypterInterface $innerHandler,
34
        private readonly array $previousKeys,
35
    ) {
36
    }
25✔
37

38
    /**
39
     * {@inheritDoc}
40
     *
41
     * Encryption always uses the inner handler's current key.
42
     */
43
    public function encrypt(#[SensitiveParameter] $data, #[SensitiveParameter] $params = null)
44
    {
45
        return $this->innerHandler->encrypt($data, $params);
17✔
46
    }
47

48
    /**
49
     * {@inheritDoc}
50
     *
51
     * Attempts decryption with current key first. If that fails and no
52
     * explicit key was provided in $params, tries each previous key.
53
     *
54
     * @throws EncryptionException
55
     */
56
    public function decrypt($data, #[SensitiveParameter] $params = null)
57
    {
58
        try {
59
            return $this->innerHandler->decrypt($data, $params);
17✔
60
        } catch (EncryptionException $e) {
9✔
61
            // Don't try previous keys if an explicit key was provided
62
            if (is_string($params) || (is_array($params) && isset($params['key']))) {
9✔
63
                throw $e;
4✔
64
            }
65

66
            if ($this->previousKeys === []) {
5✔
NEW
67
                throw $e;
×
68
            }
69

70
            foreach ($this->previousKeys as $previousKey) {
5✔
71
                try {
72
                    $previousParams = is_array($params)
5✔
NEW
73
                        ? array_merge($params, ['key' => $previousKey])
×
74
                        : $previousKey;
5✔
75

76
                    return $this->innerHandler->decrypt($data, $previousParams);
5✔
77
                } catch (EncryptionException) {
3✔
78
                    continue;
3✔
79
                }
80
            }
81

82
            throw $e;
2✔
83
        }
84
    }
85

86
    /**
87
     * Delegate property access to the inner handler.
88
     *
89
     * @return array|bool|int|string|null
90
     */
91
    public function __get(string $key)
92
    {
93
        if (method_exists($this->innerHandler, '__get')) {
7✔
94
            return $this->innerHandler->__get($key);
7✔
95
        }
96

NEW
97
        return null;
×
98
    }
99

100
    /**
101
     * Delegate property existence check to inner handler.
102
     */
103
    public function __isset(string $key): bool
104
    {
NEW
105
        if (method_exists($this->innerHandler, '__isset')) {
×
NEW
106
            return $this->innerHandler->__isset($key);
×
107
        }
108

NEW
109
        return false;
×
110
    }
111
}
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