Passed
Branch staging (987394)
by Tony Karavasilev (Тони
03:22
created

LayeredEncryption::setLayers()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
c 1
b 0
f 0
dl 0
loc 17
ccs 11
cts 11
cp 1
rs 10
cc 4
nc 4
nop 1
crap 4
1
<?php
2
3
/**
4
 * Cryptographic protocol for multiple layered encryption.
5
 */
6
7
namespace CryptoManana\CryptographicProtocol;
8
9
use CryptoManana\Core\Abstractions\Containers\AbstractCryptographicProtocol as CryptographicProtocol;
10
use CryptoManana\Core\Abstractions\MessageEncryption\AbstractBlockCipherAlgorithm as SymmetricBlockCipher;
11
use CryptoManana\Core\Interfaces\Containers\LayeredEncryptionInterface as LayeredDataProcessing;
12
use CryptoManana\DataStructures\EncryptionLayer as LayerConfiguration;
13
14
/**
15
 * Class LayeredEncryption - The multiple layered encryption protocol object.
16
 *
17
 * @package CryptoManana\CryptographicProtocol
18
 */
19
class LayeredEncryption extends CryptographicProtocol implements LayeredDataProcessing
20
{
21
    /**
22
     * The internal encryption cipher collection property storage.
23
     *
24
     * @var SymmetricBlockCipher[]
25
     */
26
    protected $ciphers = [];
27
28
    /**
29
     * Setter for the encryption layers' configuration.
30
     *
31
     * @param LayerConfiguration[]|array $layers Collection of layers.
32
     *
33
     * @return $this The cryptographic protocol object.
34
     * @throws \Exception Validation errors.
35
     */
36 28
    public function setLayers(array $layers)
37
    {
38 28
        if (count($layers) < 2) {
39 2
            throw new \RuntimeException('This protocol must have at least two layers to operate.');
40
        }
41
42 26
        foreach ($layers as $layer) {
43 26
            if (!$layer instanceof LayerConfiguration) {
44 2
                throw new \RuntimeException(
45 1
                    'All supplied  configuration must be of the `EncryptionLayer` data structure.'
46 2
                );
47
            }
48
49 24
            $this->addLayer($layer);
50 12
        }
51
52 22
        return $this;
53
    }
54
55
    /**
56
     * Add a single new layer at the last of the list.
57
     *
58
     * @param LayerConfiguration $layer The layer configuration.
59
     *
60
     * @return $this The cryptographic protocol object.
61
     * @throws \Exception Validation errors.
62
     */
63 24
    public function addLayer(LayerConfiguration $layer)
64
    {
65 24
        if (class_exists($layer->cipher) && is_subclass_of($layer->cipher, SymmetricBlockCipher::class)) {
66
            /** @var SymmetricBlockCipher $cipher */
67 24
            $cipher = new $layer->cipher();
68
69 24
            $cipher->setSecretKey($layer->key)
70 24
                ->setInitializationVector($layer->iv)
71 24
                ->setBlockOperationMode($layer->mode)
72 24
                ->setPaddingStandard($layer->padding)
73 24
                ->setCipherFormat($layer->format);
74
75 24
            $this->ciphers[] = $cipher;
76 12
        } else {
77 2
            throw new \RuntimeException('All supplied ciphers must be existing and for symmetric encryption.');
78
        }
79
80 24
        return $this;
81
    }
82
83
    /**
84
     * Getter for the encryption layers' configuration.
85
     *
86
     * @return LayerConfiguration[]|array Collection of used layers' configuration.
87
     *
88
     * @throws \Exception Validation errors.
89
     */
90 4
    public function getLayers()
91
    {
92 4
        $layers = [];
93
94 4
        foreach ($this->ciphers as $cipher) {
95 4
            $layers [] = new LayerConfiguration(
96 4
                get_class($cipher),
97 4
                $cipher->getSecretKey(),
98 4
                $cipher->getInitializationVector(),
99 4
                $cipher->getBlockOperationMode(),
100 4
                $cipher->getPaddingStandard(),
101 4
                $cipher->getCipherFormat()
102 4
            );
103 2
        }
104
105 4
        return $layers;
106
    }
107
108
109
    /**
110
     * Container constructor.
111
     *
112
     * @param LayerConfiguration[]|array $configuration The layers' configuration.
113
     *
114
     * @throws \Exception Initialization validation.
115
     */
116 28
    public function __construct(array $configuration = [])
117
    {
118 28
        $this->setLayers($configuration);
119 11
    }
120
121
    /**
122
     * Container destructor.
123
     */
124 22
    public function __destruct()
125
    {
126 22
        unset($this->ciphers);
127 11
    }
128
129
    /**
130
     * Container cloning via deep copy.
131
     */
132 2
    public function __clone()
133
    {
134 2
        $ciphers = [];
135
136 2
        foreach ($this->ciphers as $cipher) {
137 2
            $ciphers [] = clone $cipher;
138 1
        }
139
140 2
        $this->ciphers = $ciphers;
141 1
    }
142
143
    /**
144
     * Calculates a XOR of two binary strings.
145
     *
146
     * @param string $stringOne The first binary string.
147
     * @param string $stringTwo The second binary string.
148
     *
149
     * @return string The XOR output of both strings.
150
     */
151 8
    protected function xorTwoStrings($stringOne, $stringTwo)
152
    {
153
        /**
154
         * {@internal The encryption standard is 8-bit wise (do not use StringBuilder) and utilizes performance. }}
155
         */
156 8
        if (strlen($stringTwo) < strlen($stringOne)) {
157 8
            $stringTwo = str_pad($stringTwo, strlen($stringOne), "\x0", STR_PAD_RIGHT);
158
        }
159
160 8
        $dataLength = strlen($stringOne);
161 8
        $keyLength = strlen($stringTwo);
162 8
        $xorOutput = $stringOne;
163
164 8
        for ($i = 0; $i < $dataLength; ++$i) {
165 8
            $xorOutput[$i] = $stringOne[$i] ^ $stringTwo[$i % $keyLength];
166 4
        }
167
168 8
        return $xorOutput;
169
    }
170
171
    /**
172
     * Internal method for the validation of the one-time pad string.
173
     *
174
     * @param string $oneTimePad The optional one-time pad key.
175
     *
176
     * @throws \Exception Validation errors.
177
     */
178 20
    protected function validateOneTimePad($oneTimePad)
179
    {
180 20
        if (!is_string($oneTimePad)) {
181 4
            throw new \InvalidArgumentException('The one-time pad key must be a string or a binary string.');
182
        }
183 9
    }
184
185
    /**
186
     * Encrypts the given plain data multiple times with different algorithms as layers.
187
     *
188
     * @param string $plainData The plain input string.
189
     * @param string $oneTimePad The optional one-time pad key.
190
     *
191
     * @return string The cipher/encrypted data.
192
     * @throws \Exception Validation errors.
193
     *
194
     * @note The one-time pad key must be the same length as the input date to maximize security.
195
     */
196 18
    public function layeredEncryptData($plainData, $oneTimePad = '')
197
    {
198 18
        $this->validateOneTimePad($oneTimePad);
199
200 16
        if (!is_string($plainData)) {
201 2
            throw new \InvalidArgumentException(
202 1
                'The data for encryption must be a string or a binary string.'
203 2
            );
204 14
        } elseif (!empty(trim($oneTimePad))) {
205 8
            $plainData = $this->xorTwoStrings($plainData, $oneTimePad);
206
        }
207
208 14
        $last = count($this->ciphers) - 1;
209
210 14
        for ($i = 0; $i <= $last; $i++) {
211 14
            $cipher = $this->ciphers[$i];
212
213 14
            $plainData = $cipher->encryptData($plainData);
214 7
        }
215
216 14
        return $plainData;
217
    }
218
219
    /**
220
     * Decrypts the given cipher data multiple times with different algorithms as layers.
221
     *
222
     * @param string $cipherData The encrypted input string.
223
     * @param string $oneTimePad The optional one-time pad key.
224
     *
225
     * @return string The decrypted/plain data.
226
     * @throws \Exception Validation errors.
227
     */
228 10
    public function layeredDecryptData($cipherData, $oneTimePad = '')
229
    {
230 10
        $this->validateOneTimePad($oneTimePad);
231
232 8
        $last = count($this->ciphers) - 1;
233
234 8
        for ($i = $last; $i >= 0; $i--) {
235 8
            $cipher = $this->ciphers[$i];
236
237 8
            $cipherData = $cipher->decryptData($cipherData);
238 3
        }
239
240 6
        if (!empty(trim($oneTimePad))) {
241 6
            $cipherData = $this->xorTwoStrings($cipherData, $oneTimePad);
242
        }
243
244 6
        return $cipherData;
245
    }
246
}
247