Failed Conditions
Push — master ( e3206a...8d8ce8 )
by Florent
02:45
created

src/Util/RSA.php (5 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/*
4
 * The MIT License (MIT)
5
 *
6
 * Copyright (c) 2014-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 Jose\Util;
13
14
use Assert\Assertion;
15
use Jose\KeyConverter\RSAKey;
16
17
final class RSA
18
{
19
    /**
20
     * Integer-to-Octet-String primitive.
21
     *
22
     * @param \Jose\Util\BigInteger $x
23
     * @param int                   $xLen
24
     *
25
     * @return string
26
     */
27
    private static function convertIntegerToOctetString($x, $xLen)
28
    {
29
        $x = $x->toBytes();
30
        if (strlen($x) > $xLen) {
31
            return false;
32
        }
33
34
        return str_pad($x, $xLen, chr(0), STR_PAD_LEFT);
35
    }
36
37
    /**
38
     * Octet-String-to-Integer primitive.
39
     *
40
     * @param string $x
41
     *
42
     * @return \Jose\Util\BigInteger
43
     */
44
    private static function convertOctetStringToInteger($x)
45
    {
46
        return BigInteger::createFromBinaryString($x);
47
    }
48
49
    /**
50
     * Exponentiate with or without Chinese Remainder Theorem.
51
     * Operation with primes 'p' and 'q' is appox. 2x faster.
52
     *
53
     * @param \Jose\KeyConverter\RSAKey $key
54
     * @param \Jose\Util\BigInteger     $c
55
     *
56
     * @return \Jose\Util\BigInteger
57
     */
58
    private static function _exponentiate(RSAKey $key, $c)
59
    {
60
        if ($key->isPublic() || empty($key->getPrimes())) {
61
            return $c->modPow($key->getExponent(), $key->getModulus());
0 ignored issues
show
Bug Compatibility introduced by
The expression $c->modPow($key->getExpo...), $key->getModulus()); of type Jose\Util\BigInteger|boolean adds the type boolean to the return on line 61 which is incompatible with the return type documented by Jose\Util\RSA::_exponentiate of type Jose\Util\BigInteger.
Loading history...
62
        }
63
64
        $p = $key->getPrimes()[0];
65
        $q = $key->getPrimes()[1];
66
        $dP = $key->getExponents()[0];
67
        $dQ = $key->getExponents()[1];
68
        $qInv = $key->getCoefficient();
69
70
        $m1 = $c->modPow($dP, $p);
71
        $m2 = $c->modPow($dQ, $q);
72
        $h = $qInv->multiply($m1->subtract($m2))->mod($p);
73
        $m = $m2->add($h->multiply($q));
74
75
        return $m;
76
    }
77
78
    /**
79
     * RSAEP.
80
     *
81
     * @param \Jose\KeyConverter\RSAKey $key
82
     * @param \Jose\Util\BigInteger     $m
83
     *
84
     * @return \Jose\Util\BigInteger|false
85
     */
86
    private static function _rsaep(RSAKey $key, BigInteger $m)
87
    {
88
        if ($m->compare(BigInteger::createFromDecimalString('0')) < 0 || $m->compare($key->getModulus()) > 0) {
89
            return false;
90
        }
91
92
        return self::_exponentiate($key, $m);
93
    }
94
95
    /**
96
     * RSADP.
97
     *
98
     * @param \Jose\KeyConverter\RSAKey $key
99
     * @param \Jose\Util\BigInteger     $c
100
     *
101
     * @return \Jose\Util\BigInteger|false
102
     */
103
    private static function _rsadp(RSAKey $key, BigInteger $c)
104
    {
105
        if ($c->compare(BigInteger::createFromDecimalString('0')) < 0 || $c->compare($key->getModulus()) > 0) {
106
            return false;
107
        }
108
109
        return self::_exponentiate($key, $c);
110
    }
111
112
    /**
113
     * RSASP1.
114
     *
115
     * @param \Jose\KeyConverter\RSAKey $key
116
     * @param \Jose\Util\BigInteger     $m
117
     *
118
     * @return \Jose\Util\BigInteger|false
119
     */
120
    private static function _rsasp1(RSAKey $key, BigInteger $m)
121
    {
122
        if ($m->compare(BigInteger::createFromDecimalString('0')) < 0 || $m->compare($key->getModulus()) > 0) {
123
            return false;
124
        }
125
126
        return self::_exponentiate($key, $m);
127
    }
128
129
    /**
130
     * RSAVP1.
131
     *
132
     * @param \Jose\KeyConverter\RSAKey $key
133
     * @param \Jose\Util\BigInteger     $s
134
     *
135
     * @return \Jose\Util\BigInteger|false
136
     */
137
    private static function _rsavp1(RSAKey $key, BigInteger $s)
138
    {
139
        if ($s->compare(BigInteger::createFromDecimalString('0')) < 0 || $s->compare($key->getModulus()) > 0) {
140
            return false;
141
        }
142
143
        return self::_exponentiate($key, $s);
144
    }
145
146
    /**
147
     * MGF1.
148
     *
149
     * @param string          $mgfSeed
150
     * @param int             $maskLen
151
     * @param \Jose\Util\Hash $mgfHash
152
     *
153
     * @return string
154
     */
155
    private static function _mgf1($mgfSeed, $maskLen, Hash $mgfHash)
156
    {
157
        $t = '';
158
        $count = ceil($maskLen / $mgfHash->getLength());
159
        for ($i = 0; $i < $count; $i++) {
160
            $c = pack('N', $i);
161
            $t .= $mgfHash->hash($mgfSeed.$c);
162
        }
163
164
        return substr($t, 0, $maskLen);
165
    }
166
167
    /**
168
     * RSAES-OAEP-ENCRYPT.
169
     *
170
     * @param \Jose\KeyConverter\RSAKey $key
171
     * @param string                    $m
172
     * @param \Jose\Util\Hash           $hash
173
     *
174
     * @return string
175
     */
176
    private static function _rsaes_oaep_encrypt(RSAKey $key, $m, Hash $hash)
177
    {
178
        $mLen = strlen($m);
179
        $lHash = $hash->hash('');
180
        $ps = str_repeat(chr(0), $key->getModulusLength() - $mLen - 2 * $hash->getLength() - 2);
181
        $db = $lHash.$ps.chr(1).$m;
182
        $seed = random_bytes($hash->getLength());
183
        $dbMask = self::_mgf1($seed, $key->getModulusLength() - $hash->getLength() - 1, $hash/*MGF*/);
184
        $maskedDB = $db ^ $dbMask;
185
        $seedMask = self::_mgf1($maskedDB, $hash->getLength(), $hash/*MGF*/);
186
        $maskedSeed = $seed ^ $seedMask;
187
        $em = chr(0).$maskedSeed.$maskedDB;
188
189
        $m = self::convertOctetStringToInteger($em);
190
        $c = self::_rsaep($key, $m);
191
        $c = self::convertIntegerToOctetString($c, $key->getModulusLength());
192
193
        return $c;
194
    }
195
196
    /**
197
     * RSAES-OAEP-DECRYPT.
198
     *
199
     * @param \Jose\KeyConverter\RSAKey $key
200
     * @param string                    $c
201
     * @param \Jose\Util\Hash           $hash
202
     *
203
     * @return string
204
     */
205
    private static function _rsaes_oaep_decrypt(RSAKey $key, $c, Hash $hash)
206
    {
207
        $c = self::convertOctetStringToInteger($c);
208
        $m = self::_rsadp($key, $c);
209
210
        Assertion::isInstanceOf($m, BigInteger::class);
211
212
        $em = self::convertIntegerToOctetString($m, $key->getModulusLength());
213
214
        $lHash = $hash->hash('');
215
        $maskedSeed = substr($em, 1, $hash->getLength());
216
        $maskedDB = substr($em, $hash->getLength() + 1);
217
        $seedMask = self::_mgf1($maskedDB, $hash->getLength(), $hash/*MGF*/);
218
        $seed = $maskedSeed ^ $seedMask;
219
        $dbMask = self::_mgf1($seed, $key->getModulusLength() - $hash->getLength() - 1, $hash/*MGF*/);
220
        $db = $maskedDB ^ $dbMask;
221
        $lHash2 = substr($db, 0, $hash->getLength());
222
        $m = substr($db, $hash->getLength());
223
224
        Assertion::eq($lHash, $lHash2);
225
226
        $m = ltrim($m, chr(0));
227
228
        Assertion::eq(ord($m[0]), 1);
229
230
        return substr($m, 1);
231
    }
232
233
    /**
234
     * EMSA-PSS-ENCODE.
235
     *
236
     * @param string          $m
237
     * @param int             $emBits
238
     * @param \Jose\Util\Hash $hash
239
     *
240
     * @return string|bool
241
     */
242
    private static function _emsa_pss_encode($m, $emBits, Hash $hash)
243
    {
244
        // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
245
        // be output.
246
247
        $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8)
248
        $sLen = $hash->getLength();
249
250
        $mHash = $hash->hash($m);
251
        if ($emLen < $hash->getLength() + $sLen + 2) {
252
            return false;
253
        }
254
255
        $salt = random_bytes($sLen);
256
        $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
257
        $h = $hash->hash($m2);
258
        $ps = str_repeat(chr(0), $emLen - $sLen - $hash->getLength() - 2);
259
        $db = $ps.chr(1).$salt;
260
        $dbMask = self::_mgf1($h, $emLen - $hash->getLength() - 1, $hash/*MGF*/);
261
        $maskedDB = $db ^ $dbMask;
262
        $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0];
263
        $em = $maskedDB.$h.chr(0xBC);
264
265
        return $em;
266
    }
267
268
    /**
269
     * EMSA-PSS-VERIFY.
270
     *
271
     * @param string                    $m
272
     * @param string                    $em
273
     * @param int                       $emBits
274
     * @param \Jose\Util\Hash           $hash
275
     *
276
     * @return string
277
     */
278
    private static function _emsa_pss_verify($m, $em, $emBits, Hash $hash)
279
    {
280
        // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
281
        // be output.
282
283
        $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8);
284
        $sLen = $hash->getLength();
285
286
        $mHash = $hash->hash($m);
287
        if ($emLen < $hash->getLength() + $sLen + 2) {
288
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Jose\Util\RSA::_emsa_pss_verify of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
289
        }
290
291
        if ($em[strlen($em) - 1] != chr(0xBC)) {
292
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Jose\Util\RSA::_emsa_pss_verify of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
293
        }
294
295
        $maskedDB = substr($em, 0, -$hash->getLength() - 1);
296
        $h = substr($em, -$hash->getLength() - 1, $hash->getLength());
297
        $temp = chr(0xFF << ($emBits & 7));
298
        if ((~$maskedDB[0] & $temp) != $temp) {
299
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Jose\Util\RSA::_emsa_pss_verify of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
300
        }
301
        $dbMask = self::_mgf1($h, $emLen - $hash->getLength() - 1, $hash/*MGF*/);
302
        $db = $maskedDB ^ $dbMask;
303
        $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0];
304
        $temp = $emLen - $hash->getLength() - $sLen - 2;
305
        if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) {
306
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Jose\Util\RSA::_emsa_pss_verify of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
307
        }
308
        $salt = substr($db, $temp + 1); // should be $sLen long
309
        $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
310
        $h2 = $hash->hash($m2);
311
312
        return hash_equals($h, $h2);
313
    }
314
315
    /**
316
     * Encryption.
317
     *
318
     * @param \Jose\KeyConverter\RSAKey $key
319
     * @param string                    $plaintext
320
     * @param string                    $hash_algorithm
321
     *
322
     * @return string
323
     */
324
    public static function encrypt(RSAKey $key, $plaintext, $hash_algorithm)
325
    {
326
        $hash = Hash::$hash_algorithm();
327
        $length = $key->getModulusLength() - 2 * $hash->getLength() - 2;
328
329
        Assertion::greaterThan($length, 0);
330
331
        $plaintext = str_split($plaintext, $length);
332
        $ciphertext = '';
333
        foreach ($plaintext as $m) {
334
            $ciphertext .= self::_rsaes_oaep_encrypt($key, $m, $hash);
335
        }
336
337
        return $ciphertext;
338
    }
339
340
    /**
341
     * Decryption.
342
     *
343
     * @param \Jose\KeyConverter\RSAKey $key
344
     * @param string                    $ciphertext
345
     * @param string                    $hash_algorithm
346
     *
347
     * @return string
348
     */
349
    public static function decrypt(RSAKey $key, $ciphertext, $hash_algorithm)
350
    {
351
        Assertion::greaterThan($key->getModulusLength(), 0);
352
353
        $hash = Hash::$hash_algorithm();
354
355
        $ciphertext = str_split($ciphertext, $key->getModulusLength());
356
        $ciphertext[count($ciphertext) - 1] = str_pad($ciphertext[count($ciphertext) - 1], $key->getModulusLength(), chr(0), STR_PAD_LEFT);
357
358
        $plaintext = '';
359
360
        foreach ($ciphertext as $c) {
361
            $temp = self::_rsaes_oaep_decrypt($key, $c, $hash);
362
            if ($temp === false) {
363
                return false;
364
            }
365
            $plaintext .= $temp;
366
        }
367
368
        return $plaintext;
369
    }
370
371
    /**
372
     * Create a signature.
373
     *
374
     * @param \Jose\KeyConverter\RSAKey $key
375
     * @param string                    $message
376
     * @param string                    $hash
377
     *
378
     * @return string
379
     */
380
    public static function sign(RSAKey $key, $message, $hash)
381
    {
382
        Assertion::string($message);
383
        Assertion::string($hash);
384
        Assertion::inArray($hash, ['sha256', 'sha384', 'sha512']);
385
386
        $em = self::_emsa_pss_encode($message, 8 * $key->getModulusLength() - 1, Hash::$hash());
387
388
        Assertion::string($em);
389
390
        $message = self::convertOctetStringToInteger($em);
391
        $signature = self::_rsasp1($key, $message);
392
393
        Assertion::isInstanceOf($signature, BigInteger::class);
394
395
        $signature = self::convertIntegerToOctetString($signature, $key->getModulusLength());
396
397
        return $signature;
398
    }
399
400
    /**
401
     * Verifies a signature.
402
     *
403
     * @param \Jose\KeyConverter\RSAKey $key
404
     * @param string                    $message
405
     * @param string                    $signature
406
     * @param string                    $hash
407
     *
408
     * @return bool
409
     */
410
    public static function verify(RSAKey $key, $message, $signature, $hash)
411
    {
412
        Assertion::string($message);
413
        Assertion::string($signature);
414
        Assertion::string($hash);
415
        Assertion::inArray($hash, ['sha256', 'sha384', 'sha512']);
416
        Assertion::eq(strlen($signature), $key->getModulusLength());
417
418
        $modBits = 8 * $key->getModulusLength();
419
420
        $s2 = self::convertOctetStringToInteger($signature);
421
        $m2 = self::_rsavp1($key, $s2);
422
423
        Assertion::isInstanceOf($m2, BigInteger::class);
424
425
        $em = self::convertIntegerToOctetString($m2, $modBits >> 3);
426
427
        return self::_emsa_pss_verify($message, $em, $modBits - 1, Hash::$hash());
428
    }
429
}
430