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.
Passed
Push — php72 ( ee07bb...18e574 )
by Joni
01:52
created

GCM::_pad128()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 7
ccs 5
cts 5
cp 1
crap 2
rs 10
c 0
b 0
f 0
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 46
    public function __construct(Cipher $cipher, int $tag_length = 16)
67
    {
68 46
        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 45
        $this->_cipher = $cipher;
73 45
        $this->_tagLength = $tag_length;
74 45
    }
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 <code>C</code> and authentication tag
87
     *               <code>T</code>
88
     */
89 46
    public function encrypt(string $P, string $A, string $K, string $IV): array
90
    {
91 46
        $ghash = new GHASH($this->_cipher->encrypt(self::ZB_128, $K));
92
        // generate pre-counter block
93 46
        $J0 = $this->_generateJ0($IV, $ghash);
94
        // encrypt
95 46
        $C = $this->_gctr(self::_inc32($J0), $P, $K);
96
        // generate authentication tag
97 46
        $T = $this->_computeAuthTag($A, $C, $J0, $K, $ghash);
98 46
        return [$C, $T];
99
    }
100
101
    /**
102
     * Decrypt ciphertext.
103
     *
104
     * @param string $C  Ciphertext
105
     * @param string $T  Authentication tag
106
     * @param string $A  Additional authenticated data
107
     * @param string $K  Encryption key
108
     * @param string $IV Initialization vector
109
     *
110
     * @throws AuthenticationException If message authentication fails
111
     * @throws \RuntimeException       For generic errors
112
     *
113
     * @return string Plaintext <code>P</code>
114
     */
115 44
    public function decrypt(string $C, string $T, string $A, string $K,
116
        string $IV): string
117
    {
118 44
        $ghash = new GHASH($this->_cipher->encrypt(self::ZB_128, $K));
119
        // generate pre-counter block
120 44
        $J0 = $this->_generateJ0($IV, $ghash);
121
        // generate authentication tag
122 44
        $T2 = $this->_computeAuthTag($A, $C, $J0, $K, $ghash);
123
        // check that authentication tag matches
124 44
        if (!hash_equals($T2, $T)) {
125 1
            throw new AuthenticationException('Authentication failed.');
126
        }
127
        // decrypt
128 43
        return $this->_gctr(self::_inc32($J0), $C, $K);
129
    }
130
131
    /**
132
     * Convert string to GMP number.
133
     *
134
     * String is interpreted as an unsigned integer with big endian order and
135
     * the most significant byte first.
136
     *
137
     * @param string $data Binary data
138
     *
139
     * @return \GMP
140
     */
141 53
    public static function strToGMP(string $data): \GMP
142
    {
143 53
        $num = gmp_import($data, 1, GMP_MSW_FIRST | GMP_BIG_ENDIAN);
144 53
        assert($num instanceof \GMP, new \RuntimeException('gmp_import() failed.'));
145 53
        return $num;
146
    }
147
148
    /**
149
     * Convert GMP number to string.
150
     *
151
     * Returned string represents an unsigned integer with big endian order and
152
     * the most significant byte first.
153
     *
154
     * @param \GMP $num  GMP number
155
     * @param int  $size Width of the string in bytes
156
     *
157
     * @return string Binary data
158
     */
159 53
    public static function gmpToStr(\GMP $num, int $size): string
160
    {
161 53
        $data = gmp_export($num, 1, GMP_MSW_FIRST | GMP_BIG_ENDIAN);
162 53
        $len = strlen($data);
163 53
        if ($len < $size) {
164 43
            $data = str_repeat("\0", $size - $len) . $data;
165
        }
166 53
        return $data;
167
    }
168
169
    /**
170
     * Generate pre-counter block.
171
     *
172
     * See NIST SP-300-38D section 7.1 step 2 for the details.
173
     *
174
     * @param string $IV    Initialization vector
175
     * @param GHASH  $ghash GHASH functor
176
     *
177
     * @return string
178
     */
179 49
    private function _generateJ0(string $IV, GHASH $ghash): string
180
    {
181
        // if len(IV) = 96
182 49
        if (12 == strlen($IV)) {
183 41
            return $IV . "\0\0\0\1";
184
        }
185 8
        $data = self::_pad128($IV) . self::ZB_64 . self::_uint64(
186 8
            strlen($IV) << 3);
187 8
        return $ghash($data);
188
    }
189
190
    /**
191
     * Apply GCTR algorithm.
192
     *
193
     * See NIST SP-300-38D section 6.5 for the details.
194
     *
195
     * @param string $ICB Initial counter block
196
     * @param string $X   Input data
197
     * @param string $K   Encryption key
198
     *
199
     * @return string Output data
200
     */
201 49
    private function _gctr(string $ICB, string $X, string $K): string
202
    {
203
        // if data is an empty string, return an empty string
204 49
        if ('' == $X) {
205 13
            return '';
206
        }
207
        // number of blocks
208 49
        $n = ceil(strlen($X) / 16);
209 49
        $CB = $ICB;
210 49
        $Y = '';
211 49
        for ($i = 0; $i < $n - 1; ++$i) {
212
            // plaintext block
213 30
            $xi = substr($X, $i << 4, 16);
214
            // encrypt block and append to Y
215 30
            $Y .= $xi ^ $this->_cipher->encrypt($CB, $K);
216
            // increment counter block
217 30
            $CB = self::_inc32($CB);
218
        }
219
        // final block
220 49
        $xn = substr($X, $i << 4);
221
        // XOR against partial block
222 49
        $Y .= $xn ^ substr($this->_cipher->encrypt($CB, $K), 0, strlen($xn));
223 49
        return $Y;
224
    }
225
226
    /**
227
     * Compute authentication tag.
228
     *
229
     * See NIST SP-300-38D section 7.1 steps 5-6 for the details.
230
     *
231
     * @param string $A     Additional authenticated data
232
     * @param string $C     Ciphertext
233
     * @param string $J0    Pre-counter block
234
     * @param string $K     Encryption key
235
     * @param GHASH  $ghash GHASH functor
236
     *
237
     * @return string Authentication tag <code>T</code>
238
     */
239 49
    private function _computeAuthTag(string $A, string $C, string $J0, string $K,
240
        GHASH $ghash): string
241
    {
242 49
        $data = self::_pad128($A) . self::_pad128($C) .
243 49
             self::_uint64(strlen($A) << 3) . self::_uint64(strlen($C) << 3);
244 49
        $S = $ghash($data);
245 49
        return substr($this->_gctr($J0, $S, $K), 0, $this->_tagLength);
246
    }
247
248
    /**
249
     * Pad data to 128 bit block boundary.
250
     *
251
     * @param string $data
252
     *
253
     * @return string
254
     */
255 49
    private static function _pad128(string $data): string
256
    {
257 49
        $padlen = 16 - strlen($data) % 16;
258 49
        if (16 != $padlen) {
259 39
            $data .= str_repeat("\0", $padlen);
260
        }
261 49
        return $data;
262
    }
263
264
    /**
265
     * Increment 32 rightmost bits of the counter block.
266
     *
267
     * See NIST SP-300-38D section 6.2 for the details.
268
     *
269
     * @param string $X
270
     *
271
     * @return string
272
     */
273 49
    private static function _inc32(string $X): string
274
    {
275 49
        $Y = substr($X, 0, -4);
276
        // increment counter
277 49
        $n = self::strToGMP(substr($X, -4)) + 1;
278
        // wrap by using only the 32 rightmost bits
279 49
        $Y .= substr(self::gmpToStr($n, 4), -4);
280 49
        return $Y;
281
    }
282
283
    /**
284
     * Convert integer to 64 bit big endian binary string.
285
     *
286
     * @param int $num
287
     *
288
     * @return string
289
     */
290 49
    private static function _uint64(int $num): string
291
    {
292
        // truncate on 32 bit hosts
293 49
        if (PHP_INT_SIZE < 8) {
294
            return "\0\0\0\0" . pack('N', $num);
295
        }
296 49
        return pack('J', $num);
297
    }
298
}
299