Skip to content

Commit 1be9d96

Browse files
committed
update cryptographer api
1 parent d64c0ee commit 1be9d96

10 files changed

Lines changed: 814 additions & 620 deletions

src/Attribute/SensitiveData.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#[Attribute(Attribute::TARGET_PROPERTY)]
1111
final class SensitiveData
1212
{
13-
/** @var (callable(string, mixed):mixed)|null */
13+
/** @var (callable(string):mixed)|null */
1414
public readonly mixed $fallbackCallable;
1515

1616
public function __construct(
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
3+
namespace Patchlevel\Hydrator\Cryptography;
4+
5+
use Patchlevel\Hydrator\Cryptography\Cipher\Cipher;
6+
use Patchlevel\Hydrator\Cryptography\Cipher\CipherKey;
7+
use Patchlevel\Hydrator\Cryptography\Cipher\CipherKeyFactory;
8+
use Patchlevel\Hydrator\Cryptography\Cipher\DecryptionFailed;
9+
use Patchlevel\Hydrator\Cryptography\Cipher\EncryptionFailed;
10+
use Patchlevel\Hydrator\Cryptography\Cipher\OpensslCipher;
11+
use Patchlevel\Hydrator\Cryptography\Cipher\OpensslCipherKeyFactory;
12+
use Patchlevel\Hydrator\Cryptography\Store\CipherKeyNotExists;
13+
use Patchlevel\Hydrator\Cryptography\Store\CipherKeyStore;
14+
15+
/**
16+
* @phpstan-type EncryptedDataV1 array{
17+
* __enc: 'v1',
18+
* data: non-empty-string,
19+
* method?: non-empty-string,
20+
* iv?: non-empty-string,
21+
* }
22+
*/
23+
final class BaseCryptographer implements Cryptographer
24+
{
25+
public function __construct(
26+
private readonly Cipher $cipher,
27+
private readonly CipherKeyStore $cipherKeyStore,
28+
private readonly CipherKeyFactory $cipherKeyFactory,
29+
)
30+
{
31+
}
32+
33+
/**
34+
* @throws EncryptionFailed
35+
*
36+
* @return EncryptedDataV1
37+
*/
38+
public function encrypt(string $subjectId, mixed $value): array
39+
{
40+
try {
41+
$cipherKey = $this->cipherKeyStore->get($subjectId);
42+
} catch (CipherKeyNotExists) {
43+
$cipherKey = ($this->cipherKeyFactory)();
44+
$this->cipherKeyStore->store($subjectId, $cipherKey);
45+
}
46+
47+
return [
48+
'__enc' => 'v1',
49+
'data' => $this->cipher->encrypt($cipherKey, $value),
50+
'method' => $cipherKey->method,
51+
'iv' => $cipherKey->iv,
52+
];
53+
}
54+
55+
/**
56+
* @param EncryptedDataV1 $encryptedData
57+
*
58+
* @throws CipherKeyNotExists
59+
* @throws DecryptionFailed
60+
*/
61+
public function decrypt(string $subjectId, mixed $encryptedData): mixed
62+
{
63+
$cipherKey = $this->cipherKeyStore->get($subjectId);
64+
65+
return $this->cipher->decrypt(
66+
new CipherKey(
67+
$cipherKey->key,
68+
$encryptedData['method'] ?? $cipherKey->method,
69+
$encryptedData['iv'] ?? $cipherKey->iv
70+
),
71+
$encryptedData['data']
72+
);
73+
}
74+
75+
public function supports(mixed $value): bool
76+
{
77+
return is_array($value) && array_key_exists('__enc', $value) && $value['__enc'] === 'v1';
78+
}
79+
80+
/** @param non-empty-string $method */
81+
public static function createWithOpenssl(
82+
CipherKeyStore $cryptoStore,
83+
string $method = OpensslCipherKeyFactory::DEFAULT_METHOD,
84+
): static {
85+
return new self(
86+
new OpensslCipher(),
87+
$cryptoStore,
88+
new OpensslCipherKeyFactory($method),
89+
);
90+
}
91+
}

src/Cryptography/Cipher/Cipher.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66

77
interface Cipher
88
{
9-
/** @throws EncryptionFailed */
9+
/**
10+
* @return non-empty-string
11+
*
12+
* @throws EncryptionFailed
13+
*/
1014
public function encrypt(CipherKey $key, mixed $data): string;
1115

1216
/** @throws DecryptionFailed */

src/Cryptography/Cipher/OpensslCipher.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
final class OpensslCipher implements Cipher
1919
{
20+
/**
21+
* @return non-empty-string
22+
*/
2023
public function encrypt(CipherKey $key, mixed $data): string
2124
{
2225
$encryptedData = @openssl_encrypt(

src/Cryptography/CryptoNormalizer.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,7 @@ public function normalize(mixed $value, array $context = []): mixed
3434
return null;
3535
}
3636

37-
$subjectId = $context[SubjectIds::class]->get($this->subjectIdName);
38-
39-
return ['__enc' => $this->cryptographer->encrypt($subjectId, $value)];
37+
return $this->cryptographer->encrypt($context[SubjectIds::class]->get($this->subjectIdName), $value);
4038
}
4139

4240
/**
@@ -46,7 +44,7 @@ public function normalize(mixed $value, array $context = []): mixed
4644
*/
4745
public function denormalize(mixed $value, array $context = []): mixed
4846
{
49-
if (!is_array($value) || !array_key_exists('__enc', $value)) {
47+
if (!$this->cryptographer->supports($value)) {
5048
if ($this->normalizer === null) {
5149
return $value;
5250
}
@@ -57,10 +55,10 @@ public function denormalize(mixed $value, array $context = []): mixed
5755
$subjectId = $context[SubjectIds::class]->get($this->subjectIdName);
5856

5957
try {
60-
$data = $this->cryptographer->decrypt($subjectId, $value['__enc']);
58+
$data = $this->cryptographer->decrypt($subjectId, $value);
6159
} catch (DecryptionFailed|CipherKeyNotExists) {
6260
if ($this->fallback instanceof Closure) {
63-
return ($this->fallback)($subjectId, $value['__enc']);
61+
return ($this->fallback)($subjectId);
6462
}
6563

6664
return $this->fallback;

src/Cryptography/Cryptographer.php

Lines changed: 4 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,61 +2,21 @@
22

33
namespace Patchlevel\Hydrator\Cryptography;
44

5-
use Patchlevel\Hydrator\Cryptography\Cipher\Cipher;
6-
use Patchlevel\Hydrator\Cryptography\Cipher\CipherKeyFactory;
75
use Patchlevel\Hydrator\Cryptography\Cipher\DecryptionFailed;
86
use Patchlevel\Hydrator\Cryptography\Cipher\EncryptionFailed;
9-
use Patchlevel\Hydrator\Cryptography\Cipher\OpensslCipher;
10-
use Patchlevel\Hydrator\Cryptography\Cipher\OpensslCipherKeyFactory;
117
use Patchlevel\Hydrator\Cryptography\Store\CipherKeyNotExists;
12-
use Patchlevel\Hydrator\Cryptography\Store\CipherKeyStore;
138

14-
final class Cryptographer
9+
interface Cryptographer
1510
{
16-
public function __construct(
17-
private readonly Cipher $cipher,
18-
private readonly CipherKeyStore $cipherKeyStore,
19-
private readonly CipherKeyFactory $cipherKeyFactory,
20-
)
21-
{
22-
}
23-
2411
/**
2512
* @throws EncryptionFailed
2613
*/
27-
public function encrypt(string $subjectId, mixed $value): string
28-
{
29-
try {
30-
$cipherKey = $this->cipherKeyStore->get($subjectId);
31-
} catch (CipherKeyNotExists) {
32-
$cipherKey = ($this->cipherKeyFactory)();
33-
$this->cipherKeyStore->store($subjectId, $cipherKey);
34-
}
35-
36-
return $this->cipher->encrypt($cipherKey, $value);
37-
}
38-
14+
public function encrypt(string $subjectId, mixed $value): mixed;
3915
/**
4016
* @throws CipherKeyNotExists
4117
* @throws DecryptionFailed
4218
*/
43-
public function decrypt(string $subjectId, string $value): mixed
44-
{
45-
$cipherKey = $this->cipherKeyStore->get($subjectId);
46-
47-
return $this->cipher->decrypt($cipherKey, $value);
48-
}
49-
19+
public function decrypt(string $subjectId, mixed $encryptedData): mixed;
5020

51-
/** @param non-empty-string $method */
52-
public static function createWithOpenssl(
53-
CipherKeyStore $cryptoStore,
54-
string $method = OpensslCipherKeyFactory::DEFAULT_METHOD,
55-
): static {
56-
return new self(
57-
new OpensslCipher(),
58-
$cryptoStore,
59-
new OpensslCipherKeyFactory($method),
60-
);
61-
}
21+
public function supports(mixed $value): bool;
6222
}

src/Cryptography/SensitiveDataInfo.php

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)