Failed Conditions
Push — v7 ( 61ffea...f7e5f1 )
by Florent
04:20
created

RSA::convertIntegerToOctetString()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 2
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
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\KeyManagement\KeyConverter\RSAKey;
18
19
final class RSA
20
{
21
    /**
22
     * @param BigInteger $x
23
     * @param int        $xLen
24
     *
25
     * @return string
26
     */
27
    private static function convertIntegerToOctetString(BigInteger $x, int $xLen): string
28
    {
29
        $x = $x->toBytes();
30
        if (strlen($x) > $xLen) {
31
            throw new \RuntimeException('Invalid length.');
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 BigInteger
43
     */
44
    private static function convertOctetStringToInteger(string $x): BigInteger
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 RSAKey     $key
54
     * @param BigInteger $c
55
     *
56
     * @return BigInteger
57
     */
58
    private static function exponentiate(RSAKey $key, BigInteger $c): BigInteger
59
    {
60
        if ($key->isPublic() || empty($key->getPrimes())) {
61
            return $c->modPow($key->getExponent(), $key->getModulus());
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)->add($p))->mod($p);
73
        $m = $m2->add($h->multiply($q));
74
75
        return $m;
76
    }
77
78
    /**
79
     * RSA EP.
80
     *
81
     * @param RSAKey     $key
82
     * @param BigInteger $m
83
     *
84
     * @return BigInteger
85
     */
86
    private static function getRSAEP(RSAKey $key, BigInteger $m): BigInteger
87
    {
88
        if ($m->compare(BigInteger::createFromDecimal(0)) < 0 || $m->compare($key->getModulus()) > 0) {
89
            throw new \RuntimeException();
90
        }
91
92
        return self::exponentiate($key, $m);
93
    }
94
95
    /**
96
     * RSA DP.
97
     *
98
     * @param RSAKey     $key
99
     * @param BigInteger $c
100
     *
101
     * @return BigInteger
102
     */
103
    private static function getRSADP(RSAKey $key, BigInteger $c): BigInteger
104
    {
105
        if ($c->compare(BigInteger::createFromDecimal(0)) < 0 || $c->compare($key->getModulus()) > 0) {
106
            throw new \RuntimeException();
107
        }
108
109
        return self::exponentiate($key, $c);
110
    }
111
112
    /**
113
     * MGF1.
114
     *
115
     * @param string $mgfSeed
116
     * @param int    $maskLen
117
     * @param Hash   $mgfHash
118
     *
119
     * @return string
120
     */
121
    private static function getMGF1(string $mgfSeed, int $maskLen, Hash $mgfHash): string
122
    {
123
        $t = '';
124
        $count = ceil($maskLen / $mgfHash->getLength());
125
        for ($i = 0; $i < $count; ++$i) {
126
            $c = pack('N', $i);
127
            $t .= $mgfHash->hash($mgfSeed.$c);
128
        }
129
130
        return mb_substr($t, 0, $maskLen, '8bit');
131
    }
132
133
    /**
134
     * RSAES-OAEP-ENCRYPT.
135
     *
136
     * @param RSAKey $key
137
     * @param string $m
138
     * @param Hash   $hash
139
     *
140
     * @return string
141
     */
142
    private static function encryptRSAESOAEP(RSAKey $key, string $m, Hash $hash): string
143
    {
144
        $mLen = mb_strlen($m, '8bit');
145
        $lHash = $hash->hash('');
146
        $ps = str_repeat(chr(0), $key->getModulusLength() - $mLen - 2 * $hash->getLength() - 2);
147
        $db = $lHash.$ps.chr(1).$m;
148
        $seed = random_bytes($hash->getLength());
149
        $dbMask = self::getMGF1($seed, $key->getModulusLength() - $hash->getLength() - 1, $hash/*MGF*/);
150
        $maskedDB = strval($db ^ $dbMask);
151
        $seedMask = self::getMGF1($maskedDB, $hash->getLength(), $hash/*MGF*/);
152
        $maskedSeed = $seed ^ $seedMask;
153
        $em = chr(0).$maskedSeed.$maskedDB;
154
155
        $m = self::convertOctetStringToInteger($em);
156
        $c = self::getRSAEP($key, $m);
157
        $c = self::convertIntegerToOctetString($c, $key->getModulusLength());
158
159
        return $c;
160
    }
161
162
    /**
163
     * RSAES-OAEP-DECRYPT.
164
     *
165
     * @param RSAKey $key
166
     * @param string $c
167
     * @param Hash   $hash
168
     *
169
     * @return string
170
     */
171
    private static function getRSAESOAEP(RSAKey $key, string $c, Hash $hash): string
172
    {
173
        $c = self::convertOctetStringToInteger($c);
174
        $m = self::getRSADP($key, $c);
175
        $em = self::convertIntegerToOctetString($m, $key->getModulusLength());
176
        $lHash = $hash->hash('');
177
        $maskedSeed = mb_substr($em, 1, $hash->getLength(), '8bit');
178
        $maskedDB = mb_substr($em, $hash->getLength() + 1, null, '8bit');
179
        $seedMask = self::getMGF1($maskedDB, $hash->getLength(), $hash/*MGF*/);
180
        $seed = strval($maskedSeed ^ $seedMask);
181
        $dbMask = self::getMGF1($seed, $key->getModulusLength() - $hash->getLength() - 1, $hash/*MGF*/);
182
        $db = $maskedDB ^ $dbMask;
183
        $lHash2 = mb_substr($db, 0, $hash->getLength(), '8bit');
184
        $m = mb_substr($db, $hash->getLength(), null, '8bit');
185
        if (!hash_equals($lHash, $lHash2)) {
186
            throw new \RuntimeException();
187
        }
188
        $m = ltrim($m, chr(0));
189
        if (1 !== ord($m[0])) {
190
            throw new \RuntimeException();
191
        }
192
193
        return mb_substr($m, 1, null, '8bit');
194
    }
195
196
    /**
197
     * Encryption.
198
     *
199
     * @param RSAKey $key
200
     * @param string $plaintext
201
     * @param string $hash_algorithm
202
     *
203
     * @return string
204
     */
205
    public static function encrypt(RSAKey $key, string $plaintext, string $hash_algorithm): string
206
    {
207
        /*
208
         * @var Hash
209
         */
210
        $hash = Hash::$hash_algorithm();
211
        $length = $key->getModulusLength() - 2 * $hash->getLength() - 2;
212
        if (0 >= $length) {
213
            throw new \RuntimeException();
214
        }
215
        $plaintext = str_split($plaintext, $length);
216
        $ciphertext = '';
217
        foreach ($plaintext as $m) {
218
            $ciphertext .= self::encryptRSAESOAEP($key, $m, $hash);
219
        }
220
221
        return $ciphertext;
222
    }
223
224
    /**
225
     * Decryption.
226
     *
227
     * @param RSAKey $key
228
     * @param string $ciphertext
229
     * @param string $hash_algorithm
230
     *
231
     * @return string
232
     */
233
    public static function decrypt(RSAKey $key, string $ciphertext, string $hash_algorithm): string
234
    {
235
        if (0 >= $key->getModulusLength()) {
236
            throw new \RuntimeException();
237
        }
238
        $hash = Hash::$hash_algorithm();
239
        $ciphertext = str_split($ciphertext, $key->getModulusLength());
240
        $ciphertext[count($ciphertext) - 1] = str_pad($ciphertext[count($ciphertext) - 1], $key->getModulusLength(), chr(0), STR_PAD_LEFT);
241
        $plaintext = '';
242
        foreach ($ciphertext as $c) {
243
            $temp = self::getRSAESOAEP($key, $c, $hash);
244
            $plaintext .= $temp;
245
        }
246
247
        return $plaintext;
248
    }
249
}
250