Completed
Push — master ( 45f73a...5c3fef )
by Florent
02:28
created

GCM::getProduct()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 24
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 24
rs 8.6845
cc 4
eloc 17
nc 5
nop 2
1
<?php
2
3
/*
4
 * The MIT License (MIT)
5
 *
6
 * Copyright (c) 2014-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 Jose\Util;
13
14
use Assert\Assertion;
15
16
final class GCM
17
{
18
    /**
19
     * @param string $K
20
     * @param string $IV
21
     * @param string $P
22
     * @param string $A
23
     * @param int    $t
24
     *
25
     * @return array|null
26
     */
27
    public static function encrypt($K, $IV, $P, $A, $t = 128)
28
    {
29
        $cipher = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
30
        Assertion::notNull($cipher);
31
32
        $key_length = StringUtil::getStringLength($K) * 8;
33
        Assertion::inArray($key_length, [128, 192, 256]);
34
35
        $iv = str_repeat(chr(0), 16);  // initialize to 16 byte string of "0"s
36
        $s = mcrypt_generic_init($cipher, $K, $iv);
37
        Assertion::greaterOrEqualThan($s, 0);
38
39
        $H = mcrypt_generic($cipher, StringUtil::addPadding('', 16, "\0"));
40
        $iv_len = self::getLength($IV);
41
        if ($iv_len == 96) {
42
            $J0 = $IV.pack('H*', '00000001');
43
        } else {
44
            $s = (128 * ceil($iv_len / 128)) - $iv_len;
45
            Assertion::eq(($s + 64) % 8, 0);
46
            $packed_iv_len = pack('N', $iv_len);
47
            $iv_len_padding = StringUtil::addPadding($packed_iv_len, 8, "\0", STR_PAD_LEFT);
48
            $hash_X = $IV.StringUtil::addPadding('', ($s + 64) / 8, "\0").$iv_len_padding;
49
            $J0 = self::getHash($H, $hash_X);
50
        }
51
        $C = self::getGCTR($K, self::getInc(32, $J0), $P);
52
53
        $u = (128 * ceil(self::getLength($C) / 128)) - self::getLength($C);
54
        $v = (128 * ceil(self::getLength($A) / 128)) - self::getLength($A);
55
        $a_len_padding = StringUtil::addPadding(pack('N', self::getLength($A)), 8, "\0", STR_PAD_LEFT);
56
        $c_len_padding = StringUtil::addPadding(pack('N', self::getLength($C)), 8, "\0", STR_PAD_LEFT);
57
58
        $S = self::getHash($H, $A.StringUtil::addPadding('', $v / 8, "\0").$C.StringUtil::addPadding('', $u / 8, "\0").$a_len_padding.$c_len_padding);
59
        $T = self::getMSB($t, self::getGCTR($K, $J0, $S));
60
        mcrypt_generic_deinit($cipher);
61
        mcrypt_module_close($cipher);
62
63
        return [$C, $T];
64
    }
65
66
    /**
67
     * @param string $K
68
     * @param string $IV
69
     * @param string $C
70
     * @param string $A
71
     * @param string $T
72
     *
73
     * @return array|null
74
     */
75
    public static function decrypt($K, $IV, $C, $A, $T)
76
    {
77
        $cipher = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
78
        if (!$cipher) {
79
            return;
80
        }
81
        $key_length = StringUtil::getStringLength($K) * 8;
82
        Assertion::inArray($key_length, [128, 192, 256]);
83
84
85
        $iv = str_repeat(chr(0), 16);  // initialize to 16 byte string of "0"s
86
        $s = mcrypt_generic_init($cipher, $K, $iv);
87
        Assertion::greaterOrEqualThan($s, 0);
88
89
        $H = mcrypt_generic($cipher, StringUtil::addPadding('', 16, "\0"));
90
91
        $iv_len = self::getLength($IV);
92
        if ($iv_len == 96) {
93
            $J0 = $IV.pack('H*', '00000001');
94
        } else {
95
            $s = (128 * ceil($iv_len / 128)) - $iv_len;
96
            Assertion::notEq(($s + 64) % 8, 0);
97
98
            $packed_iv_len = pack('N', $iv_len);
99
            $iv_len_padding = StringUtil::addPadding($packed_iv_len, 8, "\0", STR_PAD_LEFT);
100
            $hash_X = $IV.StringUtil::addPadding('', ($s + 64) / 8, "\0").$iv_len_padding;
101
            $J0 = self::getHash($H, $hash_X);
102
        }
103
        $P = self::getGCTR($K, self::getInc(32, $J0), $C);
104
105
        $u = (128 * ceil(self::getLength($C) / 128)) - self::getLength($C);
106
        $v = (128 * ceil(self::getLength($A) / 128)) - self::getLength($A);
107
        $a_len_padding = StringUtil::addPadding(pack('N', self::getLength($A)), 8, "\0", STR_PAD_LEFT);
108
        $c_len_padding = StringUtil::addPadding(pack('N', self::getLength($C)), 8, "\0", STR_PAD_LEFT);
109
110
        $S = self::getHash($H, $A.StringUtil::addPadding('', $v / 8, "\0").$C.StringUtil::addPadding('', $u / 8, "\0").$a_len_padding.$c_len_padding);
111
        $T1 = self::getMSB(self::getLength($T), self::getGCTR($K, $J0, $S));
112
        $result = strcmp($T, $T1);
113
        if ($result) {
114
            return;
115
        }
116
        mcrypt_generic_deinit($cipher);
117
        mcrypt_module_close($cipher);
118
119
        return $P;
120
    }
121
122
    /**
123
     * @param string $x
124
     *
125
     * @return int
126
     */
127
    private static function getLength($x)
128
    {
129
        return StringUtil::getStringLength($x) * 8;
130
    }
131
132
    /**
133
     * @param int $num_bits
134
     * @param int $x
135
     *
136
     * @return string
137
     */
138
    private static function getMSB($num_bits, $x)
139
    {
140
        Assertion::string($x);
141
        Assertion::integer($num_bits);
142
        Assertion::eq($num_bits % 8, 0);
143
144
        $num_bytes = $num_bits / 8;
145
        $len_x = StringUtil::getStringLength($x);
146
        Assertion::lessOrEqualThan($num_bytes, $len_x);
147
148
        return StringUtil::getSubString($x, 0, $num_bytes);
149
    }
150
151
    /**
152
     * @param int $num_bits
153
     * @param int $x
154
     *
155
     * @return string
156
     */
157
    private static function getLSB($num_bits, $x)
158
    {
159
        Assertion::string($x);
160
        Assertion::integer($num_bits);
161
        Assertion::eq($num_bits % 8, 0);
162
163
        $num_bytes = ($num_bits / 8);
164
        Assertion::lessOrEqualThan($num_bytes, StringUtil::getStringLength($x));
165
166
        return StringUtil::getSubString($x, $num_bytes * -1);
167
    }
168
169
    /**
170
     * @param int $s_bits
171
     * @param int $x
172
     *
173
     * @return string
174
     */
175
    private static function getInc($s_bits, $x)
176
    {
177
        Assertion::eq($s_bits, 32);
178
        Assertion::string($x);
179
180
        $lsb = self::getLSB($s_bits, $x);
181
        $X = (self::toUInt32Bits($lsb) + 1);
182
        $res = self::getMSB(self::getLength($x) - $s_bits, $x).pack('N', $X);
183
184
        return $res;
185
    }
186
187
    /**
188
     * @param string $bin
189
     *
190
     * @return mixed
191
     */
192
    private static function toUInt32Bits($bin)
193
    {
194
        // $bin is the binary 32-bit BE string that represents the integer
195
        $int_size = 4;
196
        if ($int_size <= 4) {
197
            list(, $h, $l) = unpack('n*', $bin);
198
199
            return $l + ($h * 0x010000);
200
        } else {
201
            list(, $int) = unpack('N', $bin);
202
203
            return $int;
204
        }
205
    }
206
207
    /**
208
     * @param $X
209
     * @param $Y
210
     *
211
     * @return string
212
     */
213
    private static function getProduct($X, $Y)
214
    {
215
        $R = pack('H*', 'E1').StringUtil::addPadding('', 15, "\0");
216
        $Z = StringUtil::addPadding('', 16, "\0");
217
        $V = $Y;
218
        Assertion::eq(StringUtil::getStringLength($X), 16);
219
220
        $parts = str_split($X, 4);
221
        $x = sprintf('%032b%032b%032b%032b', self::toUInt32Bits($parts[0]), self::toUInt32Bits($parts[1]), self::toUInt32Bits($parts[2]), self::toUInt32Bits($parts[3]));
222
        $lsb_mask = "\1";
223
        for ($i = 0; $i < 128; $i++) {
224
            if ($x[$i]) {
225
                $Z = self::getBitXor($Z, $V);
226
            }
227
            $lsb_8 = StringUtil::getSubString($V, -1);
228
            if (ord($lsb_8 & $lsb_mask)) {
229
                $V = self::getBitXor(self::shiftStringToRight($V), $R);
230
            } else {
231
                $V = self::shiftStringToRight($V);
232
            }
233
        }
234
235
        return $Z;
236
    }
237
238
    /**
239
     * @param string $input
240
     *
241
     * @return string
242
     */
243
    private static function shiftStringToRight($input)
244
    {
245
        $width = 4;
246
        $parts = array_map('self::toUInt32Bits', str_split($input, $width));
247
        $runs = count($parts);
248
        $len = StringUtil::getStringLength($input) / 4;
249
        Assertion::integer($len);
250
251
        for ($i = $runs - 1; $i >= 0; $i--) {
252
            if ($i) {
253
                $lsb1 = $parts[$i - 1] & 0x00000001;
254
                if ($lsb1) {
255
                    $parts[$i] = ($parts[$i] >> 1) | 0x80000000;
256
                    $parts[$i] = pack('N', $parts[$i]);
257
                    continue;
258
                }
259
            }
260
            $parts[$i] = ($parts[$i] >> 1) & 0x7FFFFFFF; // get rid of sign bit
261
            $parts[$i] = pack('N', $parts[$i]);
262
        }
263
        $res = implode('', $parts);
264
265
        return $res;
266
    }
267
268
    /**
269
     * @param string $H
270
     * @param string $X
271
     *
272
     * @return mixed
273
     */
274
    private static function getHash($H, $X)
275
    {
276
        Assertion::string($H);
277
        Assertion::string($X);
278
        Assertion::eq(StringUtil::getStringLength($X) % 16, 0);
279
280
        $Y = [];
281
        $Y[0] = StringUtil::addPadding('', 16, "\0");
282
        $num_blocks = (int)(StringUtil::getStringLength($X) / 16);
283
        for ($i = 1; $i <= $num_blocks; $i++) {
284
            $Y[$i] = self::getProduct(self::getBitXor($Y[$i - 1], StringUtil::getSubString($X, ($i - 1) * 16, 16)), $H);
285
        }
286
287
        return $Y[$num_blocks];
288
    }
289
290
    /**
291
     * @param string $K
292
     * @param string $ICB
293
     * @param string $X
294
     *
295
     * @return null|string
296
     */
297
    private static function getGCTR($K, $ICB, $X)
298
    {
299
        if ($X == '') {
300
            return '';
301
        }
302
303
        $cipher = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
304
        if (!$cipher) {
305
            return;
306
        }
307
        $key_length = StringUtil::getStringLength($K) * 8;
308
309
        Assertion::integer($key_length, [128, 192, 256]);
0 ignored issues
show
Documentation introduced by
array(128, 192, 256) is of type array<integer,integer,{"...nteger","2":"integer"}>, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
310
311
        $iv = str_repeat(chr(0), 16);  // initialize to 16 byte string of "0"s
312
        $s = mcrypt_generic_init($cipher, $K, $iv);
313
        Assertion::greaterOrEqualThan($s, 0);
314
315
        $n = (int)ceil(self::getLength($X) / 128);
316
        $CB = [];
317
        $Y = [];
318
        $CB[1] = $ICB;
319
        for ($i = 2; $i <= $n; $i++) {
320
            $CB[$i] = self::getInc(32, $CB[$i - 1]);
321
        }
322
        for ($i = 1; $i < $n; $i++) {
323
            $C = mcrypt_generic($cipher, $CB[$i]);
324
            $Y[$i] = self::getBitXor(StringUtil::getSubString($X, ($i - 1) * 16, 16), $C);
325
        }
326
327
        $Xn = StringUtil::getSubString($X, ($n - 1) * 16);
328
        $C = mcrypt_generic($cipher, $CB[$n]);
329
        $Y[$n] = self::getBitXor($Xn, self::getMSB(self::getLength($Xn), $C));
330
        mcrypt_generic_deinit($cipher);
331
        mcrypt_module_close($cipher);
332
333
        return implode('', $Y);
334
    }
335
336
    /**
337
     * @param string $o1
338
     * @param string $o2
339
     *
340
     * @return string
341
     */
342
    private static function getBitXor($o1, $o2)
343
    {
344
        $xorWidth = PHP_INT_SIZE;
345
        $o1 = str_split($o1, $xorWidth);
346
        $o2 = str_split($o2, $xorWidth);
347
        $res = '';
348
        $runs = count($o1);
349
        for ($i = 0; $i < $runs; $i++) {
350
            $res .= $o1[$i] ^ $o2[$i];
351
        }
352
353
        return $res;
354
    }
355
}
356