Failed Conditions
Push — master ( 6fd0c0...b655be )
by Florent
01:26
created

AESGCM::common()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 21
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 9.3142
c 0
b 0
f 0
cc 2
eloc 15
nc 2
nop 4
1
<?php
2
3
/*
4
 * The MIT License (MIT)
5
 *
6
 * Copyright (c) 2016 Spomky-Labs
7
 *
8
 * This software may be modified and distributed under the terms
9
 * of the MIT license.  See the LICENSE file for details.
10
 */
11
12
namespace AESGCM;
13
14
use Assert\Assertion;
15
16
final class AESGCM
17
{
18
    /**
19
     * @param string      $K          Key encryption key
20
     * @param string      $IV         Initialization vector
21
     * @param null|string $P          Data to encrypt (null for authentication)
22
     * @param null|string $A          Additional Authentication Data
23
     * @param int         $tag_length Tag length
24
     *
25
     * @return array
26
     */
27
    public static function encrypt($K, $IV, $P = null, $A = null, $tag_length = 128)
28
    {
29
        Assertion::string($K, 'The key encryption key must be a binary string.');
30
        $key_length = mb_strlen($K, '8bit') * 8;
31
        Assertion::inArray($key_length, [128, 192, 256], 'Bad key encryption key length.');
32
        Assertion::string($IV, 'The Initialization Vector must be a binary string.');
33
        Assertion::nullOrString($P, 'The data to encrypt must be null or a binary string.');
34
        Assertion::nullOrString($A, 'The Additional Authentication Data must be null or a binary string.');
35
        Assertion::integer($tag_length, 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
36
        Assertion::inArray($tag_length, [128, 120, 112, 104, 96], 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
37
38
        if (version_compare(PHP_VERSION, '7.1.0RC5') >= 0 && null !== $P) {
39
            return self::encryptWithPHP71($K, $key_length, $IV, $P, $A, $tag_length);
40
        } elseif (class_exists('\Crypto\Cipher')) {
41
            return self::encryptWithCryptoExtension($K, $key_length, $IV, $P, $A, $tag_length);
42
        }
43
44
        return self::encryptWithPHP($K, $key_length, $IV, $P, $A, $tag_length);
45
    }
46
47
    /**
48
     * This method will append the tag at the end of the ciphertext.
49
     *
50
     * @param string      $K          Key encryption key
51
     * @param string      $IV         Initialization vector
52
     * @param null|string $P          Data to encrypt (null for authentication)
53
     * @param null|string $A          Additional Authentication Data
54
     * @param int         $tag_length Tag length
55
     *
56
     * @return string
57
     */
58
    public static function encryptAndAppendTag($K, $IV, $P = null, $A = null, $tag_length = 128)
59
    {
60
        return implode(self::encrypt($K, $IV, $P, $A, $tag_length));
61
    }
62
63
    /**
64
     * @param string      $K          Key encryption key
65
     * @param string      $key_length Key length
66
     * @param string      $IV         Initialization vector
67
     * @param null|string $P          Data to encrypt (null for authentication)
68
     * @param null|string $A          Additional Authentication Data
69
     * @param int         $tag_length Tag length
70
     *
71
     * @return array
72
     */
73
    private static function encryptWithPHP71($K, $key_length, $IV, $P = null, $A = null, $tag_length = 128)
74
    {
75
        $mode = 'aes-'.($key_length).'-gcm';
76
        $T = null;
77
        $C = openssl_encrypt($P, $mode, $K, OPENSSL_RAW_DATA, $IV, $T, $A, $tag_length / 8);
78
        Assertion::true(false !== $C, 'Unable to encrypt the data.');
79
80
        return [$C, $T];
81
    }
82
83
    /**
84
     * @param string      $K          Key encryption key
85
     * @param string      $key_length Key length
86
     * @param string      $IV         Initialization vector
87
     * @param null|string $P          Data to encrypt (null for authentication)
88
     * @param null|string $A          Additional Authentication Data
89
     * @param int         $tag_length Tag length
90
     *
91
     * @return array
92
     */
93
    private static function encryptWithPHP($K, $key_length, $IV, $P = null, $A = null, $tag_length = 128)
94
    {
95
        list($J0, $v, $a_len_padding, $H) = self::common($K, $key_length, $IV, $A);
96
97
        $C = self::getGCTR($K, $key_length, self::getInc(32, $J0), $P);
98
        $u = self::calcVector($C);
99
        $c_len_padding = self::addPadding($C);
100
101
        $S = self::getHash($H, $A.str_pad('', $v / 8, "\0").$C.str_pad('', $u / 8, "\0").$a_len_padding.$c_len_padding);
102
        $T = self::getMSB($tag_length, self::getGCTR($K, $key_length, $J0, $S));
103
104
        return [$C, $T];
105
    }
106
107
    /**
108
     * @param string      $K          Key encryption key
109
     * @param string      $key_length Key length
110
     * @param string      $IV         Initialization vector
111
     * @param null|string $P          Data to encrypt (null for authentication)
112
     * @param null|string $A          Additional Authentication Data
113
     * @param int         $tag_length Tag length
114
     *
115
     * @return array
116
     */
117
    private static function encryptWithCryptoExtension($K, $key_length, $IV, $P = null, $A = null, $tag_length = 128)
118
    {
119
        $cipher = \Crypto\Cipher::aes(\Crypto\Cipher::MODE_GCM, $key_length);
120
        $cipher->setAAD($A);
121
        $cipher->setTagLength($tag_length / 8);
122
        $C = $cipher->encrypt($P, $K, $IV);
123
        $T = $cipher->getTag();
124
125
        return [$C, $T];
126
    }
127
128
    /**
129
     * @param string      $K  Key encryption key
130
     * @param string      $IV Initialization vector
131
     * @param string|null $C  Data to encrypt (null for authentication)
132
     * @param string|null $A  Additional Authentication Data
133
     * @param string      $T  Tag
134
     *
135
     * @return string
136
     */
137
    public static function decrypt($K, $IV, $C, $A, $T)
138
    {
139
        Assertion::string($K, 'The key encryption key must be a binary string.');
140
        $key_length = mb_strlen($K, '8bit') * 8;
141
        Assertion::inArray($key_length, [128, 192, 256], 'Bad key encryption key length.');
142
        Assertion::string($IV, 'The Initialization Vector must be a binary string.');
143
        Assertion::nullOrString($C, 'The data to encrypt must be null or a binary string.');
144
        Assertion::nullOrString($A, 'The Additional Authentication Data must be null or a binary string.');
145
146
        $tag_length = self::getLength($T);
147
        Assertion::integer($tag_length, 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
148
        Assertion::inArray($tag_length, [128, 120, 112, 104, 96], 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
149
150
        if (version_compare(PHP_VERSION, '7.1.0RC5') >= 0 && null !== $C) {
151
            return self::decryptWithPHP71($K, $key_length, $IV, $C, $A, $T);
152
        } elseif (class_exists('\Crypto\Cipher')) {
153
            return self::decryptWithCryptoExtension($K, $key_length, $IV, $C, $A, $T, $tag_length);
154
        }
155
156
        return self::decryptWithPHP($K, $key_length, $IV, $C, $A, $T, $tag_length);
157
    }
158
159
    /**
160
     * This method should be used if the tag is appended at the end of the ciphertext.
161
     * It is used by some AES GCM implementations such as the Java one.
162
     *
163
     * @param string      $K          Key encryption key
164
     * @param string      $IV         Initialization vector
165
     * @param string|null $Ciphertext Data to encrypt (null for authentication)
166
     * @param string|null $A          Additional Authentication Data
167
     * @param int         $tag_length Tag length
168
     *
169
     * @return string
170
     *
171
     * @see self::encryptAndAppendTag
172
     */
173
    public static function decryptWithAppendedTag($K, $IV, $Ciphertext = null, $A = null, $tag_length = 128)
174
    {
175
        $tag_length_in_bits = $tag_length / 8;
176
        $C = mb_substr($Ciphertext, 0, -$tag_length_in_bits, '8bit');
177
        $T = mb_substr($Ciphertext, -$tag_length_in_bits, null, '8bit');
178
179
        return self::decrypt($K, $IV, $C, $A, $T);
180
    }
181
182
    /**
183
     * @param string      $K          Key encryption key
184
     * @param string      $key_length Key length
185
     * @param string      $IV         Initialization vector
186
     * @param string|null $C          Data to encrypt (null for authentication)
187
     * @param string|null $A          Additional Authentication Data
188
     * @param string      $T          Tag
189
     *
190
     * @return string
191
     */
192
    private static function decryptWithPHP71($K, $key_length, $IV, $C, $A, $T)
193
    {
194
        $mode = 'aes-'.($key_length).'-gcm';
195
        $P = openssl_decrypt(null === $C ? '' : $C, $mode, $K, OPENSSL_RAW_DATA, $IV, $T, $A);
196
        Assertion::true(false !== $P, 'Unable to decrypt or to verify the tag.');
197
198
        return $P;
199
    }
200
201
    /**
202
     * @param string      $K          Key encryption key
203
     * @param string      $key_length Key length
204
     * @param string      $IV         Initialization vector
205
     * @param string|null $C          Data to encrypt (null for authentication)
206
     * @param string|null $A          Additional Authentication Data
207
     * @param string      $T          Tag
208
     * @param int         $tag_length Tag length
209
     *
210
     * @return string
211
     */
212
    private static function decryptWithPHP($K, $key_length, $IV, $C, $A, $T, $tag_length = 128)
213
    {
214
        list($J0, $v, $a_len_padding, $H) = self::common($K, $key_length, $IV, $A);
215
216
        $P = self::getGCTR($K, $key_length, self::getInc(32, $J0), $C);
217
218
        $u = self::calcVector($C);
219
        $c_len_padding = self::addPadding($C);
220
221
        $S = self::getHash($H, $A.str_pad('', $v / 8, "\0").$C.str_pad('', $u / 8, "\0").$a_len_padding.$c_len_padding);
222
        $T1 = self::getMSB($tag_length, self::getGCTR($K, $key_length, $J0, $S));
223
        Assertion::eq($T1, $T, 'Unable to decrypt or to verify the tag.');
224
225
        return $P;
226
    }
227
228
    /**
229
     * @param string      $K          Key encryption key
230
     * @param string      $key_length Key length
231
     * @param string      $IV         Initialization vector
232
     * @param string|null $C          Data to encrypt (null for authentication)
233
     * @param string|null $A          Additional Authentication Data
234
     * @param string      $T          Tag
235
     * @param int         $tag_length Tag length
236
     *
237
     * @return string
238
     */
239
    private static function decryptWithCryptoExtension($K, $key_length, $IV, $C, $A, $T, $tag_length = 128)
240
    {
241
        $cipher = \Crypto\Cipher::aes(\Crypto\Cipher::MODE_GCM, $key_length);
242
        $cipher->setTag($T);
243
        $cipher->setAAD($A);
244
        $cipher->setTagLength($tag_length / 8);
245
246
        return $cipher->decrypt($C, $K, $IV);
247
    }
248
249
    /**
250
     * @param $K
251
     * @param $key_length
252
     * @param $IV
253
     * @param $A
254
     *
255
     * @return array
256
     */
257
    private static function common($K, $key_length, $IV, $A)
258
    {
259
        $H = openssl_encrypt(str_repeat("\0", 16), 'aes-'.($key_length).'-ecb', $K, OPENSSL_NO_PADDING | OPENSSL_RAW_DATA); //---
260
        $iv_len = self::getLength($IV);
261
262
        if (96 === $iv_len) {
263
            $J0 = $IV.pack('H*', '00000001');
264
        } else {
265
            $s = self::calcVector($IV);
266
            Assertion::eq(($s + 64) % 8, 0, 'Unable to decrypt or to verify the tag.');
267
268
            $packed_iv_len = pack('N', $iv_len);
269
            $iv_len_padding = str_pad($packed_iv_len, 8, "\0", STR_PAD_LEFT);
270
            $hash_X = $IV.str_pad('', ($s + 64) / 8, "\0").$iv_len_padding;
271
            $J0 = self::getHash($H, $hash_X);
272
        }
273
        $v = self::calcVector($A);
274
        $a_len_padding = self::addPadding($A);
275
276
        return [$J0, $v, $a_len_padding, $H];
277
    }
278
279
    /**
280
     * @param string $value
281
     *
282
     * @return int
283
     */
284
    private static function calcVector($value)
285
    {
286
        return (128 * ceil(self::getLength($value) / 128)) - self::getLength($value);
287
    }
288
289
    /**
290
     * @param string $value
291
     *
292
     * @return string
293
     */
294
    private static function addPadding($value)
295
    {
296
        return str_pad(pack('N', self::getLength($value)), 8, "\0", STR_PAD_LEFT);
297
    }
298
299
    /**
300
     * @param string $x
301
     *
302
     * @return int
303
     */
304
    private static function getLength($x)
305
    {
306
        return mb_strlen($x, '8bit') * 8;
307
    }
308
309
    /**
310
     * @param int $num_bits
311
     * @param int $x
312
     *
313
     * @return string
314
     */
315
    private static function getMSB($num_bits, $x)
316
    {
317
        $num_bytes = $num_bits / 8;
318
319
        return mb_substr($x, 0, $num_bytes, '8bit');
320
    }
321
322
    /**
323
     * @param int $num_bits
324
     * @param int $x
325
     *
326
     * @return string
327
     */
328
    private static function getLSB($num_bits, $x)
329
    {
330
        $num_bytes = ($num_bits / 8);
331
332
        return mb_substr($x, -$num_bytes, null, '8bit');
333
    }
334
335
    /**
336
     * @param int $s_bits
337
     * @param int $x
338
     *
339
     * @return string
340
     */
341
    private static function getInc($s_bits, $x)
342
    {
343
        $lsb = self::getLSB($s_bits, $x);
344
        $X = self::toUInt32Bits($lsb) + 1;
345
        $res = self::getMSB(self::getLength($x) - $s_bits, $x).pack('N', $X);
346
347
        return $res;
348
    }
349
350
    /**
351
     * @param string $bin
352
     *
353
     * @return mixed
354
     */
355
    private static function toUInt32Bits($bin)
356
    {
357
        list(, $h, $l) = unpack('n*', $bin);
358
359
        return $l + ($h * 0x010000);
360
    }
361
362
    /**
363
     * @param $X
364
     * @param $Y
365
     *
366
     * @return string
367
     */
368
    private static function getProduct($X, $Y)
369
    {
370
        $R = pack('H*', 'E1').str_pad('', 15, "\0");
371
        $Z = str_pad('', 16, "\0");
372
        $V = $Y;
373
374
        $parts = str_split($X, 4);
375
        $x = sprintf('%032b%032b%032b%032b', self::toUInt32Bits($parts[0]), self::toUInt32Bits($parts[1]), self::toUInt32Bits($parts[2]), self::toUInt32Bits($parts[3]));
376
        $lsb_mask = "\1";
377
        for ($i = 0; $i < 128; $i++) {
378
            if ($x[$i]) {
379
                $Z = self::getBitXor($Z, $V);
380
            }
381
            $lsb_8 = mb_substr($V, -1, null, '8bit');
382
            if (ord($lsb_8 & $lsb_mask)) {
383
                $V = self::getBitXor(self::shiftStringToRight($V), $R);
384
            } else {
385
                $V = self::shiftStringToRight($V);
386
            }
387
        }
388
389
        return $Z;
390
    }
391
392
    /**
393
     * @param string $input
394
     *
395
     * @return string
396
     */
397
    private static function shiftStringToRight($input)
398
    {
399
        $width = 4;
400
        $parts = array_map('self::toUInt32Bits', str_split($input, $width));
401
        $runs = count($parts);
402
403
        for ($i = $runs - 1; $i >= 0; $i--) {
404
            if ($i) {
405
                $lsb1 = $parts[$i - 1] & 0x00000001;
406
                if ($lsb1) {
407
                    $parts[$i] = ($parts[$i] >> 1) | 0x80000000;
408
                    $parts[$i] = pack('N', $parts[$i]);
409
                    continue;
410
                }
411
            }
412
            $parts[$i] = ($parts[$i] >> 1) & 0x7FFFFFFF;
413
            $parts[$i] = pack('N', $parts[$i]);
414
        }
415
        $res = implode('', $parts);
416
417
        return $res;
418
    }
419
420
    /**
421
     * @param string $H
422
     * @param string $X
423
     *
424
     * @return mixed
425
     */
426
    private static function getHash($H, $X)
427
    {
428
        $Y = [];
429
        $Y[0] = str_pad('', 16, "\0");
430
        $num_blocks = (int) (mb_strlen($X, '8bit') / 16);
431
        for ($i = 1; $i <= $num_blocks; $i++) {
432
            $Y[$i] = self::getProduct(self::getBitXor($Y[$i - 1], mb_substr($X, ($i - 1) * 16, 16, '8bit')), $H);
433
        }
434
435
        return $Y[$num_blocks];
436
    }
437
438
    /**
439
     * @param string $K
440
     * @param int    $key_length
441
     * @param string $ICB
442
     * @param string $X
443
     *
444
     * @return string
445
     */
446
    private static function getGCTR($K, $key_length, $ICB, $X)
447
    {
448
        if (empty($X)) {
449
            return '';
450
        }
451
452
        $n = (int) ceil(self::getLength($X) / 128);
453
        $CB = [];
454
        $Y = [];
455
        $CB[1] = $ICB;
456
        for ($i = 2; $i <= $n; $i++) {
457
            $CB[$i] = self::getInc(32, $CB[$i - 1]);
458
        }
459
        $mode = 'aes-'.($key_length).'-ecb';
460
        for ($i = 1; $i < $n; $i++) {
461
            $C = openssl_encrypt($CB[$i], $mode, $K, OPENSSL_NO_PADDING | OPENSSL_RAW_DATA);
462
            $Y[$i] = self::getBitXor(mb_substr($X, ($i - 1) * 16, 16, '8bit'), $C);
463
        }
464
465
        $Xn = mb_substr($X, ($n - 1) * 16, null, '8bit');
466
        $C = openssl_encrypt($CB[$n], $mode, $K, OPENSSL_NO_PADDING | OPENSSL_RAW_DATA);
467
        $Y[$n] = self::getBitXor($Xn, self::getMSB(self::getLength($Xn), $C));
468
469
        return implode('', $Y);
470
    }
471
472
    /**
473
     * @param string $o1
474
     * @param string $o2
475
     *
476
     * @return string
477
     */
478
    private static function getBitXor($o1, $o2)
479
    {
480
        $xorWidth = PHP_INT_SIZE;
481
        $o1 = str_split($o1, $xorWidth);
482
        $o2 = str_split($o2, $xorWidth);
483
        $res = '';
484
        $runs = count($o1);
485
        for ($i = 0; $i < $runs; $i++) {
486
            $res .= $o1[$i] ^ $o2[$i];
487
        }
488
489
        return $res;
490
    }
491
}
492