Failed Conditions
Push — master ( d39f59...052283 )
by Florent
01:54
created

RSACrypt::exponentiate()   B

Complexity

Conditions 5
Paths 2

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 13
nc 2
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2017 Spomky-Labs
9
 *
10
 * This software may be modified and distributed under the terms
11
 * of the MIT license.  See the LICENSE file for details.
12
 */
13
14
namespace Jose\Component\Encryption\Util;
15
16
use Jose\Component\Core\Util\BigInteger;
17
use Jose\Component\Core\Util\Hash;
18
use Jose\Component\Core\Util\RSAKey;
19
20
/**
21
 * Class RSACrypt.
22
 */
23
final class RSACrypt
24
{
25
    /**
26
     * Optimal Asymmetric Encryption Padding (OAEP).
27
     */
28
    public const ENCRYPTION_OAEP = 1;
29
30
    /**
31
     * Use PKCS#1 padding.
32
     */
33
    public const ENCRYPTION_PKCS1 = 2;
34
35
    /**
36
     * @param RSAKey      $key
37
     * @param string      $data
38
     * @param int         $mode
39
     * @param null|string $hash
40
     *
41
     * @return string
42
     */
43
    public static function encrypt(RSAKey $key, string $data, int $mode, ?string $hash = null): string
44
    {
45
        switch ($mode) {
46
            case self::ENCRYPTION_OAEP:
47
                return self::encryptWithRSAOAEP($key, $data, $hash);
48
            case self::ENCRYPTION_PKCS1:
49
                return self::encryptWithRSA15($key, $data);
50
            default:
51
                throw new \InvalidArgumentException('Unsupported mode.');
52
        }
53
    }
54
55
    /**
56
     * @param RSAKey      $key
57
     * @param string      $plaintext
58
     * @param int         $mode
59
     * @param null|string $hash
60
     *
61
     * @return string
62
     */
63
    public static function decrypt(RSAKey $key, string $plaintext, int $mode, ?string $hash = null): string
64
    {
65
        switch ($mode) {
66
            case self::ENCRYPTION_OAEP:
67
                return self::decryptWithRSAOAEP($key, $plaintext, $hash);
68
            case self::ENCRYPTION_PKCS1:
69
                return self::decryptWithRSA15($key, $plaintext);
70
            default:
71
                throw new \InvalidArgumentException('Unsupported mode.');
72
        }
73
    }
74
75
    /**
76
     * @param RSAKey $key
77
     * @param string $data
78
     *
79
     * @return string
80
     */
81
    public static function encryptWithRSA15(RSAKey $key, string $data): string
82
    {
83
        $mLen = mb_strlen($data, '8bit');
84
85
        if ($mLen > $key->getModulusLength() - 11) {
86
            throw new \InvalidArgumentException('Message too long');
87
        }
88
89
        $psLen = $key->getModulusLength() - $mLen - 3;
90
        $ps = '';
91
        while (mb_strlen($ps, '8bit') !== $psLen) {
92
            $temp = random_bytes($psLen - mb_strlen($ps, '8bit'));
93
            $temp = str_replace("\x00", '', $temp);
94
            $ps .= $temp;
95
        }
96
        $type = 2;
97
        $data = chr(0).chr($type).$ps.chr(0).$data;
98
99
        $data = BigInteger::createFromBinaryString($data);
100
        $c = self::getRSAEP($key, $data);
101
        $c = self::convertIntegerToOctetString($c, $key->getModulusLength());
102
103
        return $c;
104
    }
105
106
    /**
107
     * @param RSAKey $key
108
     * @param string $c
109
     *
110
     * @return string
111
     */
112
    public static function decryptWithRSA15(RSAKey $key, string $c): string
113
    {
114
        if (mb_strlen($c, '8bit') !== $key->getModulusLength()) {
115
            throw new \InvalidArgumentException('Unable to decrypt');
116
        }
117
118
        $c = BigInteger::createFromBinaryString($c);
119
        $m = self::getRSADP($key, $c);
120
        $em = self::convertIntegerToOctetString($m, $key->getModulusLength());
121
        if (false === $em) {
122
            throw new \InvalidArgumentException('Unable to decrypt');
123
        }
124
125
        if (0 != ord($em[0]) || ord($em[1]) > 2) {
126
            throw new \InvalidArgumentException('Unable to decrypt');
127
        }
128
129
        $ps = mb_substr($em, 2, mb_strpos($em, chr(0), 2, '8bit') - 2, '8bit');
130
        $m = mb_substr($em, mb_strlen($ps, '8bit') + 3, null, '8bit');
131
132
        if (mb_strlen($ps, '8bit') < 8) {
133
            throw new \InvalidArgumentException('Unable to decrypt');
134
        }
135
136
        return $m;
137
    }
138
139
    /**
140
     * Encryption.
141
     *
142
     * @param RSAKey $key
143
     * @param string $plaintext
144
     * @param string $hash_algorithm
145
     *
146
     * @return string
147
     */
148
    public static function encryptWithRSAOAEP(RSAKey $key, string $plaintext, string $hash_algorithm): string
149
    {
150
        /** @var Hash $hash */
151
        $hash = Hash::$hash_algorithm();
152
        $length = $key->getModulusLength() - 2 * $hash->getLength() - 2;
153
        if (0 >= $length) {
154
            throw new \RuntimeException();
155
        }
156
        $plaintext = str_split($plaintext, $length);
157
        $ciphertext = '';
158
        foreach ($plaintext as $m) {
159
            $ciphertext .= self::encryptRSAESOAEP($key, $m, $hash);
160
        }
161
162
        return $ciphertext;
163
    }
164
165
    /**
166
     * Decryption.
167
     *
168
     * @param RSAKey $key
169
     * @param string $ciphertext
170
     * @param string $hash_algorithm
171
     *
172
     * @return string
173
     */
174
    public static function decryptWithRSAOAEP(RSAKey $key, string $ciphertext, string $hash_algorithm): string
175
    {
176
        if (0 >= $key->getModulusLength()) {
177
            throw new \RuntimeException();
178
        }
179
        $hash = Hash::$hash_algorithm();
180
        $ciphertext = str_split($ciphertext, $key->getModulusLength());
181
        $ciphertext[count($ciphertext) - 1] = str_pad($ciphertext[count($ciphertext) - 1], $key->getModulusLength(), chr(0), STR_PAD_LEFT);
182
        $plaintext = '';
183
        foreach ($ciphertext as $c) {
184
            $temp = self::getRSAESOAEP($key, $c, $hash);
185
            $plaintext .= $temp;
186
        }
187
188
        return $plaintext;
189
    }
190
191
    /**
192
     * @param BigInteger $x
193
     * @param int        $xLen
194
     *
195
     * @return string
196
     */
197
    private static function convertIntegerToOctetString(BigInteger $x, int $xLen): string
198
    {
199
        $x = $x->toBytes();
200
        if (mb_strlen($x, '8bit') > $xLen) {
201
            throw new \RuntimeException('Invalid length.');
202
        }
203
204
        return str_pad($x, $xLen, chr(0), STR_PAD_LEFT);
205
    }
206
207
    /**
208
     * Octet-String-to-Integer primitive.
209
     *
210
     * @param string $x
211
     *
212
     * @return BigInteger
213
     */
214
    private static function convertOctetStringToInteger(string $x): BigInteger
215
    {
216
        return BigInteger::createFromBinaryString($x);
217
    }
218
219
    /**
220
     * RSA EP.
221
     *
222
     * @param RSAKey     $key
223
     * @param BigInteger $m
224
     *
225
     * @return BigInteger
226
     */
227
    private static function getRSAEP(RSAKey $key, BigInteger $m): BigInteger
228
    {
229
        if ($m->compare(BigInteger::createFromDecimal(0)) < 0 || $m->compare($key->getModulus()) > 0) {
0 ignored issues
show
Documentation introduced by
$key->getModulus() is of type object<Jose\Component\Core\Util\BigInteger>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
230
            throw new \RuntimeException();
231
        }
232
233
        return RSAKey::exponentiate($key, $m);
234
    }
235
236
    /**
237
     * RSA DP.
238
     *
239
     * @param RSAKey     $key
240
     * @param BigInteger $c
241
     *
242
     * @return BigInteger
243
     */
244
    private static function getRSADP(RSAKey $key, BigInteger $c): BigInteger
245
    {
246
        if ($c->compare(BigInteger::createFromDecimal(0)) < 0 || $c->compare($key->getModulus()) > 0) {
0 ignored issues
show
Documentation introduced by
$key->getModulus() is of type object<Jose\Component\Core\Util\BigInteger>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
247
            throw new \RuntimeException();
248
        }
249
250
        return RSAKey::exponentiate($key, $c);
251
    }
252
253
    /**
254
     * MGF1.
255
     *
256
     * @param string $mgfSeed
257
     * @param int    $maskLen
258
     * @param Hash   $mgfHash
259
     *
260
     * @return string
261
     */
262
    private static function getMGF1(string $mgfSeed, int $maskLen, Hash $mgfHash): string
263
    {
264
        $t = '';
265
        $count = ceil($maskLen / $mgfHash->getLength());
266
        for ($i = 0; $i < $count; ++$i) {
267
            $c = pack('N', $i);
268
            $t .= $mgfHash->hash($mgfSeed.$c);
269
        }
270
271
        return mb_substr($t, 0, $maskLen, '8bit');
272
    }
273
274
    /**
275
     * RSAES-OAEP-ENCRYPT.
276
     *
277
     * @param RSAKey $key
278
     * @param string $m
279
     * @param Hash   $hash
280
     *
281
     * @return string
282
     */
283
    private static function encryptRSAESOAEP(RSAKey $key, string $m, Hash $hash): string
284
    {
285
        $mLen = mb_strlen($m, '8bit');
286
        $lHash = $hash->hash('');
287
        $ps = str_repeat(chr(0), $key->getModulusLength() - $mLen - 2 * $hash->getLength() - 2);
288
        $db = $lHash.$ps.chr(1).$m;
289
        $seed = random_bytes($hash->getLength());
290
        $dbMask = self::getMGF1($seed, $key->getModulusLength() - $hash->getLength() - 1, $hash/*MGF*/);
291
        $maskedDB = strval($db ^ $dbMask);
292
        $seedMask = self::getMGF1($maskedDB, $hash->getLength(), $hash/*MGF*/);
293
        $maskedSeed = $seed ^ $seedMask;
294
        $em = chr(0).$maskedSeed.$maskedDB;
295
296
        $m = self::convertOctetStringToInteger($em);
297
        $c = self::getRSAEP($key, $m);
298
        $c = self::convertIntegerToOctetString($c, $key->getModulusLength());
299
300
        return $c;
301
    }
302
303
    /**
304
     * RSAES-OAEP-DECRYPT.
305
     *
306
     * @param RSAKey $key
307
     * @param string $c
308
     * @param Hash   $hash
309
     *
310
     * @return string
311
     */
312
    private static function getRSAESOAEP(RSAKey $key, string $c, Hash $hash): string
313
    {
314
        $c = self::convertOctetStringToInteger($c);
315
        $m = self::getRSADP($key, $c);
316
        $em = self::convertIntegerToOctetString($m, $key->getModulusLength());
317
        $lHash = $hash->hash('');
318
        $maskedSeed = mb_substr($em, 1, $hash->getLength(), '8bit');
319
        $maskedDB = mb_substr($em, $hash->getLength() + 1, null, '8bit');
320
        $seedMask = self::getMGF1($maskedDB, $hash->getLength(), $hash/*MGF*/);
321
        $seed = strval($maskedSeed ^ $seedMask);
322
        $dbMask = self::getMGF1($seed, $key->getModulusLength() - $hash->getLength() - 1, $hash/*MGF*/);
323
        $db = $maskedDB ^ $dbMask;
324
        $lHash2 = mb_substr($db, 0, $hash->getLength(), '8bit');
325
        $m = mb_substr($db, $hash->getLength(), null, '8bit');
326
        if (!hash_equals($lHash, $lHash2)) {
327
            throw new \RuntimeException();
328
        }
329
        $m = ltrim($m, chr(0));
330
        if (1 !== ord($m[0])) {
331
            throw new \RuntimeException();
332
        }
333
334
        return mb_substr($m, 1, null, '8bit');
335
    }
336
}
337