GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

GCM   A
last analyzed

Complexity

Total Complexity 19

Size/Duplication

Total Lines 278
Duplicated Lines 0 %

Test Coverage

Coverage 98.51%

Importance

Changes 0
Metric Value
eloc 61
dl 0
loc 278
ccs 66
cts 67
cp 0.9851
rs 10
c 0
b 0
f 0
wmc 19

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 2
A gmpToStr() 0 8 2
A encrypt() 0 10 1
A _generateJ0() 0 8 2
A decrypt() 0 13 2
A _pad128() 0 7 2
A strToGMP() 0 5 1
A _gctr() 0 23 3
A _computeAuthTag() 0 7 1
A _inc32() 0 8 1
A _uint64() 0 7 2
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace Sop\GCM;
6
7
use Sop\GCM\Cipher\Cipher;
8
use Sop\GCM\Exception\AuthenticationException;
9
10
/**
11
 * Implements encryption and decryption in Galois/Counter Mode.
12
 *
13
 * @see http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf
14
 * @see http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf
15
 */
16
class GCM
17
{
18
    /**
19
     * Block of 64 zero bits.
20
     *
21
     * @var string
22
     */
23
    const ZB_64 = "\0\0\0\0\0\0\0\0";
24
25
    /**
26
     * Block of 128 zero bits.
27
     *
28
     * @var string
29
     */
30
    const ZB_128 = self::ZB_64 . self::ZB_64;
31
32
    /**
33
     * Array of supported t-values, that is, the bit length of the
34
     * authentication tag.
35
     *
36
     * See NIST SP-800-38D section 5.2.1.2 for the details.
37
     *
38
     * @internal
39
     *
40
     * @var array
41
     */
42
    const SUPPORTED_T_LEN = [128, 120, 112, 104, 96, 64, 32];
43
44
    /**
45
     * Cipher.
46
     *
47
     * @var Cipher
48
     */
49
    protected $_cipher;
50
51
    /**
52
     * Authentication tag length in bytes.
53
     *
54
     * @var int
55
     */
56
    protected $_tagLength;
57
58
    /**
59
     * Constructor.
60
     *
61
     * @param Cipher $cipher     Cipher implementation
62
     * @param int    $tag_length Authentication tag length in bytes
63
     *
64
     * @throws \DomainException If tag length is not supported
65
     */
66 44
    public function __construct(Cipher $cipher, int $tag_length = 16)
67
    {
68 44
        if (!in_array($tag_length << 3, self::SUPPORTED_T_LEN)) {
69 1
            throw new \DomainException(
70 1
                "Tag length {$tag_length} is not supported.");
71
        }
72 43
        $this->_cipher = $cipher;
73 43
        $this->_tagLength = $tag_length;
74 43
    }
75
76
    /**
77
     * Encrypt plaintext.
78
     *
79
     * @param string $P  Plaintext
80
     * @param string $A  Additional authenticated data
81
     * @param string $K  Encryption key
82
     * @param string $IV Initialization vector
83
     *
84
     * @throws \RuntimeException For generic errors
85
     *
86
     * @return array Tuple of ciphertext `C` and authentication tag `T`
87
     */
88 45
    public function encrypt(string $P, string $A, string $K, string $IV): array
89
    {
90 45
        $ghash = new GHASH($this->_cipher->encrypt(self::ZB_128, $K));
91
        // generate pre-counter block
92 45
        $J0 = $this->_generateJ0($IV, $ghash);
93
        // encrypt
94 45
        $C = $this->_gctr(self::_inc32($J0), $P, $K);
95
        // generate authentication tag
96 45
        $T = $this->_computeAuthTag($A, $C, $J0, $K, $ghash);
97 45
        return [$C, $T];
98
    }
99
100
    /**
101
     * Decrypt ciphertext.
102
     *
103
     * @param string $C  Ciphertext
104
     * @param string $T  Authentication tag
105
     * @param string $A  Additional authenticated data
106
     * @param string $K  Encryption key
107
     * @param string $IV Initialization vector
108
     *
109
     * @throws AuthenticationException If message authentication fails
110
     * @throws \RuntimeException       For generic errors
111
     *
112
     * @return string Plaintext `P`
113
     */
114 43
    public function decrypt(string $C, string $T, string $A, string $K, string $IV): string
115
    {
116 43
        $ghash = new GHASH($this->_cipher->encrypt(self::ZB_128, $K));
117
        // generate pre-counter block
118 43
        $J0 = $this->_generateJ0($IV, $ghash);
119
        // generate authentication tag
120 43
        $T2 = $this->_computeAuthTag($A, $C, $J0, $K, $ghash);
121
        // check that authentication tag matches
122 43
        if (!hash_equals($T2, $T)) {
123 1
            throw new AuthenticationException('Authentication failed.');
124
        }
125
        // decrypt
126 42
        return $this->_gctr(self::_inc32($J0), $C, $K);
127
    }
128
129
    /**
130
     * Convert string to GMP number.
131
     *
132
     * String is interpreted as an unsigned integer with big endian order and
133
     * the most significant byte first.
134
     *
135
     * @param string $data Binary data
136
     *
137
     * @return \GMP
138
     */
139 51
    public static function strToGMP(string $data): \GMP
140
    {
141 51
        $num = gmp_import($data, 1, GMP_MSW_FIRST | GMP_BIG_ENDIAN);
142 51
        assert($num instanceof \GMP, new \RuntimeException('gmp_import() failed.'));
143 51
        return $num;
144
    }
145
146
    /**
147
     * Convert GMP number to string.
148
     *
149
     * Returned string represents an unsigned integer with big endian order and
150
     * the most significant byte first.
151
     *
152
     * @param \GMP $num  GMP number
153
     * @param int  $size Width of the string in bytes
154
     *
155
     * @return string Binary data
156
     */
157 51
    public static function gmpToStr(\GMP $num, int $size): string
158
    {
159 51
        $data = gmp_export($num, 1, GMP_MSW_FIRST | GMP_BIG_ENDIAN);
160 51
        $len = strlen($data);
161 51
        if ($len < $size) {
162 41
            $data = str_repeat("\0", $size - $len) . $data;
163
        }
164 51
        return $data;
165
    }
166
167
    /**
168
     * Generate pre-counter block.
169
     *
170
     * See NIST SP-300-38D section 7.1 step 2 for the details.
171
     *
172
     * @param string $IV    Initialization vector
173
     * @param GHASH  $ghash GHASH functor
174
     *
175
     * @return string
176
     */
177 47
    private function _generateJ0(string $IV, GHASH $ghash): string
178
    {
179
        // if len(IV) = 96
180 47
        if (12 === strlen($IV)) {
181 39
            return $IV . "\0\0\0\1";
182
        }
183 8
        $data = self::_pad128($IV) . self::ZB_64 . self::_uint64(strlen($IV) << 3);
184 8
        return $ghash($data);
185
    }
186
187
    /**
188
     * Apply GCTR algorithm.
189
     *
190
     * See NIST SP-300-38D section 6.5 for the details.
191
     *
192
     * @param string $ICB Initial counter block
193
     * @param string $X   Input data
194
     * @param string $K   Encryption key
195
     *
196
     * @return string Output data
197
     */
198 47
    private function _gctr(string $ICB, string $X, string $K): string
199
    {
200
        // if data is an empty string, return an empty string
201 47
        if ('' === $X) {
202 13
            return '';
203
        }
204
        // number of blocks
205 47
        $n = ceil(strlen($X) / 16);
206 47
        $CB = $ICB;
207 47
        $Y = '';
208 47
        for ($i = 0; $i < $n - 1; ++$i) {
209
            // plaintext block
210 30
            $xi = substr($X, $i << 4, 16);
211
            // encrypt block and append to Y
212 30
            $Y .= $xi ^ $this->_cipher->encrypt($CB, $K);
213
            // increment counter block
214 30
            $CB = self::_inc32($CB);
215
        }
216
        // final block
217 47
        $xn = substr($X, $i << 4);
218
        // XOR against partial block
219 47
        $Y .= $xn ^ substr($this->_cipher->encrypt($CB, $K), 0, strlen($xn));
220 47
        return $Y;
221
    }
222
223
    /**
224
     * Compute authentication tag.
225
     *
226
     * See NIST SP-300-38D section 7.1 steps 5-6 for the details.
227
     *
228
     * @param string $A     Additional authenticated data
229
     * @param string $C     Ciphertext
230
     * @param string $J0    Pre-counter block
231
     * @param string $K     Encryption key
232
     * @param GHASH  $ghash GHASH functor
233
     *
234
     * @return string Authentication tag `T`
235
     */
236 47
    private function _computeAuthTag(string $A, string $C, string $J0, string $K,
237
        GHASH $ghash): string
238
    {
239 47
        $data = self::_pad128($A) . self::_pad128($C) .
240 47
             self::_uint64(strlen($A) << 3) . self::_uint64(strlen($C) << 3);
241 47
        $S = $ghash($data);
242 47
        return substr($this->_gctr($J0, $S, $K), 0, $this->_tagLength);
243
    }
244
245
    /**
246
     * Pad data to 128 bit block boundary.
247
     *
248
     * @param string $data
249
     *
250
     * @return string
251
     */
252 47
    private static function _pad128(string $data): string
253
    {
254 47
        $padlen = 16 - strlen($data) % 16;
255 47
        if (16 !== $padlen) {
256 37
            $data .= str_repeat("\0", $padlen);
257
        }
258 47
        return $data;
259
    }
260
261
    /**
262
     * Increment 32 rightmost bits of the counter block.
263
     *
264
     * See NIST SP-300-38D section 6.2 for the details.
265
     *
266
     * @param string $X
267
     *
268
     * @return string
269
     */
270 47
    private static function _inc32(string $X): string
271
    {
272 47
        $Y = substr($X, 0, -4);
273
        // increment counter
274 47
        $n = self::strToGMP(substr($X, -4)) + 1;
275
        // wrap by using only the 32 rightmost bits
276 47
        $Y .= substr(self::gmpToStr($n, 4), -4);
277 47
        return $Y;
278
    }
279
280
    /**
281
     * Convert integer to 64 bit big endian binary string.
282
     *
283
     * @param int $num
284
     *
285
     * @return string
286
     */
287 47
    private static function _uint64(int $num): string
288
    {
289
        // truncate on 32 bit hosts
290 47
        if (PHP_INT_SIZE < 8) {
291
            return "\0\0\0\0" . pack('N', $num);
292
        }
293 47
        return pack('J', $num);
294
    }
295
}
296