Completed
Push — master ( b8fcd2...ffbcb5 )
by Tony Karavasilev (Тони
17:13
created

AbstractRsaEncryption::decryptData()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 40
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 20
c 1
b 0
f 0
dl 0
loc 40
ccs 17
cts 17
cp 1
rs 9.6
cc 4
nc 4
nop 1
crap 4
1
<?php
2
3
/**
4
 * The asymmetric RSA algorithm abstraction specification.
5
 */
6
7
namespace CryptoManana\Core\Abstractions\MessageEncryption;
8
9
use \CryptoManana\Core\Abstractions\MessageEncryption\AbstractAsymmetricEncryptionAlgorithm as AsymmetricAlgorithm;
10
use \CryptoManana\Core\Interfaces\MessageEncryption\DataEncryptionInterface as AsymmetricDataEncryption;
11
use \CryptoManana\Core\Interfaces\MessageEncryption\AsymmetricPaddingInterface as AsymmetricDataPadding;
12
use \CryptoManana\Core\Interfaces\MessageEncryption\ObjectEncryptionInterface as ObjectEncryption;
13
use \CryptoManana\Core\Interfaces\MessageEncryption\FileEncryptionInterface as FileEncryption;
14
use \CryptoManana\Core\Interfaces\MessageEncryption\CipherDataFormatsInterface as CipherDataFormatting;
15
use \CryptoManana\Core\Traits\MessageEncryption\AsymmetricPaddingTrait as RsaPaddingStandards;
16
use \CryptoManana\Core\Traits\MessageEncryption\ObjectEncryptionTrait as EncryptObjects;
17
use \CryptoManana\Core\Traits\MessageEncryption\FileEncryptionTrait as EncryptFiles;
18
use \CryptoManana\Core\Traits\MessageEncryption\CipherDataFormatsTrait as CipherDataFormats;
19
20
/**
21
 * Class AbstractRsaEncryption - The RSA encryption algorithm abstraction representation.
22
 *
23
 * @package CryptoManana\Core\Abstractions\MessageEncryption
24
 *
25
 * @mixin RsaPaddingStandards
26
 * @mixin CipherDataFormats
27
 */
28
abstract class AbstractRsaEncryption extends AsymmetricAlgorithm implements
29
    AsymmetricDataEncryption,
30
    AsymmetricDataPadding,
31
    ObjectEncryption,
32
    FileEncryption,
33
    CipherDataFormatting
34
{
35
    /**
36
     * The RSA data padding basic standards.
37
     *
38
     * {@internal Reusable implementation of `AsymmetricPaddingInterface`. }}
39
     */
40
    use RsaPaddingStandards;
41
42
    /**
43
     * Object encryption and decryption capabilities.
44
     *
45
     * {@internal Reusable implementation of `ObjectEncryptionInterface`. }}
46
     */
47
    use EncryptObjects;
48
49
    /**
50
     * File content encryption and decryption capabilities.
51
     *
52
     * {@internal Reusable implementation of `FileEncryptionInterface`. }}
53
     */
54
    use EncryptFiles;
55
56
    /**
57
     * Cipher data outputting formats.
58
     *
59
     * {@internal Reusable implementation of `CipherDataFormatsInterface`. }}
60
     */
61
    use CipherDataFormats;
62
63
    /**
64
     * The internal name of the algorithm.
65
     */
66
    const ALGORITHM_NAME = OPENSSL_KEYTYPE_RSA;
67
68
    /**
69
     * The asymmetric data padding operation property.
70
     *
71
     * @var int The data padding operation integer code value.
72
     */
73
    protected $padding = self::OAEP_PADDING;
74
75
    /**
76
     * The output cipher format property storage.
77
     *
78
     * @var int The output cipher format integer code value.
79
     */
80
    protected $cipherFormat = self::ENCRYPTION_OUTPUT_BASE_64;
81
82
    /**
83
     * Flag for enabling/disabling data processing via chunks.
84
     *
85
     * @var bool Flag to for data processing via chunks.
86
     */
87
    protected $useChunks = false;
88
89
    /**
90
     * Internal method for the validation of plain data used at encryption/signing operations.
91
     *
92
     * @param string $plainData The plain input string.
93
     *
94
     * @throws \Exception Validation errors.
95
     */
96 152
    protected function validatePlainData($plainData)
97
    {
98 152
        if (!is_string($plainData)) {
99 8
            throw new \InvalidArgumentException('The data for encryption must be a string or a binary string.');
100 144
        } elseif ($this->useChunks === false) {
101 136
            $chunkSize = (int)ceil(static::KEY_SIZE / 8) - (($this->padding === OPENSSL_PKCS1_PADDING) ? 11 : 42);
102
103 136
            if (strlen($plainData) > $chunkSize) {
104 8
                throw new \InvalidArgumentException(
105 4
                    'The data for encryption must be less or equal of ' . $chunkSize . ' bytes. Another option is ' .
106 8
                    'to allow chunk processing via the `enableChunkProcessing` method, which is not recommended.'
107
                );
108
            }
109
        }
110 136
    }
111
112
    /**
113
     * Internal method for the validation of cipher/signature data used at decryption/verifying operations.
114
     *
115
     * @param string $cipherOrSignatureData The encrypted input string or a signature string.
116
     *
117
     * @throws \Exception Validation errors.
118
     */
119 112
    protected function validateCipherOrSignatureData($cipherOrSignatureData)
120
    {
121 112
        if (!is_string($cipherOrSignatureData)) {
122 8
            throw new \InvalidArgumentException('The data for decryption must be a string or a binary string.');
123 104
        } elseif ($cipherOrSignatureData === '') {
124 8
            throw new \InvalidArgumentException('The string is empty and there is nothing to decrypt from it.');
125
        }
126
127 96
        $chunkSize = (int)ceil(static::KEY_SIZE / 8);
128 96
        $rawDataSize = strlen($this->changeOutputFormat($cipherOrSignatureData, false));
129
130 96
        if ($this->useChunks === false && $rawDataSize > $chunkSize) {
131 8
            throw new \InvalidArgumentException(
132 4
                'The data for decryption must be less or equal of ' . $chunkSize . ' bytes. Another option is ' .
133 8
                'to allow chunk processing via the `enableChunkProcessing` method, which is not recommended.'
134
            );
135 88
        } elseif ($rawDataSize % $chunkSize !== 0) {
136 8
            throw new \InvalidArgumentException(
137 8
                'The data length for decryption must dividable by ' . $chunkSize . ' byte blocks.'
138
            );
139
        }
140 80
    }
141
142
    /**
143
     * Encrypts the given plain data.
144
     *
145
     * @param string $plainData The plain input string.
146
     *
147
     * @return string The cipher/encrypted data.
148
     * @throws \Exception Validation errors.
149
     */
150 160
    public function encryptData($plainData)
151
    {
152 160
        $this->checkIfThePublicKeyIsSet();
153 152
        $this->validatePlainData($plainData);
154
155 136
        $publicKeyResource = openssl_pkey_get_public(base64_decode($this->publicKey));
156
157
        // @codeCoverageIgnoreStart
158
        if ($publicKeyResource === false) {
159
            throw new \RuntimeException(
160
                'Failed to use the current public key, probably because of ' .
161
                'a misconfigured OpenSSL library or an invalid key.'
162
            );
163
        }
164
        // @codeCoverageIgnoreEnd
165
166
        /**
167
         * {@internal The reserved bytes for the PKCS1 standard are 11 and for the OAEP are 42. }}
168
         */
169 136
        $reservedSize = ($this->padding === OPENSSL_PKCS1_PADDING) ? 11 : 42;
170 136
        $chunkSize = (int)ceil(static::KEY_SIZE / 8) - $reservedSize;
171
172 136
        $plainData = ($plainData === '') ? ' ' : $plainData;
173 136
        $cipherData = '';
174
175 136
        while ($plainData) {
176 136
            $dataChunk = substr($plainData, 0, $chunkSize);
177 136
            $plainData = substr($plainData, $chunkSize);
178 136
            $encryptedChunk = '';
179
180 136
            if (!openssl_public_encrypt($dataChunk, $encryptedChunk, $publicKeyResource, $this->padding)) {
181 8
                throw new \InvalidArgumentException('The data encryption failed because of a wrong format');
182
            }
183
184 128
            $cipherData .= $encryptedChunk;
185
        }
186
187
        // Free the public key (resource cleanup)
188 128
        openssl_free_key($publicKeyResource);
189 128
        $publicKeyResource = null;
190
191 128
        return $this->changeOutputFormat($cipherData, true);
192
    }
193
194
    /**
195
     * Decrypts the given cipher data.
196
     *
197
     * @param string $cipherData The encrypted input string.
198
     *
199
     * @return string The decrypted/plain data.
200
     * @throws \Exception Validation errors.
201
     */
202 120
    public function decryptData($cipherData)
203
    {
204 120
        $this->checkIfThePrivateKeyIsSet();
205 112
        $this->validateCipherOrSignatureData($cipherData);
206
207 80
        $cipherData = $this->changeOutputFormat($cipherData, false);
208 80
        $privateKeyResource = openssl_pkey_get_private(base64_decode($this->privateKey));
209
210
        // @codeCoverageIgnoreStart
211
        if ($privateKeyResource === false) {
212
            throw new \RuntimeException(
213
                'Failed to use the current private key, probably because of ' .
214
                'a misconfigured OpenSSL library or an invalid key.'
215
            );
216
        }
217
        // @codeCoverageIgnoreEnd
218
219
        /**
220
         * {@internal The block size must be exactly dividable by the chunks size. }}
221
         */
222 80
        $chunkSize = (int)ceil(static::KEY_SIZE / 8);
223 80
        $plainData = '';
224
225 80
        while ($cipherData) {
226 80
            $chunkData = substr($cipherData, 0, $chunkSize);
227 80
            $cipherData = substr($cipherData, $chunkSize);
228 80
            $decryptedChunk = '';
229
230 80
            if (!openssl_private_decrypt($chunkData, $decryptedChunk, $privateKeyResource, $this->padding)) {
231 8
                throw new \InvalidArgumentException('The data decryption failed because of a wrong format');
232
            }
233
234 72
            $plainData .= $decryptedChunk;
235
        }
236
237
        // Free the private key (resource cleanup)
238 72
        openssl_free_key($privateKeyResource);
239 72
        $privateKeyResource = null;
240
241 72
        return $plainData;
242
    }
243
244
    /**
245
     * RSA asymmetric algorithm constructor.
246
     */
247 296
    public function __construct()
248
    {
249 296
    }
250
251
    /**
252
     * Get debug information for the class instance.
253
     *
254
     * @return array Debug information.
255
     */
256 8
    public function __debugInfo()
257
    {
258
        return [
259 8
            'standard' => 'RSA',
260 8
            'type' => 'asymmetrical encryption or public-key cipher',
261 8
            'key size in bits' => static::KEY_SIZE,
262 8
            'maximum input in bits' => static::KEY_SIZE - (($this->padding === OPENSSL_PKCS1_PADDING) ? 88 : 336),
263 8
            'is chunk processing enabled' => $this->useChunks,
264 8
            'padding standard' => $this->padding === self::OAEP_PADDING ? 'OAEP' : 'PKCS1',
265 8
            'private key' => $this->privateKey,
266 8
            'public key' => $this->publicKey,
267
        ];
268
    }
269
}
270