Skip to content

Commit 24ab7f4

Browse files
committed
Add initphp/encryption package info, require PHP 8.1 in README.md
1 parent d06ac65 commit 24ab7f4

11 files changed

Lines changed: 1864 additions & 84 deletions

README.md

Lines changed: 193 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,132 +1,241 @@
1-
# Encryption
2-
PHP OpenSSL/Sodium Encryption and Decryption
1+
# initphp/encryption
32

4-
[![Latest Stable Version](http://poser.pugx.org/initphp/encryption/v)](https://packagist.org/packages/initphp/encryption) [![Total Downloads](http://poser.pugx.org/initphp/encryption/downloads)](https://packagist.org/packages/initphp/encryption) [![Latest Unstable Version](http://poser.pugx.org/initphp/encryption/v/unstable)](https://packagist.org/packages/initphp/encryption) [![License](http://poser.pugx.org/initphp/encryption/license)](https://packagist.org/packages/initphp/encryption) [![PHP Version Require](http://poser.pugx.org/initphp/encryption/require/php)](https://packagist.org/packages/initphp/encryption)
3+
Secure, modern symmetric encryption for PHP on top of OpenSSL and libsodium.
4+
5+
[![Latest Stable Version](https://poser.pugx.org/initphp/encryption/v)](https://packagist.org/packages/initphp/encryption)
6+
[![Total Downloads](https://poser.pugx.org/initphp/encryption/downloads)](https://packagist.org/packages/initphp/encryption)
7+
[![License](https://poser.pugx.org/initphp/encryption/license)](https://packagist.org/packages/initphp/encryption)
8+
[![PHP Version Require](https://poser.pugx.org/initphp/encryption/require/php)](https://packagist.org/packages/initphp/encryption)
9+
10+
## Why
11+
12+
PHP's encryption primitives are powerful but unforgiving. Pick the wrong cipher,
13+
mix up IV and HMAC ordering, forget constant-time comparison, or hand libsodium
14+
a 14-byte "key" and you ship a vulnerability — or, more often, a silent failure
15+
that "works on my machine".
16+
17+
This package wraps `ext-openssl` and `ext-sodium` behind a small, opinionated
18+
API:
19+
20+
- Authenticated by default. OpenSSL uses encrypt-then-MAC; Sodium uses the
21+
built-in AEAD construction.
22+
- Keys of any non-empty length are accepted and derived to the size the
23+
primitive actually requires.
24+
- Ciphertexts are self-describing: a 2-byte header (version + serializer flag)
25+
lets the library reject malformed or out-of-date input with a clear error
26+
instead of returning garbage.
27+
- A single `EncryptionException` covers every failure mode, so a `try` /
28+
`catch` is enough to handle all error paths.
29+
- JSON is the default payload serializer, so the historical
30+
`unserialize()`-on-attacker-controlled-bytes pitfall is closed by default.
531

632
## Requirements
733

8-
- PHP 7.4 or higher
9-
- MB_String extension
10-
- Depending on usage:
11-
- OpenSSL extesion
12-
- Sodium extension
34+
- PHP **8.1** or higher
35+
- `ext-openssl` for the OpenSSL handler
36+
- `ext-sodium` for the Sodium handler
1337

38+
Both extensions ship with mainstream PHP distributions, but the package only
39+
loads the one you actually instantiate — you can use one handler without the
40+
other being available.
1441

1542
## Installation
1643

17-
```
44+
```bash
1845
composer require initphp/encryption
1946
```
2047

21-
## Configuration
48+
## Quickstart
2249

23-
```php
24-
$options = [
25-
'algo' => 'SHA256',
26-
'cipher' => 'AES-256-CTR',
27-
'key' => null,
28-
'blocksize' => 16,
29-
];
30-
```
50+
```php
51+
<?php
3152

32-
- `algo` : Used by OpenSSL handler only. The algorithm to use to sign the data.
33-
- `cipher` : Used by OpenSSL handler only. The encryption algorithm that will be used to encrypt the data.
34-
- `key` : The top secret key string to use for encryption.
35-
- `blocksize` : It is used for sodium handler only. It is used in the `sodium_pad()` and `sodium_unpad()` functions.
53+
require __DIR__ . '/vendor/autoload.php';
3654

55+
use InitPHP\Encryption\Encrypt;
56+
use InitPHP\Encryption\OpenSSL;
3757

38-
## Usage
58+
$handler = Encrypt::use(OpenSSL::class, [
59+
'key' => getenv('APP_ENCRYPTION_KEY'),
60+
]);
3961

40-
```php
41-
require_once "vendor/autoload.php";
42-
use \InitPHP\Encryption\Encrypt;
62+
$ciphertext = $handler->encrypt(['user_id' => 42, 'role' => 'admin']);
63+
// → "02006f1c…": hex string, safe to store in cookies / DBs / URL params
4364

44-
// OpenSSL Handler
45-
/** @var $openssl \InitPHP\Encryption\HandlerInterface */
46-
$openssl = Encrypt::use(\InitPHP\Encryption\OpenSSL::class, [
47-
'algo' => 'SHA256',
48-
'cipher' => 'AES-256-CTR',
49-
'key' => 'TOP_Secret_Key',
50-
]);
65+
$plaintext = $handler->decrypt($ciphertext);
66+
// → ['user_id' => 42, 'role' => 'admin']
67+
```
68+
69+
The Sodium handler has the same surface:
70+
71+
```php
72+
use InitPHP\Encryption\Encrypt;
73+
use InitPHP\Encryption\Sodium;
5174

52-
// Sodium Handler
53-
/** @var $sodium \InitPHP\Encryption\HandlerInterface */
54-
$sodium = Encrypt::use(\InitPHP\Encryption\Sodium::class, [
55-
'key' => 'TOP_Secret_Key',
56-
'blocksize' => 16,
75+
$handler = Encrypt::use(Sodium::class, [
76+
'key' => getenv('APP_ENCRYPTION_KEY'),
5777
]);
78+
79+
$ciphertext = $handler->encrypt('a secret message');
80+
$plaintext = $handler->decrypt($ciphertext);
5881
```
5982

60-
### Methods
83+
## Configuration
6184

62-
#### `encrypt()`
85+
Every option is optional except `key`. Unknown keys are ignored. Keys are
86+
case-insensitive on input (`'CIPHER'` and `'cipher'` are the same option).
6387

64-
```php
65-
public function encrypt(mixed $data, array $options = []): string;
66-
```
88+
| Option | Used by | Default | Description |
89+
| ------------ | --------- | -------------- | ----------- |
90+
| `key` | both | _required_ | The user-supplied secret. Any non-empty string; the handler derives a key of the correct length internally. |
91+
| `cipher` | OpenSSL | `AES-256-CTR` | Any algorithm from `openssl_get_cipher_methods()`. |
92+
| `algo` | OpenSSL | `SHA256` | Any algorithm from `hash_hmac_algos()`. Used both for HKDF key derivation and for the HMAC tag. |
93+
| `blocksize` | Sodium | `16` | Block size for `sodium_pad()` / `sodium_unpad()`. Must be a positive integer. |
94+
| `serializer` | both | `'json'` | One of `'json'`, `'php_serialize'`, `'php'`, `'serialize'`. See [Serialization](#serialization). |
95+
96+
Options can be set in three places, in order of precedence (highest wins):
6797

68-
#### `decrypt()`
98+
```php
99+
// 1) Per-call override
100+
$handler->encrypt($data, ['cipher' => 'AES-256-GCM']);
69101

70-
```php
71-
public function decrypt(string $data, array $options = []): mixed;
102+
// 2) Mutated on the handler
103+
$handler->setOption('cipher', 'AES-256-GCM');
104+
$handler->setOptions(['cipher' => 'AES-256-GCM', 'algo' => 'SHA512']);
105+
106+
// 3) Constructor / factory
107+
$handler = Encrypt::use(OpenSSL::class, ['cipher' => 'AES-256-GCM']);
72108
```
73109

74-
## Writing Your Own Handler
110+
Per-call options do **not** mutate the handler — they are merged into a fresh
111+
array for that single call only.
75112

76-
```php
77-
namespace App;
113+
## Serialization
78114

79-
use \InitPHP\Encryption\{HandlerInterface, BaseHandler};
115+
`encrypt()` accepts `mixed` and round-trips the value through a serializer
116+
chosen via the `serializer` option. The flag is embedded in the ciphertext, so
117+
`decrypt()` always restores the original type without you having to track the
118+
choice yourself.
80119

81-
class MyHandler extends BaseHandler implements HandlerInterface
82-
{
83-
public function encrypt($data, array $options = []): string
84-
{
85-
$options = $this->options($options);
86-
// ... process
87-
}
120+
| `serializer` value | On-the-wire flag | Behaviour |
121+
| --- | --- | --- |
122+
| `'json'` (default) | `0x00` | Uses `json_encode`/`json_decode` with `JSON_THROW_ON_ERROR`. Safe: no PHP class is ever instantiated during decoding. Cannot carry raw binary bytes — use `php_serialize` if you need that. |
123+
| `'php_serialize'`, `'php'`, `'serialize'` | `0x01` | Uses `serialize()`/`unserialize()` with `['allowed_classes' => false]`. Round-trips scalars, arrays and binary strings; custom objects degrade to `__PHP_Incomplete_Class` on decode. |
88124

89-
public function decrypt($data, array $options = [])
90-
{
91-
$options = $this->options($options);
92-
// ... process
93-
}
94-
}
95-
```
125+
The PHP serializer is opt-in for one reason only: even though we always pass
126+
`allowed_classes:false`, the safer default lets you not have to think about
127+
object-injection vectors at all.
96128

97-
```php
98-
use \InitPHP\Encryption\Encrypt;
129+
## Writing a Custom Handler
99130

100-
$myhandler = Encrypt::use(\App\MyHandler::class);
101-
```
131+
Extend `BaseHandler` (not `OpenSSL` / `Sodium` — those are `final`) and
132+
implement `encrypt()` and `decrypt()`:
102133

103-
## Getting Help
134+
```php
135+
namespace App\Crypto;
104136

105-
If you have questions, concerns, bug reports, etc, please file an issue in this repository's Issue Tracker.
137+
use InitPHP\Encryption\BaseHandler;
138+
use InitPHP\Encryption\Exceptions\EncryptionException;
139+
140+
final class MyHandler extends BaseHandler
141+
{
142+
public function encrypt(mixed $data, array $options = []): string
143+
{
144+
$options = $this->resolveOptions($options);
145+
$key = $this->requireKey($options);
146+
$serializerFlag = $this->serializerFlag($options);
106147

107-
## Getting Involved
148+
$payload = $this->serializePayload($data, $serializerFlag);
149+
// ... apply your primitive of choice, return a hex-encoded string ...
150+
}
108151

109-
> All contributions to this project will be published under the MIT License. By submitting a pull request or filing a bug, issue, or feature request, you are agreeing to comply with this waiver of copyright interest.
152+
public function decrypt(string $data, array $options = []): mixed
153+
{
154+
$options = $this->resolveOptions($options);
155+
$key = $this->requireKey($options);
110156

111-
There are two primary ways to help:
157+
// ... reverse the encoding, return $this->unserializePayload($plain, $flag) ...
158+
}
159+
}
112160

113-
- Using the issue tracker, and
114-
- Changing the code-base.
115-
116-
### Using the issue tracker
161+
// Use it via the factory just like the built-in handlers:
162+
$handler = \InitPHP\Encryption\Encrypt::use(\App\Crypto\MyHandler::class, [
163+
'key' => 'secret',
164+
]);
165+
```
117166

118-
Use the issue tracker to suggest feature requests, report bugs, and ask questions. This is also a great way to connect with the developers of the project as well as others who are interested in this solution.
167+
`BaseHandler` gives you `resolveOptions()`, `requireKey()`,
168+
`serializerFlag()`, `serializePayload()` and `unserializePayload()` for free,
169+
so you only write the cryptographic glue.
119170

120-
Use the issue tracker to find ways to contribute. Find a bug or a feature, mention in the issue that you will take on that effort, then follow the Changing the code-base guidance below.
171+
## Error Handling
121172

122-
### Changing the code-base
173+
Every failure path raises `InitPHP\Encryption\Exceptions\EncryptionException`
174+
(which extends `\RuntimeException`). A single `catch` covers everything:
123175

124-
Generally speaking, you should fork this repository, make changes in your own fork, and then submit a pull request. All new code should have associated unit tests that validate implemented features and the presence or lack of defects. Additionally, the code should follow any stylistic and architectural guidelines prescribed by the project. In the absence of such guidelines, mimic the styles and patterns in the existing code-base.
176+
```php
177+
use InitPHP\Encryption\Exceptions\EncryptionException;
125178

126-
## Credits
179+
try {
180+
$plaintext = $handler->decrypt($incoming);
181+
} catch (EncryptionException $e) {
182+
// Bad input, tampered ciphertext, missing key, unsupported format
183+
// version, unknown cipher or hash algorithm, …
184+
$logger->warning('decrypt failed', ['reason' => $e->getMessage()]);
185+
}
186+
```
127187

128-
- [Muhammet ŞAFAK](https://www.muhammetsafak.com.tr)
188+
Notable messages you may see:
189+
190+
- `Unsupported ciphertext format version 0x01; expected 0x02. Ciphertexts produced by 1.x are not readable by 2.x.`
191+
- `HMAC verification failed; ciphertext is corrupted or has been tampered with.`
192+
- `Sodium decryption failed; ciphertext is corrupted or has been tampered with.`
193+
- `The "key" option is required and must be a non-empty string.`
194+
- `Unknown OpenSSL cipher "…".`
195+
196+
## Security Notes
197+
198+
- **Key management is your job.** Store the key outside the code repository —
199+
environment variable, secret manager, KMS, etc. — and rotate it like any
200+
other secret.
201+
- **Key strength matters.** The handler accepts any non-empty user key and
202+
derives one of the right length, but it cannot add entropy that the input
203+
does not contain. Use a random 256-bit string (`bin2hex(random_bytes(32))`)
204+
in production rather than a passphrase.
205+
- **Authentication is always on.** OpenSSL ciphertexts include an HMAC of the
206+
header, IV, and ciphertext; Sodium uses its built-in AEAD. There is no
207+
"encrypt without authenticate" mode.
208+
- **Format is versioned.** The first byte of every ciphertext identifies the
209+
format. A future major release that changes the layout will bump this byte
210+
and reject older ciphertexts with a clear error.
211+
- **Found something concerning?** See [SECURITY.md](./SECURITY.md) — please do
212+
not open a public issue for vulnerabilities.
213+
214+
## Upgrading from 1.x
215+
216+
Version 2.0 is a hard reset of the public surface and the on-wire format:
217+
218+
- Minimum PHP version is now **8.1**.
219+
- Ciphertexts produced by 1.x **cannot be decrypted by 2.x.** Plan a
220+
re-encryption migration before upgrading.
221+
- The default payload serializer is JSON (was `serialize`). Pass
222+
`'serializer' => 'php_serialize'` to keep the old behaviour.
223+
- `Encrypt::create()` has been removed; use `Encrypt::use()`.
224+
- The Sodium handler no longer requires a 32-byte key — any non-empty string
225+
is now accepted and derived internally.
226+
- `ext-mbstring` is no longer required.
227+
228+
A full migration walk-through lives in
229+
[`docs/08-migration-v1-to-v2.md`](./docs/08-migration-v1-to-v2.md) (shipped
230+
with the package; see also the [docs/](./docs/) index).
231+
232+
## Contributing
233+
234+
PRs are welcome. Please read [CONTRIBUTING.md](./CONTRIBUTING.md) first — it
235+
covers the local quality gates (`composer test`, `composer phpstan`,
236+
`composer cs-check`) and the security-review process for changes touching the
237+
cryptographic primitives.
129238

130239
## License
131240

132-
Copyright &copy; 2022 [MIT License](./LICENSE)
241+
MIT — see [LICENSE](./LICENSE).

0 commit comments

Comments
 (0)