RSA   B
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 314
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 36
lcom 1
cbo 3
dl 0
loc 314
rs 8.8
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A verify() 0 11 3
A convertIntegerToOctetString() 0 9 2
C exponentiate() 0 22 7
A getMGF1() 0 11 2
A encodeEMSAPSS() 0 20 2
B verifyEMSAPSS() 0 33 6
B encodeEMSA15() 0 29 5
A sign() 0 11 3
A signWithPSS() 0 8 1
A signWithPKCS15() 0 8 1
A verifyWithPSS() 0 12 2
A verifyWithPKCS15() 0 11 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\Signature\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 RSA.
22
 */
23
final class RSA
24
{
25
    /**
26
     * Probabilistic Signature Scheme.
27
     */
28
    public const SIGNATURE_PSS = 1;
29
30
    /**
31
     * Use the PKCS#1.
32
     */
33
    public const SIGNATURE_PKCS1 = 2;
34
35
    /**
36
     * @param BigInteger $x
37
     * @param int        $xLen
38
     *
39
     * @return string
40
     */
41
    private static function convertIntegerToOctetString(BigInteger $x, int $xLen): string
42
    {
43
        $x = $x->toBytes();
44
        if (mb_strlen($x, '8bit') > $xLen) {
45
            throw new \RuntimeException();
46
        }
47
48
        return str_pad($x, $xLen, chr(0), STR_PAD_LEFT);
49
    }
50
51
    /**
52
     * Exponentiate with or without Chinese Remainder Theorem.
53
     * Operation with primes 'p' and 'q' is appox. 2x faster.
54
     *
55
     * @param RSAKey     $key
56
     * @param BigInteger $c
57
     *
58
     * @return BigInteger
59
     */
60
    private static function exponentiate(RSAKey $key, BigInteger $c): BigInteger
61
    {
62
        if ($c->compare(BigInteger::createFromDecimal(0)) < 0 || $c->compare($key->getModulus()) > 0) {
63
            throw new \RuntimeException();
64
        }
65
        if ($key->isPublic() || empty($key->getPrimes()) || empty($key->getExponents()) || null === $key->getCoefficient()) {
66
            return $c->modPow($key->getExponent(), $key->getModulus());
67
        }
68
69
        $p = $key->getPrimes()[0];
70
        $q = $key->getPrimes()[1];
71
        $dP = $key->getExponents()[0];
72
        $dQ = $key->getExponents()[1];
73
        $qInv = $key->getCoefficient();
74
75
        $m1 = $c->modPow($dP, $p);
76
        $m2 = $c->modPow($dQ, $q);
77
        $h = $qInv->multiply($m1->subtract($m2)->add($p))->mod($p);
78
        $m = $m2->add($h->multiply($q));
79
80
        return $m;
81
    }
82
83
    /**
84
     * MGF1.
85
     *
86
     * @param string $mgfSeed
87
     * @param int    $maskLen
88
     * @param Hash   $mgfHash
89
     *
90
     * @return string
91
     */
92
    private static function getMGF1(string $mgfSeed, int $maskLen, Hash $mgfHash): string
93
    {
94
        $t = '';
95
        $count = ceil($maskLen / $mgfHash->getLength());
96
        for ($i = 0; $i < $count; ++$i) {
97
            $c = pack('N', $i);
98
            $t .= $mgfHash->hash($mgfSeed.$c);
99
        }
100
101
        return mb_substr($t, 0, $maskLen, '8bit');
102
    }
103
104
    /**
105
     * EMSA-PSS-ENCODE.
106
     *
107
     * @param string $message
108
     * @param int    $modulusLength
109
     * @param Hash   $hash
110
     *
111
     * @return string
112
     */
113
    private static function encodeEMSAPSS(string $message, int $modulusLength, Hash $hash): string
114
    {
115
        $emLen = ($modulusLength + 1) >> 3;
116
        $sLen = $hash->getLength();
117
        $mHash = $hash->hash($message);
118
        if ($emLen <= $hash->getLength() + $sLen + 2) {
119
            throw new \RuntimeException();
120
        }
121
        $salt = random_bytes($sLen);
122
        $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
123
        $h = $hash->hash($m2);
124
        $ps = str_repeat(chr(0), $emLen - $sLen - $hash->getLength() - 2);
125
        $db = $ps.chr(1).$salt;
126
        $dbMask = self::getMGF1($h, $emLen - $hash->getLength() - 1, $hash);
127
        $maskedDB = $db ^ $dbMask;
128
        $maskedDB[0] = ~chr(0xFF << ($modulusLength & 7)) & $maskedDB[0];
129
        $em = $maskedDB.$h.chr(0xBC);
130
131
        return $em;
132
    }
133
134
    /**
135
     * EMSA-PSS-VERIFY.
136
     *
137
     * @param string $m
138
     * @param string $em
139
     * @param int    $emBits
140
     * @param Hash   $hash
141
     *
142
     * @return bool
143
     */
144
    private static function verifyEMSAPSS(string $m, string $em, int $emBits, Hash $hash): bool
145
    {
146
        $emLen = ($emBits + 1) >> 3;
147
        $sLen = $hash->getLength();
148
        $mHash = $hash->hash($m);
149
        if ($emLen < $hash->getLength() + $sLen + 2) {
150
            throw new \InvalidArgumentException();
151
        }
152
        if ($em[mb_strlen($em, '8bit') - 1] !== chr(0xBC)) {
153
            throw new \InvalidArgumentException();
154
        }
155
        $maskedDB = mb_substr($em, 0, -$hash->getLength() - 1, '8bit');
156
        $h = mb_substr($em, -$hash->getLength() - 1, $hash->getLength(), '8bit');
157
        $temp = chr(0xFF << ($emBits & 7));
158
        if ((~$maskedDB[0] & $temp) !== $temp) {
159
            throw new \InvalidArgumentException();
160
        }
161
        $dbMask = self::getMGF1($h, $emLen - $hash->getLength() - 1, $hash/*MGF*/);
162
        $db = $maskedDB ^ $dbMask;
163
        $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0];
164
        $temp = $emLen - $hash->getLength() - $sLen - 2;
165
        if (mb_substr($db, 0, $temp, '8bit') !== str_repeat(chr(0), $temp)) {
166
            throw new \InvalidArgumentException();
167
        }
168
        if (1 !== ord($db[$temp])) {
169
            throw new \InvalidArgumentException();
170
        }
171
        $salt = mb_substr($db, $temp + 1, null, '8bit'); // should be $sLen long
172
        $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
173
        $h2 = $hash->hash($m2);
174
175
        return hash_equals($h, $h2);
176
    }
177
178
    /**
179
     * @param string $m
180
     * @param int    $emBits
181
     * @param Hash   $hash
182
     *
183
     * @return string
184
     */
185
    private static function encodeEMSA15(string $m, int $emBits, Hash $hash): string
186
    {
187
        $h = $hash->hash($m);
188
        switch ($hash->name()) {
189
            case 'sha256':
190
                $t = "\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20";
191
192
                break;
193
            case 'sha384':
194
                $t = "\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30";
195
196
                break;
197
            case 'sha512':
198
                $t = "\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40";
199
200
                break;
201
            default:
202
                throw new \InvalidArgumentException();
203
        }
204
        $t .= $h;
205
        $tLen = mb_strlen($t, '8bit');
206
        if ($emBits < $tLen + 11) {
207
            throw new \RuntimeException();
208
        }
209
        $ps = str_repeat(chr(0xFF), $emBits - $tLen - 3);
210
        $em2 = "\0\1$ps\0$t";
211
212
        return $em2;
213
    }
214
215
    /**
216
     * @param RSAKey $key
217
     * @param string $message
218
     * @param string $hash
219
     * @param int    $mode
220
     *
221
     * @return string
222
     */
223
    public static function sign(RSAKey $key, string $message, string $hash, int $mode): string
224
    {
225
        switch ($mode) {
226
            case self::SIGNATURE_PSS:
227
                return self::signWithPSS($key, $message, $hash);
228
            case self::SIGNATURE_PKCS1:
229
                return self::signWithPKCS15($key, $message, $hash);
230
            default:
231
                throw new \InvalidArgumentException('Unsupported mode.');
232
        }
233
    }
234
235
    /**
236
     * Create a signature.
237
     *
238
     * @param RSAKey $key
239
     * @param string $message
240
     * @param string $hash
241
     *
242
     * @return string
243
     */
244
    public static function signWithPSS(RSAKey $key, string $message, string $hash): string
245
    {
246
        $em = self::encodeEMSAPSS($message, 8 * $key->getModulusLength() - 1, Hash::$hash());
247
        $message = BigInteger::createFromBinaryString($em);
248
        $signature = self::exponentiate($key, $message);
249
250
        return self::convertIntegerToOctetString($signature, $key->getModulusLength());
251
    }
252
253
    /**
254
     * Create a signature.
255
     *
256
     * @param RSAKey $key
257
     * @param string $message
258
     * @param string $hash
259
     *
260
     * @return string
261
     */
262
    public static function signWithPKCS15(RSAKey $key, string $message, string $hash): string
263
    {
264
        $em = self::encodeEMSA15($message, $key->getModulusLength(), Hash::$hash());
265
        $message = BigInteger::createFromBinaryString($em);
266
        $signature = self::exponentiate($key, $message);
267
268
        return self::convertIntegerToOctetString($signature, $key->getModulusLength());
269
    }
270
271
    /**
272
     * @param RSAKey $key
273
     * @param string $message
274
     * @param string $signature
275
     * @param string $hash
276
     * @param int    $mode
277
     *
278
     * @return bool
279
     */
280
    public static function verify(RSAKey $key, string $message, string $signature, string $hash, int $mode): bool
281
    {
282
        switch ($mode) {
283
            case self::SIGNATURE_PSS:
284
                return self::verifyWithPSS($key, $message, $signature, $hash);
285
            case self::SIGNATURE_PKCS1:
286
                return self::verifyWithPKCS15($key, $message, $signature, $hash);
287
            default:
288
                throw new \InvalidArgumentException('Unsupported mode.');
289
        }
290
    }
291
292
    /**
293
     * Verifies a signature.
294
     *
295
     * @param RSAKey $key
296
     * @param string $message
297
     * @param string $signature
298
     * @param string $hash
299
     *
300
     * @return bool
301
     */
302
    public static function verifyWithPSS(RSAKey $key, string $message, string $signature, string $hash): bool
303
    {
304
        if (mb_strlen($signature, '8bit') !== $key->getModulusLength()) {
305
            throw new \InvalidArgumentException();
306
        }
307
        $s2 = BigInteger::createFromBinaryString($signature);
308
        $m2 = self::exponentiate($key, $s2);
309
        $em = self::convertIntegerToOctetString($m2, $key->getModulusLength());
310
        $modBits = 8 * $key->getModulusLength();
311
312
        return self::verifyEMSAPSS($message, $em, $modBits - 1, Hash::$hash());
313
    }
314
315
    /**
316
     * Verifies a signature.
317
     *
318
     * @param RSAKey $key
319
     * @param string $message
320
     * @param string $signature
321
     * @param string $hash
322
     *
323
     * @return bool
324
     */
325
    public static function verifyWithPKCS15(RSAKey $key, string $message, string $signature, string $hash): bool
326
    {
327
        if (mb_strlen($signature, '8bit') !== $key->getModulusLength()) {
328
            throw new \InvalidArgumentException();
329
        }
330
        $signature = BigInteger::createFromBinaryString($signature);
331
        $m2 = self::exponentiate($key, $signature);
332
        $em = self::convertIntegerToOctetString($m2, $key->getModulusLength());
333
334
        return hash_equals($em, self::encodeEMSA15($message, $key->getModulusLength(), Hash::$hash()));
335
    }
336
}
337