Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
composer.phar
/vendor/
composer.lock

coverage-report/

.phpunit.result.cache
.idea
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
language: php

php:
- 7.1
- 7.2
- 7.3
- 8.1
- 8.2
- 8.3

env:
- COMPOSER_OPTS=""
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ $key = 'some-secret-password-here';

$inStream = new Stream(fopen('some-input-file', 'r')); // Any PSR-7 stream will be fine here
$cipherTextStream = new AesEncryptingStream($inStream, $key, $cipherMethod); // Wrap the stream in an EncryptingStream
$cipherTextFile = Psr7\stream_for(fopen('encrypted.file', 'w'));
$cipherTextFile = Psr7\Utils::streamFor(fopen('encrypted.file', 'w'));
Psr7\copy_to_stream($cipherTextStream, $cipherTextFile); // When you read from the encrypting stream, the data will be encrypted.

// You'll also need to store the IV somewhere, because we'll need it later to decrypt the data.
Expand Down Expand Up @@ -83,7 +83,7 @@ $cipherText = openssl_encrypt(
$expectedHash = hash('sha256', $cipherText);

$hashingDecorator = new Jsq\EncryptingStreams\HashingStream(
GuzzleHttp\Psr7\stream_for($cipherText),
GuzzleHttp\Psr7\Utils::streamFor($cipherText),
$key,
function ($hash) use ($expectedHash) {
if ($hash !== $expectedHash) {
Expand Down
11 changes: 7 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@
}
},
"require": {
"php": ">=7.1",
"php": ">=8.1",
"ext-openssl": "*",
"guzzlehttp/psr7": "~1.0",
"psr/http-message": "~1.0"
"guzzlehttp/psr7": "~2.6",
"psr/http-message": "~2.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0"
"phpunit/phpunit": "^10.0"
},
"scripts": {
"tests": "phpunit"
}
}
15 changes: 6 additions & 9 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./vendor/autoload.php"
colors="true">

<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="./vendor/autoload.php" colors="true"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd">
<testsuites>
<testsuite name="unit">
<directory suffix="Test.php">tests/</directory>
</testsuite>
</testsuites>

<filter>
<whitelist>
<source>
<include>
<directory suffix=".php">src/</directory>
</whitelist>
</filter>

</include>
</source>
</phpunit>
52 changes: 20 additions & 32 deletions src/AesDecryptingStream.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

namespace Jsq\EncryptionStreams;

use GuzzleHttp\Psr7\StreamDecoratorTrait;
Expand All @@ -7,46 +8,28 @@

class AesDecryptingStream implements StreamInterface
{
const BLOCK_SIZE = 16; // 128 bits
public const BLOCK_SIZE = 16; // 128 bits

use StreamDecoratorTrait;

/**
* @var string
*/
private $plainBuffer = '';

/**
* @var string
*/
private $cipherBuffer = '';
private string $plainBuffer = '';

/**
* @var CipherMethod
*/
private $cipherMethod;
private string $cipherBuffer = '';

/**
* @var string
*/
private $key;
private CipherMethod $cipherMethod;

/**
* @var StreamInterface
*/
private $stream;
private StreamInterface $stream;

public function __construct(
StreamInterface $cipherText,
string $key,
private readonly string $key,
CipherMethod $cipherMethod
) {
$this->stream = $cipherText;
$this->key = $key;
$this->cipherMethod = clone $cipherMethod;
}

public function eof()
public function eof(): bool
{
return $this->cipherBuffer === '' && $this->stream->eof();
}
Expand Down Expand Up @@ -75,14 +58,14 @@ public function read($length): string
{
if ($length > strlen($this->plainBuffer)) {
$this->plainBuffer .= $this->decryptBlock(
self::BLOCK_SIZE * ceil(($length - strlen($this->plainBuffer)) / self::BLOCK_SIZE)
self::BLOCK_SIZE * (int) ceil(($length - strlen($this->plainBuffer)) / self::BLOCK_SIZE)
);
}

$data = substr($this->plainBuffer, 0, $length);
$this->plainBuffer = substr($this->plainBuffer, $length);

return $data ? $data : '';
return $data ?: '';
}

public function seek($offset, $whence = SEEK_SET): void
Expand All @@ -93,8 +76,7 @@ public function seek($offset, $whence = SEEK_SET): void
$this->cipherMethod->seek(0, SEEK_SET);
$this->stream->seek(0, SEEK_SET);
} else {
throw new LogicException('AES encryption streams only support being'
. ' rewound, not arbitrary seeking.');
throw new LogicException('AES encryption streams only support being rewound, not arbitrary seeking.');
}
}

Expand Down Expand Up @@ -124,9 +106,15 @@ private function decryptBlock(int $length): string
);

if ($plaintext === false) {
throw new DecryptionFailedException("Unable to decrypt $cipherText with an initialization vector"
. " of {$this->cipherMethod->getCurrentIv()} using the {$this->cipherMethod->getOpenSslName()}"
. " algorithm. Please ensure you have provided the correct algorithm, initialization vector, and key.");
throw new DecryptionFailedException(
sprintf('Unable to decrypt %s with an initialization vector', $cipherText)
. sprintf(
' of %s using the %s',
$this->cipherMethod->getCurrentIv(),
$this->cipherMethod->getOpenSslName()
)
. " algorithm. Please ensure you have provided the correct algorithm, initialization vector, and key."
);
}

$this->cipherMethod->update($cipherText);
Expand Down
44 changes: 18 additions & 26 deletions src/AesEncryptingStream.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

namespace Jsq\EncryptionStreams;

use GuzzleHttp\Psr7\StreamDecoratorTrait;
Expand All @@ -7,45 +8,30 @@

class AesEncryptingStream implements StreamInterface
{
const BLOCK_SIZE = 16; // 128 bits
public const BLOCK_SIZE = 16; // 128 bits

use StreamDecoratorTrait;

/**
* @var string
*/
private $buffer = '';

/**
* @var CipherMethod
*/
private $cipherMethod;
private string $buffer = '';

/**
* @var string
*/
private $key;
private CipherMethod $cipherMethod;

/**
* @var StreamInterface
*/
private $stream;
private StreamInterface $stream;

public function __construct(
StreamInterface $plainText,
string $key,
private readonly string $key,
CipherMethod $cipherMethod
) {
$this->stream = $plainText;
$this->key = $key;
$this->cipherMethod = clone $cipherMethod;
}

public function getSize(): ?int
{
$plainTextSize = $this->stream->getSize();

if ($this->cipherMethod->requiresPadding() && $plainTextSize !== null) {
if ($plainTextSize !== null && $this->cipherMethod->requiresPadding()) {
// PKCS7 padding requires that between 1 and self::BLOCK_SIZE be
// added to the plaintext to make it an even number of blocks.
$padding = self::BLOCK_SIZE - $plainTextSize % self::BLOCK_SIZE;
Expand All @@ -71,7 +57,7 @@ public function read($length): string
$data = substr($this->buffer, 0, $length);
$this->buffer = substr($this->buffer, $length);

return $data ? $data : '';
return $data ?: '';
}

public function seek($offset, $whence = SEEK_SET): void
Expand All @@ -84,7 +70,7 @@ public function seek($offset, $whence = SEEK_SET): void
if ($whence === SEEK_SET) {
$this->buffer = '';
$wholeBlockOffset
= (int) ($offset / self::BLOCK_SIZE) * self::BLOCK_SIZE;
= (int)($offset / self::BLOCK_SIZE) * self::BLOCK_SIZE;
$this->stream->seek($wholeBlockOffset);
$this->cipherMethod->seek($wholeBlockOffset);
$this->read($offset - $wholeBlockOffset);
Expand Down Expand Up @@ -118,9 +104,15 @@ private function encryptBlock(int $length): string
);

if ($cipherText === false) {
throw new EncryptionFailedException("Unable to encrypt data with an initialization vector"
. " of {$this->cipherMethod->getCurrentIv()} using the {$this->cipherMethod->getOpenSslName()}"
. " algorithm. Please ensure you have provided a valid algorithm and initialization vector.");
throw new EncryptionFailedException(
"Unable to encrypt data with an initialization vector"
. sprintf(
' of %s using the %s',
$this->cipherMethod->getCurrentIv(),
$this->cipherMethod->getOpenSslName()
)
. " algorithm. Please ensure you have provided a valid algorithm and initialization vector."
);
}

$this->cipherMethod->update($cipherText);
Expand Down
53 changes: 18 additions & 35 deletions src/AesGcmDecryptingStream.php
Original file line number Diff line number Diff line change
@@ -1,51 +1,32 @@
<?php

namespace Jsq\EncryptionStreams;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\StreamDecoratorTrait;
use GuzzleHttp\Psr7\Utils;
use Psr\Http\Message\StreamInterface;

#[\AllowDynamicProperties]
class AesGcmDecryptingStream implements StreamInterface
{
use StreamDecoratorTrait;

private $aad;

private $initializationVector;

private $key;

private $keySize;

private $cipherText;

private $tag;

private $tagLength;

public function __construct(
StreamInterface $cipherText,
string $key,
string $initializationVector,
string $tag,
string $aad = '',
int $tagLength = 16,
int $keySize = 256
private readonly StreamInterface $cipherText,
private readonly string $key,
private readonly string $initializationVector,
private readonly string $tag,
private readonly string $aad = '',
private readonly int $tagLength = 16,
private readonly int $keySize = 256
) {
$this->cipherText = $cipherText;
$this->key = $key;
$this->initializationVector = $initializationVector;
$this->tag = $tag;
$this->aad = $aad;
$this->tagLength = $tagLength;
$this->keySize = $keySize;
}

public function createStream(): StreamInterface
{
$plaintext = openssl_decrypt(
(string) $this->cipherText,
"aes-{$this->keySize}-gcm",
(string)$this->cipherText,
sprintf('aes-%d-gcm', $this->keySize),
$this->key,
OPENSSL_RAW_DATA,
$this->initializationVector,
Expand All @@ -54,12 +35,14 @@ public function createStream(): StreamInterface
);

if ($plaintext === false) {
throw new DecryptionFailedException("Unable to decrypt data with an initialization vector"
. " of {$this->initializationVector} using the aes-{$this->keySize}-gcm algorithm. Please"
. " ensure you have provided a valid key size, initialization vector, and key.");
throw new DecryptionFailedException(
"Unable to decrypt data with an initialization vector"
. sprintf(' of %s using the aes-%d-gcm algorithm. Please', $this->initializationVector, $this->keySize)
. " ensure you have provided a valid key size, initialization vector, and key."
);
}

return Psr7\stream_for($plaintext);
return Utils::streamFor($plaintext);
}

public function isWritable(): bool
Expand Down
Loading