Passed
Push — master ( 2935f9...5bf936 )
by Tony Karavasilev (Тони
07:06
created

LayeredEncryption::setLayers()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
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 10
cts 10
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 14
    public function setLayers(array $layers)
37
    {
38 14
        if (count($layers) < 2) {
39 1
            throw new \RuntimeException('This protocol must have at least two layers to operate.');
40
        }
41
42 13
        foreach ($layers as $layer) {
43 13
            if (!$layer instanceof LayerConfiguration) {
44 1
                throw new \RuntimeException(
45 1
                    'All supplied  configuration must be of the `EncryptionLayer` data structure.'
46 1
                );
47
            }
48
49 12
            $this->addLayer($layer);
50
        }
51
52 11
        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 12
    public function addLayer(LayerConfiguration $layer)
64
    {
65 12
        if (class_exists($layer->cipher) && is_subclass_of($layer->cipher, SymmetricBlockCipher::class)) {
66
            /** @var SymmetricBlockCipher $cipher */
67 12
            $cipher = new $layer->cipher();
68
69 12
            $cipher->setSecretKey($layer->key)
70 12
                ->setInitializationVector($layer->iv)
71 12
                ->setBlockOperationMode($layer->mode)
72 12
                ->setPaddingStandard($layer->padding)
73 12
                ->setCipherFormat($layer->format);
74
75 12
            $this->ciphers[] = $cipher;
76
        } else {
77 1
            throw new \RuntimeException('All supplied ciphers must be existing and for symmetric encryption.');
78
        }
79
80 12
        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 2
    public function getLayers()
91
    {
92 2
        $layers = [];
93
94 2
        foreach ($this->ciphers as $cipher) {
95 2
            $layers [] = new LayerConfiguration(
96 2
                get_class($cipher),
97 2
                $cipher->getSecretKey(),
98 2
                $cipher->getInitializationVector(),
99 2
                $cipher->getBlockOperationMode(),
100 2
                $cipher->getPaddingStandard(),
101 2
                $cipher->getCipherFormat()
102 2
            );
103
        }
104
105 2
        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 14
    public function __construct(array $configuration = [])
117
    {
118 14
        $this->setLayers($configuration);
119
    }
120
121
    /**
122
     * Container destructor.
123
     */
124 11
    public function __destruct()
125
    {
126 11
        unset($this->ciphers);
127
    }
128
129
    /**
130
     * Container cloning via deep copy.
131
     */
132 1
    public function __clone()
133
    {
134 1
        $ciphers = [];
135
136 1
        foreach ($this->ciphers as $cipher) {
137 1
            $ciphers [] = clone $cipher;
138
        }
139
140 1
        $this->ciphers = $ciphers;
141
    }
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 4
    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 4
        if (strlen($stringTwo) < strlen($stringOne)) {
157 4
            $stringTwo = str_pad($stringTwo, strlen($stringOne), "\x0", STR_PAD_RIGHT);
158
        }
159
160 4
        $dataLength = strlen($stringOne);
161 4
        $keyLength = strlen($stringTwo);
162 4
        $xorOutput = $stringOne;
163
164 4
        for ($i = 0; $i < $dataLength; ++$i) {
165 4
            $xorOutput[$i] = $stringOne[$i] ^ $stringTwo[$i % $keyLength];
166
        }
167
168 4
        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 10
    protected function validateOneTimePad($oneTimePad)
179
    {
180 10
        if (!is_string($oneTimePad)) {
181 2
            throw new \InvalidArgumentException('The one-time pad key must be a string or a binary string.');
182
        }
183
    }
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 9
    public function layeredEncryptData($plainData, $oneTimePad = '')
197
    {
198 9
        $this->validateOneTimePad($oneTimePad);
199
200 8
        if (!is_string($plainData)) {
201 1
            throw new \InvalidArgumentException(
202 1
                'The data for encryption must be a string or a binary string.'
203 1
            );
204 7
        } elseif (!empty(trim($oneTimePad))) {
205 4
            $plainData = $this->xorTwoStrings($plainData, $oneTimePad);
206
        }
207
208 7
        $last = count($this->ciphers) - 1;
209
210 7
        for ($i = 0; $i <= $last; $i++) {
211 7
            $cipher = $this->ciphers[$i];
212
213 7
            $plainData = $cipher->encryptData($plainData);
214
        }
215
216 7
        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 5
    public function layeredDecryptData($cipherData, $oneTimePad = '')
229
    {
230 5
        $this->validateOneTimePad($oneTimePad);
231
232 4
        $last = count($this->ciphers) - 1;
233
234 4
        for ($i = $last; $i >= 0; $i--) {
235 4
            $cipher = $this->ciphers[$i];
236
237 4
            $cipherData = $cipher->decryptData($cipherData);
238
        }
239
240 3
        if (!empty(trim($oneTimePad))) {
241 3
            $cipherData = $this->xorTwoStrings($cipherData, $oneTimePad);
242
        }
243
244 3
        return $cipherData;
245
    }
246
}
247