├── src └── Rsa │ ├── Exceptions │ ├── CouldNotDecryptData.php │ ├── FileDoesNotExist.php │ ├── InvalidPrivateKey.php │ └── InvalidPublicKey.php │ ├── KeyPair.php │ ├── PublicKey.php │ └── PrivateKey.php ├── CHANGELOG.md ├── psalm.xml.dist ├── LICENSE.md ├── .php-cs-fixer.dist.php ├── composer.json └── README.md /src/Rsa/Exceptions/CouldNotDecryptData.php: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Spatie bvba 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | in([ 5 | __DIR__ . '/src', 6 | __DIR__ . '/tests', 7 | ]) 8 | ->name('*.php') 9 | ->notName('*.blade.php') 10 | ->ignoreDotFiles(true) 11 | ->ignoreVCS(true); 12 | 13 | return (new PhpCsFixer\Config()) 14 | ->setRules([ 15 | '@PSR12' => true, 16 | 'array_syntax' => ['syntax' => 'short'], 17 | 'ordered_imports' => ['sort_algorithm' => 'alpha'], 18 | 'no_unused_imports' => true, 19 | 'not_operator_with_successor_space' => true, 20 | 'trailing_comma_in_multiline' => true, 21 | 'phpdoc_scalar' => true, 22 | 'unary_operator_spaces' => true, 23 | 'binary_operator_spaces' => true, 24 | 'blank_line_before_statement' => [ 25 | 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], 26 | ], 27 | 'phpdoc_single_line_var_spacing' => true, 28 | 'phpdoc_var_without_name' => true, 29 | 'class_attributes_separation' => [ 30 | 'elements' => [ 31 | 'method' => 'one', 32 | ], 33 | ], 34 | 'method_argument_space' => [ 35 | 'on_multiline' => 'ensure_fully_multiline', 36 | 'keep_multiple_spaces_after_comma' => true, 37 | ], 38 | 'single_trait_insert_per_statement' => true, 39 | ]) 40 | ->setFinder($finder); 41 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/crypto", 3 | "description": "Encrypting and signing data using private/public keys", 4 | "keywords": [ 5 | "spatie", 6 | "crypto" 7 | ], 8 | "homepage": "https://github.com/spatie/crypto", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Freek Van der Herten", 13 | "email": "freek@spatie.be", 14 | "homepage": "https://spatie.be", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^7.4|^8.0", 20 | "ext-openssl": "*" 21 | }, 22 | "require-dev": { 23 | "phpunit/phpunit": "^9.3", 24 | "symfony/var-dumper": "^5.1|^6.0|^7.0|^8.0" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "Spatie\\Crypto\\": "src" 29 | } 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "Spatie\\Crypto\\Tests\\": "tests" 34 | } 35 | }, 36 | "scripts": { 37 | "psalm": "vendor/bin/psalm", 38 | "test": "vendor/bin/phpunit", 39 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage", 40 | "format": "vendor/bin/php-cs-fixer fix --allow-risky=yes" 41 | }, 42 | "config": { 43 | "sort-packages": true 44 | }, 45 | "minimum-stability": "dev", 46 | "prefer-stable": true, 47 | "funding": [ 48 | { 49 | "type": "github", 50 | "url": "https://github.com/sponsors/spatie" 51 | }, 52 | { 53 | "type": "other", 54 | "url": "https://spatie.be/open-source/support-us" 55 | } 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /src/Rsa/KeyPair.php: -------------------------------------------------------------------------------- 1 | privateKeyType = $privateKeyType; 19 | $this->privateKeyBits = $privateKeyBits; 20 | $this->digestAlgorithm = (string)$digestAlgorithm; 21 | } 22 | 23 | public function password(string $password = null): self 24 | { 25 | $this->password = $password; 26 | 27 | return $this; 28 | } 29 | 30 | public function generate( 31 | string $privateKeyPath = '', 32 | string $publicKeyPath = '' 33 | ): array { 34 | /** @var \OpenSSLAsymmetricKey $asymmetricKey */ 35 | $asymmetricKey = openssl_pkey_new([ 36 | "digest_alg" => $this->digestAlgorithm, 37 | "private_key_bits" => $this->privateKeyBits, 38 | "private_key_type" => $this->privateKeyType, 39 | ]); 40 | 41 | openssl_pkey_export( 42 | $asymmetricKey, 43 | $privateKey, 44 | $this->password, 45 | ); 46 | 47 | $rawPublicKey = openssl_pkey_get_details($asymmetricKey); 48 | 49 | $publicKey = $rawPublicKey['key']; 50 | 51 | if ($privateKeyPath !== '') { 52 | file_put_contents($privateKeyPath, $privateKey); 53 | } 54 | 55 | if ($publicKeyPath !== '') { 56 | file_put_contents($publicKeyPath, $publicKey); 57 | } 58 | 59 | return [$privateKey, $publicKey]; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Rsa/PublicKey.php: -------------------------------------------------------------------------------- 1 | publicKey = openssl_pkey_get_public($publicKeyString); 33 | 34 | if ($this->publicKey === false) { 35 | throw InvalidPublicKey::make(); 36 | } 37 | } 38 | 39 | public function encrypt(string $data, int $padding = OPENSSL_PKCS1_OAEP_PADDING) 40 | { 41 | openssl_public_encrypt($data, $encrypted, $this->publicKey, $padding); 42 | 43 | return $encrypted; 44 | } 45 | 46 | public function canDecrypt(string $data): bool 47 | { 48 | try { 49 | $this->decrypt($data); 50 | } catch (CouldNotDecryptData $exception) { 51 | return false; 52 | } 53 | 54 | return true; 55 | } 56 | 57 | public function decrypt(string $data, int $padding = OPENSSL_PKCS1_PADDING): string 58 | { 59 | openssl_public_decrypt($data, $decrypted, $this->publicKey, $padding); 60 | 61 | if (is_null($decrypted)) { 62 | throw CouldNotDecryptData::make(); 63 | } 64 | 65 | return $decrypted; 66 | } 67 | 68 | public function details(): array 69 | { 70 | return openssl_pkey_get_details($this->publicKey); 71 | } 72 | 73 | public function verify(string $data, string $signature, int $algorithm = OPENSSL_ALGO_SHA256): bool 74 | { 75 | return openssl_verify($data, base64_decode($signature), $this->publicKey, $algorithm); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Rsa/PrivateKey.php: -------------------------------------------------------------------------------- 1 | privateKey = $password ? 38 | openssl_pkey_get_private($privateKeyString, $password) : 39 | openssl_pkey_get_private($privateKeyString); 40 | 41 | if ($this->privateKey === false) { 42 | throw InvalidPrivateKey::make(); 43 | } 44 | } 45 | 46 | public function encrypt(string $data, int $padding = OPENSSL_PKCS1_PADDING): string 47 | { 48 | openssl_private_encrypt($data, $decrypted, $this->privateKey, $padding); 49 | 50 | return $decrypted; 51 | } 52 | 53 | public function canDecrypt(string $data): bool 54 | { 55 | try { 56 | $this->decrypt($data); 57 | } catch (CouldNotDecryptData $exception) { 58 | return false; 59 | } 60 | 61 | return true; 62 | } 63 | 64 | public function decrypt(string $data, int $padding = OPENSSL_PKCS1_OAEP_PADDING): string 65 | { 66 | openssl_private_decrypt($data, $decrypted, $this->privateKey, $padding); 67 | 68 | if (is_null($decrypted)) { 69 | throw CouldNotDecryptData::make(); 70 | } 71 | 72 | return $decrypted; 73 | } 74 | 75 | public function details(): array 76 | { 77 | return openssl_pkey_get_details($this->privateKey); 78 | } 79 | 80 | public function sign(string $data, int $algorithm = OPENSSL_ALGO_SHA256): string 81 | { 82 | openssl_sign($data, $signature, $this->privateKey, $algorithm); 83 | 84 | return base64_encode($signature); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Encrypting and signing data using private/public keys 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/crypto.svg?style=flat-square)](https://packagist.org/packages/spatie/crypto) 4 | ![Tests](https://github.com/spatie/crypto/workflows/Tests/badge.svg) 5 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/crypto.svg?style=flat-square)](https://packagist.org/packages/spatie/crypto) 6 | 7 | 8 | This package allows you to easily generate a private/public key pairs, and encrypt/decrypt messages using those keys. 9 | 10 | ```php 11 | use Spatie\Crypto\Rsa\KeyPair; 12 | use Spatie\Crypto\Rsa\PrivateKey; 13 | use Spatie\Crypto\Rsa\PublicKey; 14 | 15 | // generating an RSA key pair 16 | [$privateKey, $publicKey] = (new KeyPair())->generate(); 17 | 18 | // when passing paths, the generated keys will be written those paths 19 | (new KeyPair())->generate($pathToPrivateKey, $pathToPublicKey); 20 | 21 | $data = 'my secret data'; 22 | 23 | $privateKey = PrivateKey::fromFile($pathToPrivateKey); 24 | $encryptedData = $privateKey->encrypt($data); // returns something unreadable 25 | 26 | $publicKey = PublicKey::fromFile($pathToPublicKey); 27 | $decryptedData = $publicKey->decrypt($encryptedData); // returns 'my secret data' 28 | ``` 29 | 30 | Most functions in this package are wrappers around `openssl_*` functions to improve DX. 31 | 32 | ## Support us 33 | 34 | [](https://spatie.be/github-ad-click/crypto) 35 | 36 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 37 | 38 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 39 | 40 | ## Installation 41 | 42 | You can install the package via composer: 43 | 44 | ```bash 45 | composer require spatie/crypto 46 | ``` 47 | 48 | ## Usage 49 | 50 | You can generate a key pair using the `generate` function on the `KeyPair` class. 51 | 52 | ```php 53 | use Spatie\Crypto\Rsa\KeyPair; 54 | 55 | [$privateKey, $publicKey] = (new KeyPair())->generate(); 56 | ``` 57 | 58 | You can write the keys to disk, by passing paths to the `generate` function. 59 | 60 | ```php 61 | // when passing paths, the generate keys will to those paths 62 | (new KeyPair())->generate($pathToPrivateKey, $pathToPublicKey) 63 | ``` 64 | 65 | You can protect the private key with a password by using the `password` method: 66 | 67 | ```php 68 | [$passwordProtectedPrivateKey, $publicKey] = (new KeyPair())->password('my-password')->generate(); 69 | ``` 70 | 71 | When using a password to generating a private key, you will need that password when instantiating the `PrivateKey` class. 72 | 73 | ### Loading keys 74 | 75 | To load a key from a file use the `fromFile` static method. 76 | 77 | ```php 78 | Spatie\Crypto\Rsa\PrivateKey::fromFile($pathToPrivateKey); 79 | Spatie\Crypto\Rsa\PublicKey::fromFile($pathToPublicKey); 80 | ``` 81 | 82 | Alternatively, you can also create a key object using a string. 83 | 84 | ```php 85 | Spatie\Crypto\Rsa\PrivateKey::fromString($privateKeyString); 86 | Spatie\Crypto\Rsa\PublicKey::fromString($publicKeyString); 87 | ``` 88 | 89 | If the private key is password protected, you need to pass the password as the second argument. 90 | 91 | ```php 92 | Spatie\Crypto\Rsa\PrivateKey::fromFile($pathToPrivateKey, $password); 93 | Spatie\Crypto\Rsa\PrivateKey::fromString($privateKeyString, $password); 94 | ``` 95 | 96 | If you do not specify the right password, a `Spatie\Crypto\Exceptions\InvalidPrivateKey` exception will be thrown. 97 | 98 | ### Encrypting a message with a private key, decrypting with the public key 99 | 100 | Here's how you can encrypt data using the private key, and how to decrypt it using the public key. 101 | 102 | ```php 103 | $data = 'my secret data'; 104 | 105 | $privateKey = Spatie\Crypto\Rsa\PrivateKey::fromFile($pathToPrivateKey); 106 | $encryptedData = $privateKey->encrypt($data); // encrypted data contains something unreadable 107 | 108 | $publicKey = Spatie\Crypto\Rsa\PublicKey::fromFile($pathToPublicKey); 109 | $decryptedData = $publicKey->decrypt($encryptedData); // decrypted data contains 'my secret data' 110 | ``` 111 | 112 | If `decrypt` cannot decrypt the given data (maybe a non-matching private key was used to encrypt the data, or maybe tampered with the data), an exception of class `Spatie\Crypto\Exceptions\CouldNotDecryptData` will be thrown. 113 | 114 | ### Encrypting a message with a public key, decrypting with the private key 115 | 116 | Here's how you can encrypt data using the public key, and how to decrypt it using the private key. 117 | 118 | ```php 119 | $data = 'my secret data'; 120 | 121 | $publicKey = Spatie\Crypto\Rsa\PublicKey::fromFile($pathToPublicKey); 122 | $encryptedData = $publicKey->encrypt($data); // encrypted data contains something unreadable 123 | 124 | $privateKey = Spatie\Crypto\Rsa\PrivateKey::fromFile($pathToPrivateKey); 125 | $decryptedData = $privateKey->decrypt($encryptedData); // decrypted data contains 'my secret data' 126 | ``` 127 | 128 | If `decrypt` cannot decrypt the given data (maybe a non-matching public key was used to encrypt the data, or maybe tampered with the data), an exception of class `Spatie\Crypto\Exceptions\CouldNotDecryptData` will be thrown. 129 | 130 | ### Determining if the data can be decrypted 131 | 132 | Both the `PublicKey` and `PrivateKey` class have a `canDecrypt` method to determine if given data can be decrypted. 133 | 134 | ```php 135 | Spatie\Crypto\Rsa\PrivateKey::fromFile($pathToPrivateKey)->canDecrypt($data); // returns a boolean; 136 | Spatie\Crypto\Rsa\PublicKey::fromFile($pathToPublicKey)->canDecrypt($data); // returns a boolean; 137 | ``` 138 | 139 | ### Signing and verifying data 140 | 141 | The `PrivateKey` class has a method `sign` to generate a signature for the given data. The `verify` method on the `PublicKey` class can be used to verify if a signature is valid for the given data. 142 | 143 | If `verify` returns `true`, you know for certain that the holder of the private key signed the message, and that it was not tampered with. 144 | 145 | ```php 146 | $signature = Spatie\Crypto\Rsa\PrivateKey::fromFile($pathToPrivateKey)->sign('my message'); // returns a string 147 | 148 | $publicKey = Spatie\Crypto\Rsa\PublicKey::fromFile($pathToPublicKey); 149 | 150 | $publicKey->verify('my message', $signature) // returns true; 151 | $publicKey->verify('my modified message', $signature) // returns false; 152 | ``` 153 | 154 | ## Alternatives 155 | 156 | This package aims to be very lightweight and easy to use. If you need more features, consider using of one these alternatives: 157 | 158 | - [paragonie/halite](https://github.com/paragonie/halite) 159 | - [vlucas/pikirasa](https://github.com/vlucas/pikirasa) 160 | - [laminas/crypt](https://docs.laminas.dev/laminas-crypt/) 161 | - [phpseclib/phpseclib](https://github.com/phpseclib/phpseclib) 162 | 163 | ## A word on the usage of RSA 164 | 165 | At the time of writing, RSA is secure enough for the use case we've built this package for. 166 | 167 | To know more about why RSA might not be good enough for you, read [this post on public-key encryption at Paragonie.com](https://paragonie.com/blog/2016/12/everything-you-know-about-public-key-encryption-in-php-is-wrong#php-openssl-rsa-bad-default) 168 | 169 | ## Testing 170 | 171 | ``` bash 172 | composer test 173 | ``` 174 | 175 | ## Changelog 176 | 177 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 178 | 179 | ## Contributing 180 | 181 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 182 | 183 | ## Security Vulnerabilities 184 | 185 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 186 | 187 | ## Credits 188 | 189 | - [Freek Van der Herten](https://github.com/freekmurze) 190 | - [All Contributors](../../contributors) 191 | 192 | ## License 193 | 194 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 195 | --------------------------------------------------------------------------------