Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
917f747
add cryptor
olegbaturin May 11, 2026
6156063
fix style
olegbaturin May 11, 2026
c5bd2ac
add readonly
olegbaturin May 11, 2026
90205a7
update doc
olegbaturin May 11, 2026
be74463
add kdf doc
olegbaturin May 11, 2026
1e2dbd0
add doc to the cryptors
olegbaturin May 11, 2026
395e497
replace mb_substr with StringHelper
olegbaturin May 11, 2026
74ab9ee
replace mb_strlen
olegbaturin May 11, 2026
ab88999
update config
olegbaturin May 11, 2026
e3d033e
fix config
olegbaturin May 11, 2026
f3371b9
update envelope cryptor
olegbaturin May 13, 2026
024ab0f
add copher tests
olegbaturin May 14, 2026
a9b2fed
add kdf tests
olegbaturin May 15, 2026
e3b42cc
fix psalm
olegbaturin May 15, 2026
1ada6d7
add versioned test
olegbaturin May 16, 2026
e30b789
remove config
olegbaturin May 16, 2026
c31e2e2
update changelog
olegbaturin May 16, 2026
14c6555
fix style
olegbaturin May 16, 2026
5a261fc
fix style
olegbaturin May 16, 2026
7469377
fix style
olegbaturin May 16, 2026
b53adc4
fix style
olegbaturin May 16, 2026
dd005ec
fix psalm
olegbaturin May 16, 2026
ef21a64
update tests
olegbaturin May 16, 2026
b2cb9da
update composer.json
olegbaturin May 16, 2026
ac79a08
update ci
olegbaturin May 16, 2026
bf24623
update ci
olegbaturin May 16, 2026
8b64a32
update ci
olegbaturin May 16, 2026
1001c88
update phpdoc
olegbaturin May 17, 2026
2750e71
update readme
olegbaturin May 17, 2026
d574ca8
add OPENSSL_DONT_ZERO_PAD_KEY to gcp cipher
olegbaturin May 20, 2026
2dee4ae
fix copilot suggestions
olegbaturin May 23, 2026
bad4df8
fix style
olegbaturin May 23, 2026
d49ad96
update phpdoc
olegbaturin May 23, 2026
c7b0247
add saltSize to kdf interface
olegbaturin May 25, 2026
7e29630
fix psalm
olegbaturin May 25, 2026
b638977
update phpdoc
olegbaturin May 25, 2026
4043010
fix misprint
olegbaturin May 25, 2026
2345581
update psalm for ciphers without nounce
olegbaturin May 25, 2026
2ad87b1
Merge branch 'master' into add-cryptor
samdark May 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ jobs:
['ubuntu-latest', 'windows-latest']
php: >-
['8.1', '8.2', '8.3', '8.4', '8.5']
extensions: sodium
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 1.2.1 under development

- New #71: Add Session, Envelope and Versioned Cryptors (@olegbaturin)
- Bug #72: Fix possibly null offset in `PasswordHasher` (@olegbaturin)

## 1.2.0 November 25, 2025
Expand Down
248 changes: 208 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Security package provides a set of classes to handle common security-related tas
- PHP 8.1 - 8.5.
- `hash` PHP extension.
- `openssl` PHP extension.
- `sodium` PHP extension.

## Installation

Expand Down Expand Up @@ -71,46 +72,6 @@ $hash = getHash();
$result = (new PasswordHasher())->validate($password, $hash);
```

### Encryption and decryption by password

Encrypting data:

```php
$encryptedData = (new Crypt())->encryptByPassword($data, $password);

// save data to database or another storage
saveData($encryptedData);
```

Decrypting it:

```php
// obtain encrypted data from database or another storage
$encryptedData = getEncryptedData();

$data = (new Crypt())->decryptByPassword($encryptedData, $password);
```

### Encryption and decryption by key

Encrypting data:

```php
$encryptedData = (new Crypt())->encryptByKey($data, $key);

// save data to database or another storage
saveData($encryptedData);
```

Decrypting it:

```php
// obtain encrypted data from database or another storage
$encryptedData = getEncryptedData();

$data = (new Crypt())->decryptByKey($encryptedData, $key);
```

### Data tampering prevention

MAC signing could be used in order to prevent data tampering. The `$key` should be present at both sending and receiving
Expand Down Expand Up @@ -167,6 +128,213 @@ There is a special function in PHP that compares strings in a constant time:
hash_equals($expected, $actual);
```

## New cryptor

`Crypt` provides encryption layer based on `AEAD` algorithms.
It supports key derivation, session‑oriented encryption, envelope encryption, and versioned ciphertexts for seamless algorithm migration.
Comment on lines +133 to +134

All high‑level encryptors implement the `CryptorInterface`. Inject the desired cryptor (`SessionCryptor`, `EnvelopeCryptor` or `VersionedCryptor`) and use it as follows:

```php
use Yiisoft\Security\Crypt\CryptorInterface;

$cryptor = $container->get(CryptorInterface::class);
/** @var high‑entropy key or low‑entropy password */
$secret;
/** @var Optional application‑specific string that is mixed into the KDF */
$context;

$encrypted = $cryptor->encrypt('secret data', $secret, $context);
$data = $cryptor->decrypt($encrypted, $secret, $context);
```

### Session cryptor

Session‑oriented encryption (single key derived per message, no key wrapping).
A fresh data encryption key (DEK) is derived from the secret and a random salt.

Structure:
```
keySalt || nonce || encrypted(data) + tag
```

DI Configuration:
```php
// /config/di.php
use Yiisoft\Security\Crypt\SessionCryptor;
use Yiisoft\Security\Crypt\Cipher\SodiumAeadCipher;
use Yiisoft\Security\Crypt\Kdf\KdfKey;

SessionCryptor::class => [
'__construct()' => [
'cipher' => Reference::to(SodiumAeadCipher::class),
'kdf' => Reference::to(KdfKey::class),
],
],
```

### Envelope cryptor

Envelope encryption (key wrapping) using a KDF to derive a Key Encryption Key (KEK)
and a random Data Encryption Key (DEK). The DEK is encrypted with the KEK and stored
together with the ciphertext.

Structure:
```
keySalt || dekNonce || encrypted(DEK) + tag || dataNonce || encrypted(data) + tag
```

DI Configuration:
```php
// /config/di.php
use Yiisoft\Security\Crypt\EnvelopeCryptor;
use Yiisoft\Security\Crypt\Cipher\SodiumAeadCipher;
use Yiisoft\Security\Crypt\Kdf\KdfKey;

EnvelopeCryptor::class => [
'__construct()' => [
'cipher' => Reference::to(SodiumAeadCipher::class),
'kdf' => Reference::to(KdfKey::class),
],
],
```


### Versioned cryptor

Wraps multiple cryptors and adds a fixed‑length version prefix to every ciphertext.

DI Configuration:
```php
// /config/di.php
use Yiisoft\Security\Crypt\VersionedCryptor;
use Yiisoft\Security\Crypt\SessionCryptor;
use Yiisoft\Security\Crypt\EnvelopeCryptor;

VersionedCryptor::class => [
'__construct()' => [
'cryptors' => ReferencesArray::from([
chr(0x01) => SessionCryptor::class,
chr(0x96) => EnvelopeCryptor::class,
]),
'currentVersion' => chr(0x01),
'versionSize' => 1
],
],
```

### Configure KDF

The KDF is responsible for deriving cryptographic keys from the provided secret. Choose the appropriate KDF based on the type of secret.

#### KdfKey - for high‑entropy keys
Use this when the secret is already a strong cryptographic key (e.g. a 256‑bit random value). It applies `HKDF` directly.

```php
// /config/di.php
use Yiisoft\Security\Crypt\Kdf\KdfKey;

KdfKey::class => [
'__construct()' => [
'algorithm' => 'sha512', // any hash_hmac_algos()
],
],
```

#### KdfPassword - for low‑entropy passwords
This first applies `PBKDF2` with a configurable iteration count, then `HKDF` to derive the final key.
Follow OWASP recommendations for iteration counts.

```php
// /config/di.php
use Yiisoft\Security\Crypt\Kdf\KdfPassword;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

KdfPasswordPbkdf2 / KdfPasswordArgon2?


KdfPassword::class => [
'__construct()' => [
'algorithm' => 'sha512', // any hash_hmac_algos()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hashAlgo?

'iterations' => 700_000,
],
],
```

### Configuring AEAD Ciphers

Two backends are available: `OpenSSL` and `Sodium` (libsodium).

#### OpenSSLAeadCipher
Supports `AES‑GCM` family.

```php
// /config/di.php
use Yiisoft\Security\Crypt\Cipher\OpenSSLAeadCipher;

OpenSSLAeadCipher::class => [
'__construct()' => [
'cipher' => 'AES-192-GCM',
],
],
```

#### SodiumAeadCipher
Supports `AES‑256‑GCM` (hardware accelerated), `ChaCha20‑Poly1305‑IETF`, and `XChaCha20‑Poly1305‑IETF`.
Note: `AES‑256‑GCM` with `Sodium` requires CPU support for AES instructions (`AES‑NI`). Use `ChaCha20‑Poly1305‑IETF` for a safe, non‑hardware‑dependent alternative.

```php
// /config/di.php
use Yiisoft\Security\Crypt\Cipher\SodiumAeadCipher;

SodiumAeadCipher::class => [
'__construct()' => [
'cipher' => 'ChaCha20-Poly1305-IETF',
],
],
```

## Old cryptor

Note: This is the legacy encryption component based on `CBC` mode + `HMAC`.
For new projects, prefer the AEAD‑based cryptors (`AES‑GCM`, `ChaCha20‑Poly1305`) which provide authenticated encryption in a single step and are less error‑prone.

### Encryption and decryption by password

Encrypting data:

```php
$encryptedData = (new Crypt())->encryptByPassword($data, $password);

// save data to database or another storage
saveData($encryptedData);
```

Decrypting it:

```php
// obtain encrypted data from database or another storage
$encryptedData = getEncryptedData();

$data = (new Crypt())->decryptByPassword($encryptedData, $password);
```

### Encryption and decryption by key

Encrypting data:

```php
$encryptedData = (new Crypt())->encryptByKey($data, $key);

// save data to database or another storage
saveData($encryptedData);
```

Decrypting it:

```php
// obtain encrypted data from database or another storage
$encryptedData = getEncryptedData();

$data = (new Crypt())->decryptByKey($encryptedData, $key);
```

## Documentation

- [Internals](docs/internals.md)
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"php": "8.1 - 8.5",
"ext-hash": "*",
"ext-openssl": "*",
"ext-sodium": "*",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move to suggests.

"yiisoft/strings": "^2.0"
},
"require-dev": {
Expand Down
1 change: 1 addition & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
errorLevel="1"
findUnusedBaselineEntry="true"
findUnusedCode="false"
ensureOverrideAttribute="false"
strictBinaryOperands="false"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
Expand Down
18 changes: 18 additions & 0 deletions src/Crypt/AeadCipherInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Security\Crypt;

/**
* Interface for authenticated encryption with associated data (AEAD) ciphers.
*/
interface AeadCipherInterface extends CipherInterface
{
/**
* @return int Tag size in bytes.
*
* @psalm-return int<1, max>
*/
public function getTagSize(): int;
}
Loading
Loading