injectMacIntoMessageData()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
/**
4
 * Cryptographic protocol for authenticated encryption.
5
 */
6
7
namespace CryptoManana\CryptographicProtocol;
8
9
use CryptoManana\Core\Abstractions\Containers\AbstractCryptographicProtocol as CryptographicProtocol;
10
use CryptoManana\Core\Abstractions\MessageDigestion\AbstractKeyedHashFunction as KeyedHashFunction;
11
use CryptoManana\Core\Abstractions\MessageEncryption\AbstractBlockCipherAlgorithm as SymmetricBlockCipher;
12
use CryptoManana\Core\Interfaces\MessageEncryption\DataEncryptionInterface as DataEncryption;
13
use CryptoManana\Core\Interfaces\Containers\KeyedDigestionInjectableInterface as KeyedHashFunctionSetter;
14
use CryptoManana\Core\Interfaces\Containers\SymmetricEncryptionInjectableInterface as SymmetricCipherSetter;
15
use CryptoManana\Core\Interfaces\Containers\AuthenticatedEncryptionInterface as AuthenticatedEncryptionProcessing;
16
use CryptoManana\Core\Traits\Containers\KeyedDigestionInjectableTrait as KeyedHashFunctionSetterImplementation;
17
use CryptoManana\Core\Traits\Containers\SymmetricEncryptionInjectableTrait as SymmetricCipherSetterImplementation;
18
use CryptoManana\DataStructures\AuthenticatedCipherData as CipherDataStructure;
19
use CryptoManana\Hashing\HmacShaTwo384 as DefaultDigestionSource;
20
21
/**
22
 * Class AuthenticatedEncryption - The authenticated encryption protocol object.
23
 *
24
 * @package CryptoManana\CryptographicProtocol
25
 *
26
 * @mixin KeyedHashFunctionSetterImplementation
27
 * @mixin SymmetricCipherSetterImplementation
28
 */
29
class AuthenticatedEncryption extends CryptographicProtocol implements
30
    KeyedHashFunctionSetter,
31
    SymmetricCipherSetter,
32
    AuthenticatedEncryptionProcessing
33
{
34
    /**
35
     * The message keyed digestion service dependency injection via a setter method implementation.
36
     *
37
     * {@internal Reusable implementation of `KeyedDigestionInjectableInterface`. }}
38
     */
39
    use KeyedHashFunctionSetterImplementation;
40
41
    /**
42
     * The message symmetric encryption service dependency injection via a setter method implementation.
43
     *
44
     * {@internal Reusable implementation of `SymmetricEncryptionInjectableInterface`. }}
45
     */
46
    use SymmetricCipherSetterImplementation;
47
48
    /**
49
     * The authenticated encryption mode operation property.
50
     *
51
     * @var int The authenticated encryption mode integer code value.
52
     */
53
    protected $authenticationMode = self::AUTHENTICATION_MODE_ENCRYPT_THEN_MAC;
54
55
    /**
56
     * Internal method for the injection of a MAC tag inside a plain message.
57
     *
58
     * @param string $plainData The plain input string.
59
     * @param string $macTag The message authentication code (tag).
60
     *
61
     * @return string The plain message containing the MAC tag inside.
62
     *
63
     * @note Used by the Encrypt-then-MAC (EtM) authenticated encryption mode realization.
64
     */
65 4
    protected function injectMacIntoMessageData($plainData, $macTag)
66
    {
67 4
        return $plainData . '%7C__%7C' . $macTag; // URL friendly delimiter of 64 bytes
68
    }
69
70
    /**
71
     * Internal method for the extraction of a MAC tag contained inside a plain message.
72
     *
73
     * @param string $plainData The plain input string.
74
     *
75
     * @return array The separated message data and MAC tag as a tuple/array representation.
76
     *
77
     * @note Used by the Encrypt-then-MAC (EtM) authenticated encryption mode realization.
78
     */
79 4
    protected function extractConcatenatedMacFromMessageData($plainData)
80
    {
81 4
        $cipherCode = explode('%7C__%7C', $plainData); // URL friendly delimiter of 64 bytes
82
83 4
        $messageData = (count($cipherCode) > 1 && isset($cipherCode[0])) ? $cipherCode[0] : '';
84 4
        $macTag = (count($cipherCode) > 1 && isset($cipherCode[1])) ? $cipherCode[1] : '';
85
86 4
        return [$messageData, $macTag]; // <- Tuple
87
    }
88
89
    /**
90
     * Builds a mutual key configuration with preserving the original/previous configuration state.
91
     *
92
     * @return array The mutual keys and the original keys configuration as a tuple/array representation.
93
     */
94 8
    protected function getMutualKeyCombinationWithSavingPreviousState()
95
    {
96 8
        $oldSecretKey = $this->symmetricCipherSource->getSecretKey();
97 8
        $oldIv = $this->symmetricCipherSource->getInitializationVector();
98 8
        $oldDigestionKey = $this->keyedDigestionSource->getKey();
99 8
        $oldDigestionSalt = $this->keyedDigestionSource->getSalt();
100
101 8
        $mutualKey = $oldSecretKey . $oldDigestionKey;
102 8
        $mutualSubKey = $oldIv . $oldDigestionSalt;
103
104 8
        return [
105 8
            [$mutualKey, $mutualSubKey], // New mutual configuration
106 8
            [$oldSecretKey, $oldIv, $oldDigestionKey, $oldDigestionSalt] // Old configuration
107 8
        ]; // <- Tuple
108
    }
109
110
    /**
111
     * Encrypts and authenticates the given plain data in Encrypt-and-MAC (E&M) mode.
112
     *
113
     * @param string $plainData The plain input string.
114
     *
115
     * @return CipherDataStructure The authenticated cipher data object.
116
     * @throws \Exception Validation errors.
117
     */
118 4
    protected function encryptAndMac($plainData)
119
    {
120 4
        list($mutualConfiguration, $originalConfiguration) = $this->getMutualKeyCombinationWithSavingPreviousState();
121 4
        list($mutualKey, $mutualSubKey) = $mutualConfiguration;
122 4
        list($oldSecretKey, $oldIv, $oldDigestionKey, $oldDigestionSalt) = $originalConfiguration;
123
124 4
        $this->symmetricCipherSource->setSecretKey($mutualKey)->setInitializationVector($mutualSubKey);
125 4
        $this->keyedDigestionSource->setKey($mutualKey)->setSalt($mutualSubKey);
126
127 4
        $cipherData = $this->symmetricCipherSource->encryptData($plainData);
128 4
        $macTag = $this->keyedDigestionSource->hashData($plainData);
129
130 4
        $this->symmetricCipherSource->setSecretKey($oldSecretKey)->setInitializationVector($oldIv);
131 4
        $this->keyedDigestionSource->setKey($oldDigestionKey)->setSalt($oldDigestionSalt);
132
133 4
        return new CipherDataStructure($cipherData, $macTag);
134
    }
135
136
    /**
137
     * Decrypts and authenticates the given plain data in Encrypt-and-MAC (E&M) mode.
138
     *
139
     * @param CipherDataStructure $authenticatedCipherData The authenticated cipher data object.
140
     *
141
     * @return string The plain data information.
142
     * @throws \Exception Validation or authentication errors.
143
     */
144 5
    protected function decryptAndMac(CipherDataStructure $authenticatedCipherData)
145
    {
146 5
        $cipherData = $authenticatedCipherData->cipherData;
147 5
        $macTag = $authenticatedCipherData->authenticationTag;
148
149 5
        list($mutualConfiguration, $originalConfiguration) = $this->getMutualKeyCombinationWithSavingPreviousState();
150 5
        list($mutualKey, $mutualSubKey) = $mutualConfiguration;
151 5
        list($oldSecretKey, $oldIv, $oldDigestionKey, $oldDigestionSalt) = $originalConfiguration;
152
153 5
        $this->symmetricCipherSource->setSecretKey($mutualKey)->setInitializationVector($mutualSubKey);
154 5
        $this->keyedDigestionSource->setKey($mutualKey)->setSalt($mutualSubKey);
155
156 5
        $plainData = $this->symmetricCipherSource->decryptData($cipherData);
157 4
        $dataDigest = $this->keyedDigestionSource->hashData($plainData);
158
159 4
        $this->symmetricCipherSource->setSecretKey($oldSecretKey)->setInitializationVector($oldIv);
160 4
        $this->keyedDigestionSource->setKey($oldDigestionKey)->setSalt($oldDigestionSalt);
161
162 4
        if (!hash_equals($macTag, $dataDigest)) {
163 2
            throw new \RuntimeException('Wrong MAC tag, the data has been tampered with or defected.');
164
        }
165
166 3
        return $plainData;
167
    }
168
169
    /**
170
     * Encrypts and authenticates the given plain data in MAC-then-Encrypt (MtE) mode.
171
     *
172
     * @param string $plainData The plain input string.
173
     *
174
     * @return CipherDataStructure The authenticated cipher data object.
175
     * @throws \Exception Validation errors.
176
     */
177 4
    protected function macThenEncrypt($plainData)
178
    {
179 4
        list($mutualConfiguration, $originalConfiguration) = $this->getMutualKeyCombinationWithSavingPreviousState();
180 4
        list($mutualKey, $mutualSubKey) = $mutualConfiguration;
181 4
        list($oldSecretKey, $oldIv, $oldDigestionKey, $oldDigestionSalt) = $originalConfiguration;
182
183 4
        $this->symmetricCipherSource->setSecretKey($mutualKey)->setInitializationVector($mutualSubKey);
184 4
        $this->keyedDigestionSource->setKey($mutualKey)->setSalt($mutualSubKey);
185
186 4
        $macTag = $this->keyedDigestionSource->hashData($plainData);
187 4
        $plainData = $this->injectMacIntoMessageData($plainData, $macTag);
188 4
        $cipherData = $this->symmetricCipherSource->encryptData($plainData);
189
190 4
        $this->symmetricCipherSource->setSecretKey($oldSecretKey)->setInitializationVector($oldIv);
191 4
        $this->keyedDigestionSource->setKey($oldDigestionKey)->setSalt($oldDigestionSalt);
192
193 4
        return new CipherDataStructure($cipherData, '');
194
    }
195
196
    /**
197
     * Decrypts and authenticates the given plain data in MAC-then-Encrypt (MtE) mode.
198
     *
199
     * @param CipherDataStructure $authenticatedCipherData The authenticated cipher data object.
200
     *
201
     * @return string The plain data information.
202
     * @throws \Exception Validation or authentication errors.
203
     */
204 5
    protected function macThenDecrypt(CipherDataStructure $authenticatedCipherData)
205
    {
206 5
        list($mutualConfiguration, $originalConfiguration) = $this->getMutualKeyCombinationWithSavingPreviousState();
207 5
        list($mutualKey, $mutualSubKey) = $mutualConfiguration;
208 5
        list($oldSecretKey, $oldIv, $oldDigestionKey, $oldDigestionSalt) = $originalConfiguration;
209
210 5
        $this->symmetricCipherSource->setSecretKey($mutualKey)->setInitializationVector($mutualSubKey);
211 5
        $this->keyedDigestionSource->setKey($mutualKey)->setSalt($mutualSubKey);
212
213 5
        list($plainData, $macTag) = $this->extractConcatenatedMacFromMessageData(
214 5
            $this->symmetricCipherSource->decryptData($authenticatedCipherData->cipherData)
215 5
        );
216
217 4
        $dataDigest = $this->keyedDigestionSource->hashData($plainData);
218
219 4
        $this->symmetricCipherSource->setSecretKey($oldSecretKey)->setInitializationVector($oldIv);
220 4
        $this->keyedDigestionSource->setKey($oldDigestionKey)->setSalt($oldDigestionSalt);
221
222 4
        if (!hash_equals($macTag, $dataDigest) || $authenticatedCipherData->authenticationTag !== '') {
223 2
            throw new \RuntimeException('Wrong MAC tag, the data has been tampered with or defected.');
224
        }
225
226 3
        return $plainData;
227
    }
228
229
    /**
230
     * Encrypts and authenticates the given plain data in Encrypt-then-MAC (EtM) mode.
231
     *
232
     * @param string $plainData The plain input string.
233
     *
234
     * @return CipherDataStructure The authenticated cipher data object.
235
     * @throws \Exception Validation errors.
236
     */
237 11
    protected function encryptThenMac($plainData)
238
    {
239 11
        $cipherData = $this->symmetricCipherSource->encryptData($plainData);
240 11
        $macTag = $this->keyedDigestionSource->hashData($cipherData);
241
242 11
        return new CipherDataStructure($cipherData, $macTag);
243
    }
244
245
    /**
246
     * Decrypts and authenticates the given plain data in Encrypt-then-MAC (EtM) mode.
247
     *
248
     * @param CipherDataStructure $authenticatedCipherData The authenticated cipher data object.
249
     *
250
     * @return string The plain data information.
251
     * @throws \Exception Validation or authentication errors.
252
     */
253 6
    protected function decryptThenMac(CipherDataStructure $authenticatedCipherData)
254
    {
255 6
        $cipherData = $authenticatedCipherData->cipherData;
256 6
        $macTag = $authenticatedCipherData->authenticationTag;
257
258 6
        $plainData = $this->symmetricCipherSource->decryptData($cipherData);
259 5
        $cipherDigest = $this->keyedDigestionSource->hashData($cipherData);
260
261 5
        if (!hash_equals($macTag, $cipherDigest)) {
262 2
            throw new \RuntimeException('Wrong MAC tag, the data has been tampered with or defected.');
263
        }
264
265 4
        return $plainData;
266
    }
267
268
    /**
269
     * Setter for authenticated encryption mode operation property.
270
     *
271
     * @param int $mode The authenticated encryption mode integer code value.
272
     *
273
     * @return $this The authenticated encryption object.
274
     * @throws \Exception Validation errors.
275
     */
276 13
    public function setAuthenticationMode($mode)
277
    {
278 13
        $mode = filter_var(
279 13
            $mode,
280 13
            FILTER_VALIDATE_INT,
281 13
            [
282 13
                "options" => [
283 13
                    "min_range" => self::AUTHENTICATION_MODE_ENCRYPT_AND_MAC,
284 13
                    "max_range" => self::AUTHENTICATION_MODE_ENCRYPT_THEN_MAC,
285 13
                ],
286 13
            ]
287 13
        );
288
289 13
        if ($mode === false) {
290 2
            throw new \InvalidArgumentException(
291 2
                'The padding standard must be a valid integer between ' .
292 2
                self::AUTHENTICATION_MODE_ENCRYPT_AND_MAC . ' and ' . self::AUTHENTICATION_MODE_ENCRYPT_THEN_MAC . '.'
293 2
            );
294
        }
295
296 11
        $this->authenticationMode = $mode;
297
298 11
        return $this;
299
    }
300
301
    /**
302
     * Getter for the authenticated encryption mode operation property.
303
     *
304
     * @return int The authenticated encryption mode integer code value.
305
     */
306 5
    public function getAuthenticationMode()
307
    {
308 5
        return $this->authenticationMode;
309
    }
310
311
    /**
312
     * The message keyed digestion service property storage.
313
     *
314
     * @var KeyedHashFunction|null The message keyed digestion service.
315
     */
316
    protected $keyedDigestionSource = null;
317
318
    /**
319
     * The message symmetric encryption algorithm service property storage.
320
     *
321
     * @var SymmetricBlockCipher|null The message symmetric encryption service.
322
     */
323
    protected $symmetricCipherSource = null;
324
325
    /**
326
     * Container constructor.
327
     *
328
     * @param SymmetricBlockCipher|null $cipher the message symmetric encryption service.
329
     * @param KeyedHashFunction|null $hasher The message keyed digestion service.
330
     *
331
     * @throws \Exception Initialization validation.
332
     */
333 27
    public function __construct(SymmetricBlockCipher $cipher = null, KeyedHashFunction $hasher = null)
334
    {
335 27
        if ($cipher instanceof DataEncryption) {
336 25
            $this->symmetricCipherSource = $cipher;
337
        } else {
338 2
            throw new \RuntimeException('No symmetric encryption service has been set.');
339
        }
340
341 25
        if ($hasher !== null) {
342 10
            $this->keyedDigestionSource = $hasher;
343 17
        } elseif (isset($this->symmetricCipherSource)) {
344 17
            $this->keyedDigestionSource = new DefaultDigestionSource();
345
346 17
            $this->keyedDigestionSource->setKey($this->symmetricCipherSource->getSecretKey())
347 17
                ->setSalt($this->symmetricCipherSource->getInitializationVector());
348
        }
349
    }
350
351
    /**
352
     * Container destructor.
353
     */
354 25
    public function __destruct()
355
    {
356 25
        unset($this->keyedDigestionSource, $this->symmetricCipherSource);
357
    }
358
359
    /**
360
     * Container cloning via deep copy.
361
     */
362 2
    public function __clone()
363
    {
364 2
        $this->keyedDigestionSource = clone $this->keyedDigestionSource;
365 2
        $this->symmetricCipherSource = clone $this->symmetricCipherSource;
366
    }
367
368
    /**
369
     * Encrypts and authenticates the given plain data.
370
     *
371
     * @param string $plainData The plain input string.
372
     *
373
     * @return CipherDataStructure The authenticated cipher data object.
374
     * @throws \Exception Validation errors.
375
     */
376 19
    public function authenticatedEncryptData($plainData)
377
    {
378 19
        if (!is_string($plainData)) {
379 2
            throw new \InvalidArgumentException('The data for encryption must be a string or a binary string.');
380
        }
381
382 17
        if ($this->authenticationMode === self::AUTHENTICATION_MODE_ENCRYPT_AND_MAC) {
383 4
            $outputCipherData = $this->encryptAndMac($plainData);
384 15
        } elseif ($this->authenticationMode === self::AUTHENTICATION_MODE_MAC_THEN_ENCRYPT) {
385 4
            $outputCipherData = $this->macThenEncrypt($plainData);
386 13
        } elseif ($this->authenticationMode === self::AUTHENTICATION_MODE_ENCRYPT_THEN_MAC) {
387 11
            $outputCipherData = $this->encryptThenMac($plainData);
388
        } else {
389 2
            throw new \OutOfBoundsException('Unsupported authenticated encryption mode was given.');
390
        }
391
392 15
        return $outputCipherData;
393
    }
394
395
    /**
396
     * Decrypts and authenticates the given cipher data.
397
     *
398
     * @param CipherDataStructure $authenticatedCipherData The authenticated cipher data object.
399
     *
400
     * @return string The plain data information.
401
     * @throws \Exception Validation errors.
402
     */
403 14
    public function authenticatedDecryptData(CipherDataStructure $authenticatedCipherData)
404
    {
405 14
        if ($this->authenticationMode === self::AUTHENTICATION_MODE_ENCRYPT_AND_MAC) {
406 5
            $plainData = $this->decryptAndMac($authenticatedCipherData);
407 11
        } elseif ($this->authenticationMode === self::AUTHENTICATION_MODE_MAC_THEN_ENCRYPT) {
408 5
            $plainData = $this->macThenDecrypt($authenticatedCipherData);
409 8
        } elseif ($this->authenticationMode === self::AUTHENTICATION_MODE_ENCRYPT_THEN_MAC) {
410 6
            $plainData = $this->decryptThenMac($authenticatedCipherData);
411
        } else {
412 2
            throw new \OutOfBoundsException('Unsupported authenticated decryption mode was given.');
413
        }
414
415 6
        return $plainData;
416
    }
417
}
418