RSACrypt::encryptRSAESOAEP()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.6666
c 0
b 0
f 0
cc 1
nc 1
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2019 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\Algorithm\KeyEncryption\Util;
15
16
use InvalidArgumentException;
17
use Jose\Component\Core\Util\BigInteger;
18
use Jose\Component\Core\Util\Hash;
19
use Jose\Component\Core\Util\RSAKey;
20
use RuntimeException;
21
22
/**
23
 * @internal
24
 */
25
class RSACrypt
26
{
27
    /**
28
     * Optimal Asymmetric Encryption Padding (OAEP).
29
     */
30
    public const ENCRYPTION_OAEP = 1;
31
32
    /**
33
     * Use PKCS#1 padding.
34
     */
35
    public const ENCRYPTION_PKCS1 = 2;
36
37
    public static function encrypt(RSAKey $key, string $data, int $mode, ?string $hash = null): string
38
    {
39
        switch ($mode) {
40
            case self::ENCRYPTION_OAEP:
41
                return self::encryptWithRSAOAEP($key, $data, $hash);
42
            case self::ENCRYPTION_PKCS1:
43
                return self::encryptWithRSA15($key, $data);
44
            default:
45
                throw new InvalidArgumentException('Unsupported mode.');
46
        }
47
    }
48
49
    public static function decrypt(RSAKey $key, string $plaintext, int $mode, ?string $hash = null): string
50
    {
51
        switch ($mode) {
52
            case self::ENCRYPTION_OAEP:
53
                return self::decryptWithRSAOAEP($key, $plaintext, $hash);
54
            case self::ENCRYPTION_PKCS1:
55
                return self::decryptWithRSA15($key, $plaintext);
56
            default:
57
                throw new InvalidArgumentException('Unsupported mode.');
58
        }
59
    }
60
61
    public static function encryptWithRSA15(RSAKey $key, string $data): string
62
    {
63
        $mLen = mb_strlen($data, '8bit');
64
        if ($mLen > $key->getModulusLength() - 11) {
65
            throw new InvalidArgumentException('Message too long');
66
        }
67
68
        $psLen = $key->getModulusLength() - $mLen - 3;
69
        $ps = '';
70
        while (mb_strlen($ps, '8bit') !== $psLen) {
71
            $temp = random_bytes($psLen - mb_strlen($ps, '8bit'));
72
            $temp = str_replace("\x00", '', $temp);
73
            $ps .= $temp;
74
        }
75
        $type = 2;
76
        $data = \chr(0).\chr($type).$ps.\chr(0).$data;
77
78
        $data = BigInteger::createFromBinaryString($data);
79
        $c = self::getRSAEP($key, $data);
80
81
        return self::convertIntegerToOctetString($c, $key->getModulusLength());
82
    }
83
84
    public static function decryptWithRSA15(RSAKey $key, string $c): string
85
    {
86
        if (mb_strlen($c, '8bit') !== $key->getModulusLength()) {
87
            throw new InvalidArgumentException('Unable to decrypt');
88
        }
89
        $c = BigInteger::createFromBinaryString($c);
90
        $m = self::getRSADP($key, $c);
91
        $em = self::convertIntegerToOctetString($m, $key->getModulusLength());
92
        if (0 !== \ord($em[0]) || \ord($em[1]) > 2) {
93
            throw new InvalidArgumentException('Unable to decrypt');
94
        }
95
        $ps = mb_substr($em, 2, (int) mb_strpos($em, \chr(0), 2, '8bit') - 2, '8bit');
96
        $m = mb_substr($em, mb_strlen($ps, '8bit') + 3, null, '8bit');
97
        if (mb_strlen($ps, '8bit') < 8) {
98
            throw new InvalidArgumentException('Unable to decrypt');
99
        }
100
101
        return $m;
102
    }
103
104
    /**
105
     * Encryption.
106
     */
107
    public static function encryptWithRSAOAEP(RSAKey $key, string $plaintext, string $hash_algorithm): string
108
    {
109
        /** @var Hash $hash */
110
        $hash = Hash::$hash_algorithm();
111
        $length = $key->getModulusLength() - 2 * $hash->getLength() - 2;
112
        if (0 >= $length) {
113
            throw new RuntimeException();
114
        }
115
        $plaintext = mb_str_split($plaintext, $length, '8bit');
116
        if (!\is_array($plaintext)) {
117
            throw new RuntimeException('Invalid payload');
118
        }
119
        $ciphertext = '';
120
        foreach ($plaintext as $m) {
121
            $ciphertext .= self::encryptRSAESOAEP($key, $m, $hash);
122
        }
123
124
        return $ciphertext;
125
    }
126
127
    /**
128
     * Decryption.
129
     */
130
    public static function decryptWithRSAOAEP(RSAKey $key, string $ciphertext, string $hash_algorithm): string
131
    {
132
        if (0 >= $key->getModulusLength()) {
133
            throw new RuntimeException('Invalid modulus length');
134
        }
135
        $hash = Hash::$hash_algorithm();
136
        $ciphertext = mb_str_split($ciphertext, $key->getModulusLength(), '8bit');
137
        if (!\is_array($ciphertext)) {
138
            throw new RuntimeException('Invalid ciphertext');
139
        }
140
        $ciphertext[\count($ciphertext) - 1] = str_pad($ciphertext[\count($ciphertext) - 1], $key->getModulusLength(), \chr(0), STR_PAD_LEFT);
141
        $plaintext = '';
142
        foreach ($ciphertext as $c) {
143
            $temp = self::getRSAESOAEP($key, $c, $hash);
144
            $plaintext .= $temp;
145
        }
146
147
        return $plaintext;
148
    }
149
150
    private static function convertIntegerToOctetString(BigInteger $x, int $xLen): string
151
    {
152
        $x = $x->toBytes();
153
        if (mb_strlen($x, '8bit') > $xLen) {
154
            throw new RuntimeException('Invalid length.');
155
        }
156
157
        return str_pad($x, $xLen, \chr(0), STR_PAD_LEFT);
158
    }
159
160
    /**
161
     * Octet-String-to-Integer primitive.
162
     */
163
    private static function convertOctetStringToInteger(string $x): BigInteger
164
    {
165
        return BigInteger::createFromBinaryString($x);
166
    }
167
168
    /**
169
     * RSA EP.
170
     */
171
    private static function getRSAEP(RSAKey $key, BigInteger $m): BigInteger
172
    {
173
        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...
174
            throw new RuntimeException();
175
        }
176
177
        return RSAKey::exponentiate($key, $m);
0 ignored issues
show
Documentation introduced by
$key is of type object<Jose\Component\Core\Util\RSAKey>, 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...
178
    }
179
180
    /**
181
     * RSA DP.
182
     */
183
    private static function getRSADP(RSAKey $key, BigInteger $c): BigInteger
184
    {
185
        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...
186
            throw new RuntimeException();
187
        }
188
189
        return RSAKey::exponentiate($key, $c);
0 ignored issues
show
Documentation introduced by
$key is of type object<Jose\Component\Core\Util\RSAKey>, 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...
190
    }
191
192
    /**
193
     * MGF1.
194
     */
195
    private static function getMGF1(string $mgfSeed, int $maskLen, Hash $mgfHash): string
196
    {
197
        $t = '';
198
        $count = ceil($maskLen / $mgfHash->getLength());
199
        for ($i = 0; $i < $count; ++$i) {
200
            $c = pack('N', $i);
201
            $t .= $mgfHash->hash($mgfSeed.$c);
202
        }
203
204
        return mb_substr($t, 0, $maskLen, '8bit');
205
    }
206
207
    /**
208
     * RSAES-OAEP-ENCRYPT.
209
     */
210
    private static function encryptRSAESOAEP(RSAKey $key, string $m, Hash $hash): string
211
    {
212
        $mLen = mb_strlen($m, '8bit');
213
        $lHash = $hash->hash('');
214
        $ps = str_repeat(\chr(0), $key->getModulusLength() - $mLen - 2 * $hash->getLength() - 2);
215
        $db = $lHash.$ps.\chr(1).$m;
216
        $seed = random_bytes($hash->getLength());
217
        $dbMask = self::getMGF1($seed, $key->getModulusLength() - $hash->getLength() - 1, $hash/*MGF*/);
218
        $maskedDB = (string) ($db ^ $dbMask);
219
        $seedMask = self::getMGF1($maskedDB, $hash->getLength(), $hash/*MGF*/);
220
        $maskedSeed = $seed ^ $seedMask;
221
        $em = \chr(0).$maskedSeed.$maskedDB;
222
223
        $m = self::convertOctetStringToInteger($em);
224
        $c = self::getRSAEP($key, $m);
225
226
        return self::convertIntegerToOctetString($c, $key->getModulusLength());
227
    }
228
229
    /**
230
     * RSAES-OAEP-DECRYPT.
231
     */
232
    private static function getRSAESOAEP(RSAKey $key, string $c, Hash $hash): string
233
    {
234
        $c = self::convertOctetStringToInteger($c);
235
        $m = self::getRSADP($key, $c);
236
        $em = self::convertIntegerToOctetString($m, $key->getModulusLength());
237
        $lHash = $hash->hash('');
238
        $maskedSeed = mb_substr($em, 1, $hash->getLength(), '8bit');
239
        $maskedDB = mb_substr($em, $hash->getLength() + 1, null, '8bit');
240
        $seedMask = self::getMGF1($maskedDB, $hash->getLength(), $hash/*MGF*/);
241
        $seed = (string) ($maskedSeed ^ $seedMask);
242
        $dbMask = self::getMGF1($seed, $key->getModulusLength() - $hash->getLength() - 1, $hash/*MGF*/);
243
        $db = $maskedDB ^ $dbMask;
244
        $lHash2 = mb_substr($db, 0, $hash->getLength(), '8bit');
245
        $m = mb_substr($db, $hash->getLength(), null, '8bit');
246
        if (!hash_equals($lHash, $lHash2)) {
247
            throw new RuntimeException();
248
        }
249
        $m = ltrim($m, \chr(0));
250
        if (1 !== \ord($m[0])) {
251
            throw new RuntimeException();
252
        }
253
254
        return mb_substr($m, 1, null, '8bit');
255
    }
256
}
257