frista28 / stream-crypto-psr7
PSR-7 stream decorators for WhatsApp-like media encryption
Requires
- php: ^8.2
- ext-openssl: *
- guzzlehttp/psr7: ^2.9
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.94
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.5
README
PSR-7 stream decorators for WhatsApp-like media encryption.
The package provides:
Frista28\StreamCryptoPsr7\Stream\EncryptingStreamfor PSR-7 stream encryptionFrista28\StreamCryptoPsr7\Stream\DecryptingStreamfor PSR-7 stream decryption with MAC validationFrista28\StreamCryptoPsr7\Crypto\MediaCryptofor lower-level string and stream APIsFrista28\StreamCryptoPsr7\Crypto\MediaTypefor media-specific HKDF context selection
The crypto core processes source streams in chunks instead of loading the full payload into a single PHP string. The decorators materialize the transformed result into a seekable temporary stream on first read, so the exposed PSR-7 stream remains rewindable and seekable after transformation.
Requirements
- PHP 8.2 or newer
ext-openssl
Installation
composer require frista28/stream-crypto-psr7
Usage
Stream Decorators
<?php use Frista28\StreamCryptoPsr7\Crypto\MediaType; use Frista28\StreamCryptoPsr7\Stream\DecryptingStream; use Frista28\StreamCryptoPsr7\Stream\EncryptingStream; use GuzzleHttp\Psr7\Utils; $mediaKey = random_bytes(32); $plainStream = Utils::streamFor('hello'); $encryptingStream = new EncryptingStream($plainStream, $mediaKey, MediaType::DOCUMENT); $encryptedPayload = (string) $encryptingStream; $decryptingStream = new DecryptingStream( Utils::streamFor($encryptedPayload), $mediaKey, MediaType::DOCUMENT, ); $decryptedPayload = (string) $decryptingStream; // hello
Low-Level Crypto API
MediaCrypto also exposes direct string and stream APIs when you do not need the decorators:
<?php use Frista28\StreamCryptoPsr7\Crypto\MediaCrypto; use Frista28\StreamCryptoPsr7\Crypto\MediaType; use GuzzleHttp\Psr7\Utils; $crypto = new MediaCrypto(); $mediaKey = random_bytes(32); $encrypted = $crypto->encrypt('hello', $mediaKey, MediaType::DOCUMENT); $decrypted = $crypto->decrypt($encrypted, $mediaKey, MediaType::DOCUMENT); $encryptedStream = $crypto->encryptStream( Utils::streamFor('hello'), $mediaKey, MediaType::DOCUMENT, );
Sidecar Generation
For streamable media types (VIDEO and AUDIO), MediaCrypto can generate WhatsApp-compatible sidecar metadata in
the same encryption pass without rereading the plaintext source stream:
<?php use Frista28\StreamCryptoPsr7\Crypto\MediaCrypto; use Frista28\StreamCryptoPsr7\Crypto\MediaType; use GuzzleHttp\Psr7\Utils; $crypto = new MediaCrypto(); $mediaKey = random_bytes(32); [ 'encryptedStream' => $encryptedStream, 'sidecar' => $sidecar, ] = $crypto->encryptStreamWithSidecar( Utils::streamFor(fopen('video.mp4', 'rb')), $mediaKey, MediaType::VIDEO, );
The returned sidecar is a binary string that should be stored alongside the encrypted media object and used as
streaming metadata for chunk validation.
The library currently implements full-stream encryption/decryption and sidecar generation. Chunk-level validation and offset-based partial decryption are intentionally outside the current API surface.
Quality Checks
Run the full local quality gate:
composer check
Run individual tools:
composer stan
composer cs:check
composer cs:fix
composer test
Development
The project targets PHP 8.2 and uses:
- PHPUnit for tests
- PHPStan for static analysis
- PHP CS Fixer for code style