Completed
Push — master ( 5c3fef...2c2e3e )
by Florent
02:40
created

GCM::calcVector()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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