1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Smoren\EncryptionTools\Helpers; |
4
|
|
|
|
5
|
|
|
use Smoren\EncryptionTools\Exceptions\AsymmetricEncryptionException; |
6
|
|
|
use Smoren\EncryptionTools\Exceptions\JsonException; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* Class AsymmetricEncryptionHelper |
10
|
|
|
* @author Smoren <[email protected]> |
11
|
|
|
*/ |
12
|
|
|
class AsymmetricEncryptionHelper |
13
|
|
|
{ |
14
|
|
|
/** |
15
|
|
|
* Generates RSA key pair |
16
|
|
|
* @return string[] [$privateKey, $publicKey] |
17
|
|
|
* @throws AsymmetricEncryptionException |
18
|
|
|
*/ |
19
|
|
|
public static function generateKeyPair(): array |
20
|
|
|
{ |
21
|
|
|
$keyPair = openssl_pkey_new(); |
22
|
|
|
if(!$keyPair) { |
23
|
|
|
throw new AsymmetricEncryptionException( |
24
|
|
|
'openssl_pkey_new() returned false', |
25
|
|
|
AsymmetricEncryptionException::OPENSSL_ERROR |
26
|
|
|
); |
27
|
|
|
} |
28
|
|
|
|
29
|
|
|
openssl_pkey_export($keyPair, $privateKey); |
30
|
|
|
$details = openssl_pkey_get_details($keyPair); |
31
|
|
|
if(!$details) { |
|
|
|
|
32
|
|
|
throw new AsymmetricEncryptionException( |
33
|
|
|
'openssl_pkey_get_details() returned false', |
34
|
|
|
AsymmetricEncryptionException::OPENSSL_ERROR |
35
|
|
|
); |
36
|
|
|
} |
37
|
|
|
|
38
|
|
|
$publicKey = $details["key"]; |
39
|
|
|
|
40
|
|
|
return [$privateKey, $publicKey]; |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Returns data encrypted by public key |
45
|
|
|
* @param mixed $data data to encrypt |
46
|
|
|
* @param string $publicKey public key |
47
|
|
|
* @return string encrypted data |
48
|
|
|
* @throws AsymmetricEncryptionException |
49
|
|
|
* @throws JsonException |
50
|
|
|
*/ |
51
|
|
|
public static function encryptByPublicKey($data, string $publicKey): string |
52
|
|
|
{ |
53
|
|
|
static::validatePublicKey($publicKey); |
54
|
|
|
if(!openssl_public_encrypt(JsonHelper::encode($data), $dataEncrypted, $publicKey)) { |
55
|
|
|
throw new AsymmetricEncryptionException( |
56
|
|
|
'openssl_public_encrypt() returned false', |
57
|
|
|
AsymmetricEncryptionException::CANNOT_ENCRYPT |
58
|
|
|
); |
59
|
|
|
} |
60
|
|
|
return base64_encode($dataEncrypted); |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* Returns data encrypted by private key |
65
|
|
|
* @param mixed $data data to encrypt |
66
|
|
|
* @param string $privateKey public key |
67
|
|
|
* @return string encrypted data |
68
|
|
|
* @throws AsymmetricEncryptionException |
69
|
|
|
* @throws JsonException |
70
|
|
|
*/ |
71
|
|
|
public static function encryptByPrivateKey($data, string $privateKey): string |
72
|
|
|
{ |
73
|
|
|
static::validatePrivateKey($privateKey); |
74
|
|
|
if(!openssl_private_encrypt(JsonHelper::encode($data), $dataEncrypted, $privateKey)) { |
75
|
|
|
throw new AsymmetricEncryptionException( |
76
|
|
|
'openssl_private_encrypt() returned false', |
77
|
|
|
AsymmetricEncryptionException::CANNOT_ENCRYPT |
78
|
|
|
); |
79
|
|
|
} |
80
|
|
|
return base64_encode($dataEncrypted); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Returns data decrypted by public key |
85
|
|
|
* @param string $dataEncrypted data to decrypt |
86
|
|
|
* @param string $publicKey public key |
87
|
|
|
* @return mixed decrypted data |
88
|
|
|
* @throws AsymmetricEncryptionException |
89
|
|
|
*/ |
90
|
|
|
public static function decryptByPublicKey(string $dataEncrypted, string $publicKey) |
91
|
|
|
{ |
92
|
|
|
static::validatePublicKey($publicKey); |
93
|
|
|
openssl_public_decrypt(base64_decode($dataEncrypted), $dataDecrypted, $publicKey); |
94
|
|
|
|
95
|
|
|
if($dataDecrypted === null) { |
96
|
|
|
throw new AsymmetricEncryptionException( |
97
|
|
|
'cannot decrypt by public key', |
98
|
|
|
AsymmetricEncryptionException::CANNOT_DECRYPT |
99
|
|
|
); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
return json_decode($dataDecrypted, true); |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Returns data decrypted by private key |
107
|
|
|
* @param string $dataEncrypted data to decrypt |
108
|
|
|
* @param string $privateKey private key |
109
|
|
|
* @return mixed decrypted data |
110
|
|
|
* @throws AsymmetricEncryptionException |
111
|
|
|
*/ |
112
|
|
|
public static function decryptByPrivateKey(string $dataEncrypted, string $privateKey) |
113
|
|
|
{ |
114
|
|
|
static::validatePrivateKey($privateKey); |
115
|
|
|
openssl_private_decrypt(base64_decode($dataEncrypted), $dataDecrypted, $privateKey); |
116
|
|
|
|
117
|
|
|
if($dataDecrypted === null) { |
118
|
|
|
throw new AsymmetricEncryptionException( |
119
|
|
|
'cannot decrypt by private key', |
120
|
|
|
AsymmetricEncryptionException::CANNOT_DECRYPT |
121
|
|
|
); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
return json_decode($dataDecrypted, true); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* Returns signature created for data with private key |
129
|
|
|
* @param mixed $data data to sign |
130
|
|
|
* @param string $privateKey private key |
131
|
|
|
* @param int $algorithm openssl algorithm |
132
|
|
|
* @return string signature |
133
|
|
|
* @throws AsymmetricEncryptionException |
134
|
|
|
* @throws JsonException |
135
|
|
|
*/ |
136
|
|
|
public static function sign($data, string $privateKey, int $algorithm = OPENSSL_ALGO_SHA256): string |
137
|
|
|
{ |
138
|
|
|
static::validatePrivateKey($privateKey); |
139
|
|
|
openssl_sign(JsonHelper::encode($data), $signature, $privateKey, $algorithm); |
140
|
|
|
return $signature; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Verifies the signature |
145
|
|
|
* @param mixed $data data to verify signature for |
146
|
|
|
* @param string $signature signature to verify |
147
|
|
|
* @param string $publicKey public key to verfy signature with |
148
|
|
|
* @param int $algorithm openssl algorithm |
149
|
|
|
* @throws AsymmetricEncryptionException if verification failure |
150
|
|
|
* @throws JsonException |
151
|
|
|
*/ |
152
|
|
|
public static function verify( |
153
|
|
|
$data, |
154
|
|
|
string $signature, |
155
|
|
|
string $publicKey, |
156
|
|
|
int $algorithm = OPENSSL_ALGO_SHA256 |
157
|
|
|
): void { |
158
|
|
|
static::validatePublicKey($publicKey); |
159
|
|
|
if(!openssl_verify(JsonHelper::encode($data), $signature, $publicKey, $algorithm)) { |
160
|
|
|
throw new AsymmetricEncryptionException('wrong signature', AsymmetricEncryptionException::CANNOT_VERIFY); |
161
|
|
|
} |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Validates public key |
166
|
|
|
* @param string $publicKey public key to validate |
167
|
|
|
* @throws AsymmetricEncryptionException if key is invalid |
168
|
|
|
*/ |
169
|
|
|
public static function validatePublicKey(string $publicKey): void |
170
|
|
|
{ |
171
|
|
|
static::validateKey($publicKey, 'PUBLIC'); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* Validates private key |
176
|
|
|
* @param string $privateKey private key to validate |
177
|
|
|
* @throws AsymmetricEncryptionException if key is invalid |
178
|
|
|
*/ |
179
|
|
|
public static function validatePrivateKey(string $privateKey): void |
180
|
|
|
{ |
181
|
|
|
static::validateKey($privateKey, 'PRIVATE'); |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* Validates key |
186
|
|
|
* @param string $key key to validate |
187
|
|
|
* @param string $keyType key type (PUBLIC or PRIVATE) |
188
|
|
|
* @throws AsymmetricEncryptionException if key is invalid |
189
|
|
|
*/ |
190
|
|
|
protected static function validateKey(string $key, string $keyType): void |
191
|
|
|
{ |
192
|
|
|
$arPublicKey = explode("\n", $key); |
193
|
|
|
$beginString = array_shift($arPublicKey); |
194
|
|
|
$endLineBreak = array_pop($arPublicKey); |
195
|
|
|
$endString = array_pop($arPublicKey); |
196
|
|
|
$lastPart = array_pop($arPublicKey); |
197
|
|
|
|
198
|
|
|
$isCorrect = true; |
199
|
|
|
|
200
|
|
|
if( |
201
|
|
|
$endLineBreak !== "" || |
202
|
|
|
$beginString !== "-----BEGIN {$keyType} KEY-----" || |
203
|
|
|
$endString !== "-----END {$keyType} KEY-----" || |
204
|
|
|
!preg_match('/^.{1,63}$/', $lastPart ?? '') |
205
|
|
|
) { |
206
|
|
|
$isCorrect = false; |
207
|
|
|
} else { |
208
|
|
|
foreach($arPublicKey as $part) { |
209
|
|
|
if(!preg_match('/^.{64}$/', $part)) { |
210
|
|
|
$isCorrect = false; |
211
|
|
|
break; |
212
|
|
|
} |
213
|
|
|
} |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
if(!$isCorrect) { |
217
|
|
|
throw new AsymmetricEncryptionException( |
218
|
|
|
'invalid key format', |
219
|
|
|
AsymmetricEncryptionException::INVALID_KEY_FORMAT |
220
|
|
|
); |
221
|
|
|
} |
222
|
|
|
} |
223
|
|
|
} |
224
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.