Failed Conditions
Push — v7 ( 348606...04dca0 )
by Florent
02:16
created

RSACrypt::getRSAESOAEP()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 24
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 8.9713
c 0
b 0
f 0
cc 3
eloc 19
nc 3
nop 3
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\RSAKey;
18
19
final class RSACrypt
20
{
21
    public static function encryptWithRSA15(RSAKey $key, string $data)
22
    {
23
        $mLen = mb_strlen($data, '8bit');
24
25
        if ($mLen > $key->getModulusLength() - 11) {
26
            throw new \InvalidArgumentException('Message too long');
27
        }
28
29
        $psLen = $key->getModulusLength() - $mLen - 3;
30
        $ps = '';
31
        while (mb_strlen($ps, '8bit') !== $psLen) {
32
            $temp = random_bytes($psLen - mb_strlen($ps, '8bit'));
33
            $temp = str_replace("\x00", '', $temp);
34
            $ps .= $temp;
35
        }
36
        $type = 2;
37
        $data = chr(0) . chr($type) . $ps . chr(0) . $data;
38
39
        $data = BigInteger::createFromBinaryString($data);
40
        $c = self::getRSAEP($key, $data);
41
        $c = self::convertIntegerToOctetString($c, $key->getModulusLength());
42
43
        return $c;
44
    }
45
46
    public static function decryptWithRSA15(RSAKey $key, $c)
47
    {
48
        if (mb_strlen($c, '8bit') !== $key->getModulusLength()) { // or if k < 11
49
            return false;
50
        }
51
52
        $c = BigInteger::createFromBinaryString($c);
53
        $m = self::getRSADP($key, $c);
54
        $em = self::convertIntegerToOctetString($m, $key->getModulusLength());
55
        if ($em === false) {
56
            return false;
57
        }
58
59
        if (ord($em[0]) != 0 || ord($em[1]) > 2) {
60
            return false;
61
        }
62
63
        $ps = mb_substr($em, 2, mb_strpos($em, chr(0), 2, '8bit') - 2, '8bit');
64
        $m = mb_substr($em, mb_strlen($ps, '8bit') + 3, null, '8bit');
65
66
        if (strlen($ps) < 8) {
67
            return false;
68
        }
69
70
        return $m;
71
    }
72
73
    /**
74
     * Encryption.
75
     *
76
     * @param RSAKey $key
77
     * @param string $plaintext
78
     * @param string $hash_algorithm
79
     *
80
     * @return string
81
     */
82
    public static function encryptWithRSAOAEP(RSAKey $key, string $plaintext, string $hash_algorithm): string
83
    {
84
        /** @var Hash $hash */
85
        $hash = Hash::$hash_algorithm();
86
        $length = $key->getModulusLength() - 2 * $hash->getLength() - 2;
87
        if (0 >= $length) {
88
            throw new \RuntimeException();
89
        }
90
        $plaintext = str_split($plaintext, $length);
91
        $ciphertext = '';
92
        foreach ($plaintext as $m) {
93
            $ciphertext .= self::encryptRSAESOAEP($key, $m, $hash);
94
        }
95
96
        return $ciphertext;
97
    }
98
99
    /**
100
     * Decryption.
101
     *
102
     * @param RSAKey $key
103
     * @param string $ciphertext
104
     * @param string $hash_algorithm
105
     *
106
     * @return string
107
     */
108
    public static function decryptWithRSAOAEP(RSAKey $key, string $ciphertext, string $hash_algorithm): string
109
    {
110
        if (0 >= $key->getModulusLength()) {
111
            throw new \RuntimeException();
112
        }
113
        $hash = Hash::$hash_algorithm();
114
        $ciphertext = str_split($ciphertext, $key->getModulusLength());
115
        $ciphertext[count($ciphertext) - 1] = str_pad($ciphertext[count($ciphertext) - 1], $key->getModulusLength(), chr(0), STR_PAD_LEFT);
116
        $plaintext = '';
117
        foreach ($ciphertext as $c) {
118
            $temp = self::getRSAESOAEP($key, $c, $hash);
119
            $plaintext .= $temp;
120
        }
121
122
        return $plaintext;
123
    }
124
125
    /**
126
     * @param BigInteger $x
127
     * @param int        $xLen
128
     *
129
     * @return string
130
     */
131
    private static function convertIntegerToOctetString(BigInteger $x, int $xLen): string
132
    {
133
        $x = $x->toBytes();
134
        if (strlen($x) > $xLen) {
135
            throw new \RuntimeException('Invalid length.');
136
        }
137
138
        return str_pad($x, $xLen, chr(0), STR_PAD_LEFT);
139
    }
140
141
    /**
142
     * Octet-String-to-Integer primitive.
143
     *
144
     * @param string $x
145
     *
146
     * @return BigInteger
147
     */
148
    private static function convertOctetStringToInteger(string $x): BigInteger
149
    {
150
        return BigInteger::createFromBinaryString($x);
151
    }
152
153
    /**
154
     * Exponentiate with or without Chinese Remainder Theorem.
155
     * Operation with primes 'p' and 'q' is appox. 2x faster.
156
     *
157
     * @param RSAKey     $key
158
     * @param BigInteger $c
159
     *
160
     * @return BigInteger
161
     */
162
    private static function exponentiate(RSAKey $key, BigInteger $c): BigInteger
163
    {
164
        if ($key->isPublic() || empty($key->getPrimes())) {
165
            return $c->modPow($key->getExponent(), $key->getModulus());
166
        }
167
168
        $p = $key->getPrimes()[0];
169
        $q = $key->getPrimes()[1];
170
        $dP = $key->getExponents()[0];
171
        $dQ = $key->getExponents()[1];
172
        $qInv = $key->getCoefficient();
173
174
        $m1 = $c->modPow($dP, $p);
175
        $m2 = $c->modPow($dQ, $q);
176
        $h = $qInv->multiply($m1->subtract($m2)->add($p))->mod($p);
177
        $m = $m2->add($h->multiply($q));
178
179
        return $m;
180
    }
181
182
    /**
183
     * RSA EP.
184
     *
185
     * @param RSAKey     $key
186
     * @param BigInteger $m
187
     *
188
     * @return BigInteger
189
     */
190
    private static function getRSAEP(RSAKey $key, BigInteger $m): BigInteger
191
    {
192
        if ($m->compare(BigInteger::createFromDecimal(0)) < 0 || $m->compare($key->getModulus()) > 0) {
193
            throw new \RuntimeException();
194
        }
195
196
        return self::exponentiate($key, $m);
197
    }
198
199
    /**
200
     * RSA DP.
201
     *
202
     * @param RSAKey     $key
203
     * @param BigInteger $c
204
     *
205
     * @return BigInteger
206
     */
207
    private static function getRSADP(RSAKey $key, BigInteger $c): BigInteger
208
    {
209
        if ($c->compare(BigInteger::createFromDecimal(0)) < 0 || $c->compare($key->getModulus()) > 0) {
210
            throw new \RuntimeException();
211
        }
212
213
        return self::exponentiate($key, $c);
214
    }
215
216
    /**
217
     * MGF1.
218
     *
219
     * @param string $mgfSeed
220
     * @param int    $maskLen
221
     * @param Hash   $mgfHash
222
     *
223
     * @return string
224
     */
225
    private static function getMGF1(string $mgfSeed, int $maskLen, Hash $mgfHash): string
226
    {
227
        $t = '';
228
        $count = ceil($maskLen / $mgfHash->getLength());
229
        for ($i = 0; $i < $count; ++$i) {
230
            $c = pack('N', $i);
231
            $t .= $mgfHash->hash($mgfSeed.$c);
232
        }
233
234
        return mb_substr($t, 0, $maskLen, '8bit');
235
    }
236
237
    /**
238
     * RSAES-OAEP-ENCRYPT.
239
     *
240
     * @param RSAKey $key
241
     * @param string $m
242
     * @param Hash   $hash
243
     *
244
     * @return string
245
     */
246
    private static function encryptRSAESOAEP(RSAKey $key, string $m, Hash $hash): string
247
    {
248
        $mLen = mb_strlen($m, '8bit');
249
        $lHash = $hash->hash('');
250
        $ps = str_repeat(chr(0), $key->getModulusLength() - $mLen - 2 * $hash->getLength() - 2);
251
        $db = $lHash.$ps.chr(1).$m;
252
        $seed = random_bytes($hash->getLength());
253
        $dbMask = self::getMGF1($seed, $key->getModulusLength() - $hash->getLength() - 1, $hash/*MGF*/);
254
        $maskedDB = strval($db ^ $dbMask);
255
        $seedMask = self::getMGF1($maskedDB, $hash->getLength(), $hash/*MGF*/);
256
        $maskedSeed = $seed ^ $seedMask;
257
        $em = chr(0).$maskedSeed.$maskedDB;
258
259
        $m = self::convertOctetStringToInteger($em);
260
        $c = self::getRSAEP($key, $m);
261
        $c = self::convertIntegerToOctetString($c, $key->getModulusLength());
262
263
        return $c;
264
    }
265
266
    /**
267
     * RSAES-OAEP-DECRYPT.
268
     *
269
     * @param RSAKey $key
270
     * @param string $c
271
     * @param Hash   $hash
272
     *
273
     * @return string
274
     */
275
    private static function getRSAESOAEP(RSAKey $key, string $c, Hash $hash): string
276
    {
277
        $c = self::convertOctetStringToInteger($c);
278
        $m = self::getRSADP($key, $c);
279
        $em = self::convertIntegerToOctetString($m, $key->getModulusLength());
280
        $lHash = $hash->hash('');
281
        $maskedSeed = mb_substr($em, 1, $hash->getLength(), '8bit');
282
        $maskedDB = mb_substr($em, $hash->getLength() + 1, null, '8bit');
283
        $seedMask = self::getMGF1($maskedDB, $hash->getLength(), $hash/*MGF*/);
284
        $seed = strval($maskedSeed ^ $seedMask);
285
        $dbMask = self::getMGF1($seed, $key->getModulusLength() - $hash->getLength() - 1, $hash/*MGF*/);
286
        $db = $maskedDB ^ $dbMask;
287
        $lHash2 = mb_substr($db, 0, $hash->getLength(), '8bit');
288
        $m = mb_substr($db, $hash->getLength(), null, '8bit');
289
        if (!hash_equals($lHash, $lHash2)) {
290
            throw new \RuntimeException();
291
        }
292
        $m = ltrim($m, chr(0));
293
        if (1 !== ord($m[0])) {
294
            throw new \RuntimeException();
295
        }
296
297
        return mb_substr($m, 1, null, '8bit');
298
    }
299
}
300