Skip to content

Add modern cryptors#71

Open
olegbaturin wants to merge 39 commits into
yiisoft:masterfrom
olegbaturin:add-cryptor
Open

Add modern cryptors#71
olegbaturin wants to merge 39 commits into
yiisoft:masterfrom
olegbaturin:add-cryptor

Conversation

@olegbaturin
Copy link
Copy Markdown
Contributor

Q A
Is bugfix?
New feature? ✔️
Breaks BC?

@codecov
Copy link
Copy Markdown

codecov Bot commented May 16, 2026

Codecov Report

❌ Patch coverage is 96.59091% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.84%. Comparing base (a0bbbe3) to head (2ad87b1).

Files with missing lines Patch % Lines
src/Crypt/Cipher/OpenSSLAeadCipher.php 91.17% 3 Missing ⚠️
src/Crypt/Cipher/SodiumAeadCipher.php 94.11% 2 Missing ⚠️
src/Crypt/Kdf/KdfPasswordArgon2.php 91.66% 1 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##              master      #71      +/-   ##
=============================================
- Coverage     100.00%   97.84%   -2.16%     
- Complexity        37      101      +64     
=============================================
  Files              7       15       +8     
  Lines            102      278     +176     
=============================================
+ Hits             102      272     +170     
- Misses             0        6       +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread src/PasswordHasher.php
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a new “modern” AEAD-based encryption layer to the package (new cipher/KDF abstractions plus high-level cryptors), along with documentation, CI updates, and a small PasswordHasher adjustment.

Changes:

  • Added AEAD cipher interfaces and implementations for OpenSSL (AES-GCM) and libsodium (AES-256-GCM/ChaCha20-Poly1305/XChaCha20-Poly1305).
  • Added KDF abstractions/implementations (HKDF-based key derivation, plus PBKDF2+HKDF for passwords) and high-level cryptors (Session, Envelope, Versioned).
  • Added PHPUnit coverage for new cryptors/ciphers/KDFs and updated README/CI/Composer metadata accordingly.

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/Crypt/CipherInterface.php Introduces base cipher abstraction used by new cryptors.
src/Crypt/AeadCipherInterface.php Adds AEAD-specific cipher contract (tag size).
src/Crypt/Cipher/OpenSSLAeadCipher.php OpenSSL-backed AES-GCM AEAD cipher implementation.
src/Crypt/Cipher/SodiumAeadCipher.php libsodium-backed AEAD cipher implementation (AES-GCM/ChaCha variants).
src/Crypt/KdfInterface.php Introduces key-derivation abstraction used by cryptors.
src/Crypt/Kdf/KdfKey.php HKDF-based KDF for high-entropy secrets.
src/Crypt/Kdf/KdfPassword.php PBKDF2+HKDF-based KDF for passwords.
src/Crypt/CryptorInterface.php Defines high-level encrypt/decrypt contract for new cryptors.
src/Crypt/SessionCryptor.php Implements per-message derived-key “session” encryption format.
src/Crypt/EnvelopeCryptor.php Implements envelope encryption (wrapped DEK) format.
src/Crypt/VersionedCryptor.php Adds version-prefix wrapper to support cryptor migrations.
src/Crypt/EncryptionException.php Introduces a dedicated exception type for encryption/decryption failures.
tests/Crypt/AbstractAeadCipherCase.php Shared test suite for AEAD cipher implementations.
tests/Crypt/OpenSSLAeadCipherTest.php OpenSSL AEAD cipher tests.
tests/Crypt/SodiumAeadCipherTest.php libsodium ChaCha/XChaCha AEAD cipher tests.
tests/Crypt/SodiumGcmCipherTest.php libsodium AES-256-GCM AEAD cipher tests (hardware dependent).
tests/Crypt/AbstractKdfCase.php Shared test suite for KDF implementations.
tests/Crypt/KdfKeyTest.php HKDF KDF tests.
tests/Crypt/KdfPasswordTest.php PBKDF2+HKDF KDF tests.
tests/Crypt/SessionCryptorTest.php Session cryptor unit tests.
tests/Crypt/EnvelopeCryptorTest.php Envelope cryptor unit tests.
tests/Crypt/VersionedCryptorTest.php Versioned cryptor unit tests.
README.md Documents the new cryptors/KDFs/ciphers and marks legacy cryptor as “old”.
composer.json Adds ext-sodium requirement.
.github/workflows/build.yml Enables sodium extension in CI.
psalm.xml Adjusts Psalm configuration.
CHANGELOG.md Notes the new cryptors in the unreleased version section.
src/PasswordHasher.php Adjusts SAFE_PARAMETERS lookup when algorithm is null.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread composer.json
Comment on lines 36 to 41
"require": {
"php": "8.1 - 8.5",
"ext-hash": "*",
"ext-openssl": "*",
"ext-sodium": "*",
"yiisoft/strings": "^2.0"
Comment thread README.md
Comment on lines +133 to +134
`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 thread README.md Outdated
Comment thread src/Crypt/Kdf/KdfPassword.php Outdated
Comment thread src/Crypt/VersionedCryptor.php
Comment thread tests/Crypt/AbstractAeadCipherCase.php
Comment thread tests/Crypt/AbstractAeadCipherCase.php
Comment thread tests/Crypt/EnvelopeCryptorTest.php
Comment thread tests/Crypt/EnvelopeCryptorTest.php
Comment thread tests/Crypt/VersionedCryptorTest.php Outdated
Copy link
Copy Markdown
Member

@samdark samdark left a comment

Choose a reason for hiding this comment

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

Not enough negative/security tests around metadata tampering: version byte changes, salt/nonce field mutation, truncated payloads at every boundary, truncated tags, empty plaintext, wrong context, and cross-version/cross-cryptor confusion.

Comment on lines +111 to +114
$tag = StringHelper::byteSubstring($data, -self::TAG_SIZE);
$ciphertext = StringHelper::byteSubstring($data, 0, -self::TAG_SIZE);

$decrypted = openssl_decrypt($ciphertext, $this->cipher, $key, OPENSSL_RAW_DATA | OPENSSL_DONT_ZERO_PAD_KEY, $nonce, $tag);
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.

Truncated GCM tags issue. If $data is shorter than TAG_SIZE, we pass a 1-15 byte tag to openssl_decrypt(), and OpenSSL authenticates only the supplied tag bytes. That makes empty-plaintext forgery possible. One can search, for example, a 1-byte tag succeeds in about 256 attempts. Reject $data shorter than self::TAG_SIZE before slicing, and add tests for empty plaintext plus truncated tags.

Comment thread composer.json
"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.

* @throws RuntimeException If sodium extension is missing, cipher not allowed, or AES-256-GCM without hardware support.
*/
public function __construct(
private readonly string $cipher = 'AES-256-GCM',
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.

Is it a good default since there could be no hardware support? In the README, the cipher is not configured, so the following examples will result in an exception.

How about choosing default such as XChaCha20-Poly1305-IETF?

Comment thread README.md

```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?

Comment thread README.md

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?

): string {
$payload = $this->cryptors[$this->currentVersion]->encrypt($data, $secret, $context);

return $this->currentVersion . $payload;
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.

Metadata is not being authenticated. Shouldn't it?

$dataEncrypted = $this->cipher->encrypt($data, $dek, $dataNonce);

// keySalt || dekNonce || cipherdek || tag || dataNonce || ciphertext || tag
return $keySalt . $dekNonce . $dekEncrypted . $dataNonce . $dataEncrypted;
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.

Metadata is not authenticated. Shouldn't it?

string $salt,
): string {
try {
$key = sodium_crypto_pwhash($keySize, $secret, $salt, $this->opslimit, $this->memlimit, $this->algo);
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.

Suggested change
$key = sodium_crypto_pwhash($keySize, $secret, $salt, $this->opslimit, $this->memlimit, $this->algo);
$key = sodium_crypto_pwhash(32, $secret, $salt, $this->opslimit, $this->memlimit, $this->algo);

This size should not depend on algo.

string $salt,
): string {
try {
$key = hash_pbkdf2($this->hashAlgo, $secret, $salt, $this->iterations, $keySize, true);
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.

Suggested change
$key = hash_pbkdf2($this->hashAlgo, $secret, $salt, $this->iterations, $keySize, true);
$key = hash_pbkdf2($this->hashAlgo, $secret, $salt, $this->iterations, 32, true);

This size should not depend on algo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants