Completed
Push — master ( a69544...c28e38 )
by Florent
02:37
created

AESGCM::getHash()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

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