-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathBaseCryptographer.php
More file actions
125 lines (107 loc) · 3.78 KB
/
Copy pathBaseCryptographer.php
File metadata and controls
125 lines (107 loc) · 3.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
<?php
declare(strict_types=1);
namespace Patchlevel\Hydrator\Extension\Cryptography;
use Patchlevel\Hydrator\Extension\Cryptography\Cipher\Cipher;
use Patchlevel\Hydrator\Extension\Cryptography\Cipher\CipherKeyFactory;
use Patchlevel\Hydrator\Extension\Cryptography\Cipher\DecryptionFailed;
use Patchlevel\Hydrator\Extension\Cryptography\Cipher\EncryptedData;
use Patchlevel\Hydrator\Extension\Cryptography\Cipher\EncryptionFailed;
use Patchlevel\Hydrator\Extension\Cryptography\Cipher\OpensslCipher;
use Patchlevel\Hydrator\Extension\Cryptography\Cipher\OpensslCipherKeyFactory;
use Patchlevel\Hydrator\Extension\Cryptography\Store\CipherKeyNotExists;
use Patchlevel\Hydrator\Extension\Cryptography\Store\CipherKeyStore;
use function is_array;
/**
* @experimental
* @phpstan-type EncryptedDataArray array{
* v: 1,
* a: non-empty-string,
* k: non-empty-string,
* n?: non-empty-string, // base64
* d: non-empty-string, // base64 ciphertext
* t?: non-empty-string, // base64 (for AEAD)
* }
*/
final class BaseCryptographer implements Cryptographer
{
private const VERSION_KEY = 'v';
private const METHOD_KEY = 'a';
private const KEY_ID_KEY = 'k';
private const NONCE_KEY = 'n';
private const DATA_KEY = 'd';
private const TAG_KEY = 't';
public function __construct(
private readonly Cipher $cipher,
private readonly CipherKeyStore $cipherKeyStore,
private readonly CipherKeyFactory $cipherKeyFactory,
) {
}
/**
* @return EncryptedDataArray
*
* @throws EncryptionFailed
*/
public function encrypt(string $subjectId, mixed $value): array
{
try {
$cipherKey = $this->cipherKeyStore->currentKeyFor($subjectId);
} catch (CipherKeyNotExists) {
$cipherKey = ($this->cipherKeyFactory)($subjectId);
$this->cipherKeyStore->store($cipherKey->id, $cipherKey);
}
$parameter = $this->cipher->encrypt($cipherKey, $value);
$result = [
self::VERSION_KEY => 1,
self::METHOD_KEY => $parameter->method,
self::KEY_ID_KEY => $cipherKey->id,
self::DATA_KEY => $parameter->data,
];
if ($parameter->nonce !== null) {
$result[self::NONCE_KEY] = $parameter->nonce;
}
if ($parameter->tag !== null) {
$result[self::TAG_KEY] = $parameter->tag;
}
return $result;
}
/**
* @param EncryptedDataArray $encryptedData
*
* @throws CipherKeyNotExists
* @throws DecryptionFailed
*/
public function decrypt(string $subjectId, mixed $encryptedData): mixed
{
$keyId = $encryptedData[self::KEY_ID_KEY] ?? null;
if ($keyId === null) {
throw DecryptionFailed::missingKeyId();
}
$cipherKey = $this->cipherKeyStore->get($keyId);
return $this->cipher->decrypt(
$cipherKey,
new EncryptedData(
$encryptedData[self::DATA_KEY],
$encryptedData[self::METHOD_KEY],
$encryptedData[self::NONCE_KEY] ?? null,
$encryptedData[self::TAG_KEY] ?? null,
),
);
}
public function supports(mixed $value): bool
{
return is_array($value)
&& isset($value[self::VERSION_KEY], $value[self::METHOD_KEY], $value[self::KEY_ID_KEY], $value[self::DATA_KEY])
&& $value[self::VERSION_KEY] === 1;
}
/** @param non-empty-string $method */
public static function createWithOpenssl(
CipherKeyStore $cryptoStore,
string $method = OpensslCipherKeyFactory::DEFAULT_METHOD,
): static {
return new self(
new OpensslCipher(),
$cryptoStore,
new OpensslCipherKeyFactory($method),
);
}
}