Completed
Push — master ( b8db86...b90281 )
by Florent
01:36
created

AESGCM::decryptWithAppendedTag()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 5
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
        Assertion::string($IV, 'The Initialization Vector must be a binary string.');
31
        Assertion::nullOrString($P, 'The data to encrypt must be null or a binary string.');
32
        Assertion::nullOrString($A, 'The Additional Authentication Data must be null or a binary string.');
33
        Assertion::integer($tag_length, 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
34
        Assertion::inArray($tag_length, [128, 120, 112, 104, 96], 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
35
        list($J0, $v, $a_len_padding, $H) = self::common($K, $IV, $A);
36
37
        $C = self::getGCTR($K, self::getInc(32, $J0), $P);
38
        $u = self::calcVector($C);
39
        $c_len_padding = self::addPadding($C);
40
41
        $S = self::getHash($H, $A.str_pad('', $v / 8, "\0").$C.str_pad('', $u / 8, "\0").$a_len_padding.$c_len_padding);
42
        $T = self::getMSB($tag_length, self::getGCTR($K, $J0, $S));
43
44
        return [$C, $T];
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      $IV Initialization vector
66
     * @param string|null $C  Data to encrypt (null for authentication)
67
     * @param string|null $A  Additional Authentication Data
68
     * @param string      $T  Tag
69
     *
70
     * @return string
71
     */
72
    public static function decrypt($K, $IV, $C = null, $A = null, $T)
73
    {
74
        Assertion::string($K, 'The key encryption key must be a binary string.');
75
        Assertion::string($IV, 'The Initialization Vector must be a binary string.');
76
        Assertion::nullOrString($C, 'The data to encrypt must be null or a binary string.');
77
        Assertion::nullOrString($A, 'The Additional Authentication Data must be null or a binary string.');
78
79
        $tag_length = self::getLength($T);
80
        Assertion::integer($tag_length, 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
81
        Assertion::inArray($tag_length, [128, 120, 112, 104, 96], 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
82
        list($J0, $v, $a_len_padding, $H) = self::common($K, $IV, $A);
83
84
        $P = self::getGCTR($K, self::getInc(32, $J0), $C);
85
86
        $u = self::calcVector($C);
87
        $c_len_padding = self::addPadding($C);
88
89
        $S = self::getHash($H, $A.str_pad('', $v / 8, "\0").$C.str_pad('', $u / 8, "\0").$a_len_padding.$c_len_padding);
90
        $T1 = self::getMSB($tag_length, self::getGCTR($K, $J0, $S));
91
        $result = strcmp($T, $T1);
92
        Assertion::eq($result, 0, 'Unable to decrypt or to verify the tag.');
93
94
        return $P;
95
    }
96
97
    /**
98
     * This method should be used if the tag is appended at the end of the ciphertext.
99
     * It is used by some AES GCM implementations such as the Java one.
100
     *
101
     * @param string      $K          Key encryption key
102
     * @param string      $IV         Initialization vector
103
     * @param string|null $Ciphertext Data to encrypt (null for authentication)
104
     * @param string|null $A          Additional Authentication Data
105
     * @param int         $tag_length Tag length
106
     *
107
     * @return string
108
     *
109
     * @see self::encryptAndAppendTag
110
     */
111
    public static function decryptWithAppendedTag($K, $IV, $Ciphertext = null, $A = null, $tag_length = 128)
112
    {
113
        $tag_length_in_bits = $tag_length/8;
114
        $C = mb_substr($Ciphertext, 0, -$tag_length_in_bits, '8bit');
115
        $T = mb_substr($Ciphertext, -$tag_length_in_bits, null, '8bit');
116
117
        return self::decrypt($K, $IV, $C, $A, $T);
118
    }
119
120
    /**
121
     * @param $K
122
     * @param $IV
123
     * @param $A
124
     *
125
     * @return array
126
     */
127
    private static function common($K, $IV, $A)
128
    {
129
        $key_length = mb_strlen($K, '8bit') * 8;
130
        Assertion::inArray($key_length, [128, 192, 256], 'Bad key encryption key length.');
131
132
        $H = openssl_encrypt(str_repeat("\0", 16), 'aes-'.($key_length).'-ecb', $K, OPENSSL_NO_PADDING | OPENSSL_RAW_DATA); //---
133
        $iv_len = self::getLength($IV);
134
135
        if (96 === $iv_len) {
136
            $J0 = $IV.pack('H*', '00000001');
137
        } else {
138
            $s = self::calcVector($IV);
139
            Assertion::eq(($s + 64) % 8, 0, 'Unable to decrypt or to verify the tag.');
140
141
            $packed_iv_len = pack('N', $iv_len);
142
            $iv_len_padding = str_pad($packed_iv_len, 8, "\0", STR_PAD_LEFT);
143
            $hash_X = $IV.str_pad('', ($s + 64) / 8, "\0").$iv_len_padding;
144
            $J0 = self::getHash($H, $hash_X);
145
        }
146
        $v = self::calcVector($A);
147
        $a_len_padding = self::addPadding($A);
148
149
        return [$J0, $v, $a_len_padding, $H];
150
    }
151
152
    /**
153
     * @param string $value
154
     *
155
     * @return int
156
     */
157
    private static function calcVector($value)
158
    {
159
        return (128 * ceil(self::getLength($value) / 128)) - self::getLength($value);
160
    }
161
162
    /**
163
     * @param string $value
164
     *
165
     * @return string
166
     */
167
    private static function addPadding($value)
168
    {
169
        return str_pad(pack('N', self::getLength($value)), 8, "\0", STR_PAD_LEFT);
170
    }
171
172
    /**
173
     * @param string $x
174
     *
175
     * @return int
176
     */
177
    private static function getLength($x)
178
    {
179
        return mb_strlen($x, '8bit') * 8;
180
    }
181
182
    /**
183
     * @param int $num_bits
184
     * @param int $x
185
     *
186
     * @return string
187
     */
188
    private static function getMSB($num_bits, $x)
189
    {
190
        $num_bytes = $num_bits / 8;
191
192
        return mb_substr($x, 0, $num_bytes, '8bit');
193
    }
194
195
    /**
196
     * @param int $num_bits
197
     * @param int $x
198
     *
199
     * @return string
200
     */
201
    private static function getLSB($num_bits, $x)
202
    {
203
        $num_bytes = ($num_bits / 8);
204
205
        return mb_substr($x, -$num_bytes, null, '8bit');
206
    }
207
208
    /**
209
     * @param int $s_bits
210
     * @param int $x
211
     *
212
     * @return string
213
     */
214
    private static function getInc($s_bits, $x)
215
    {
216
        $lsb = self::getLSB($s_bits, $x);
217
        $X = self::toUInt32Bits($lsb) + 1;
218
        $res = self::getMSB(self::getLength($x) - $s_bits, $x).pack('N', $X);
219
220
        return $res;
221
    }
222
223
    /**
224
     * @param string $bin
225
     *
226
     * @return mixed
227
     */
228
    private static function toUInt32Bits($bin)
229
    {
230
        list(, $h, $l) = unpack('n*', $bin);
231
232
        return $l + ($h * 0x010000);
233
    }
234
235
    /**
236
     * @param $X
237
     * @param $Y
238
     *
239
     * @return string
240
     */
241
    private static function getProduct($X, $Y)
242
    {
243
        $R = pack('H*', 'E1').str_pad('', 15, "\0");
244
        $Z = str_pad('', 16, "\0");
245
        $V = $Y;
246
247
        $parts = str_split($X, 4);
248
        $x = sprintf('%032b%032b%032b%032b', self::toUInt32Bits($parts[0]), self::toUInt32Bits($parts[1]), self::toUInt32Bits($parts[2]), self::toUInt32Bits($parts[3]));
249
        $lsb_mask = "\1";
250
        for ($i = 0; $i < 128; $i++) {
251
            if ($x[$i]) {
252
                $Z = self::getBitXor($Z, $V);
253
            }
254
            $lsb_8 = mb_substr($V, -1, null, '8bit');
255
            if (ord($lsb_8 & $lsb_mask)) {
256
                $V = self::getBitXor(self::shiftStringToRight($V), $R);
257
            } else {
258
                $V = self::shiftStringToRight($V);
259
            }
260
        }
261
262
        return $Z;
263
    }
264
265
    /**
266
     * @param string $input
267
     *
268
     * @return string
269
     */
270
    private static function shiftStringToRight($input)
271
    {
272
        $width = 4;
273
        $parts = array_map('self::toUInt32Bits', str_split($input, $width));
274
        $runs = count($parts);
275
276
        for ($i = $runs - 1; $i >= 0; $i--) {
277
            if ($i) {
278
                $lsb1 = $parts[$i - 1] & 0x00000001;
279
                if ($lsb1) {
280
                    $parts[$i] = ($parts[$i] >> 1) | 0x80000000;
281
                    $parts[$i] = pack('N', $parts[$i]);
282
                    continue;
283
                }
284
            }
285
            $parts[$i] = ($parts[$i] >> 1) & 0x7FFFFFFF;
286
            $parts[$i] = pack('N', $parts[$i]);
287
        }
288
        $res = implode('', $parts);
289
290
        return $res;
291
    }
292
293
    /**
294
     * @param string $H
295
     * @param string $X
296
     *
297
     * @return mixed
298
     */
299
    private static function getHash($H, $X)
300
    {
301
        $Y = [];
302
        $Y[0] = str_pad('', 16, "\0");
303
        $num_blocks = (int) (mb_strlen($X, '8bit') / 16);
304
        for ($i = 1; $i <= $num_blocks; $i++) {
305
            $Y[$i] = self::getProduct(self::getBitXor($Y[$i - 1], mb_substr($X, ($i - 1) * 16, 16, '8bit')), $H);
306
        }
307
308
        return $Y[$num_blocks];
309
    }
310
311
    /**
312
     * @param string $K
313
     * @param string $ICB
314
     * @param string $X
315
     *
316
     * @return string
317
     */
318
    private static function getGCTR($K, $ICB, $X)
319
    {
320
        if (empty($X)) {
321
            return '';
322
        }
323
324
        $n = (int) ceil(self::getLength($X) / 128);
325
        $CB = [];
326
        $Y = [];
327
        $CB[1] = $ICB;
328
        for ($i = 2; $i <= $n; $i++) {
329
            $CB[$i] = self::getInc(32, $CB[$i - 1]);
330
        }
331
        $key_length = strlen($K) * 8;
332
        $mode = 'aes-'.($key_length).'-ecb';
333
        for ($i = 1; $i < $n; $i++) {
334
            $C = openssl_encrypt($CB[$i], $mode, $K, OPENSSL_NO_PADDING | OPENSSL_RAW_DATA);
335
            $Y[$i] = self::getBitXor(mb_substr($X, ($i - 1) * 16, 16, '8bit'), $C);
336
        }
337
338
        $Xn = mb_substr($X, ($n - 1) * 16, null, '8bit');
339
        $C = openssl_encrypt($CB[$n], $mode, $K, OPENSSL_NO_PADDING | OPENSSL_RAW_DATA);
340
        $Y[$n] = self::getBitXor($Xn, self::getMSB(self::getLength($Xn), $C));
341
342
        return implode('', $Y);
343
    }
344
345
    /**
346
     * @param string $o1
347
     * @param string $o2
348
     *
349
     * @return string
350
     */
351
    private static function getBitXor($o1, $o2)
352
    {
353
        $xorWidth = PHP_INT_SIZE;
354
        $o1 = str_split($o1, $xorWidth);
355
        $o2 = str_split($o2, $xorWidth);
356
        $res = '';
357
        $runs = count($o1);
358
        for ($i = 0; $i < $runs; $i++) {
359
            $res .= $o1[$i] ^ $o2[$i];
360
        }
361
362
        return $res;
363
    }
364
}
365