55namespace Patchlevel \Hydrator \Extension \Cryptography ;
66
77use Patchlevel \Hydrator \Extension \Cryptography \Cipher \Cipher ;
8- use Patchlevel \Hydrator \Extension \Cryptography \Cipher \CipherKey ;
98use Patchlevel \Hydrator \Extension \Cryptography \Cipher \CipherKeyFactory ;
109use Patchlevel \Hydrator \Extension \Cryptography \Cipher \DecryptionFailed ;
10+ use Patchlevel \Hydrator \Extension \Cryptography \Cipher \EncryptedData ;
1111use Patchlevel \Hydrator \Extension \Cryptography \Cipher \EncryptionFailed ;
1212use Patchlevel \Hydrator \Extension \Cryptography \Cipher \OpensslCipher ;
1313use Patchlevel \Hydrator \Extension \Cryptography \Cipher \OpensslCipherKeyFactory ;
1414use Patchlevel \Hydrator \Extension \Cryptography \Store \CipherKeyNotExists ;
1515use Patchlevel \Hydrator \Extension \Cryptography \Store \CipherKeyStore ;
1616
17- use function array_key_exists ;
18- use function base64_decode ;
19- use function base64_encode ;
2017use function is_array ;
2118
2219/**
2320 * @experimental
24- * @phpstan-type EncryptedDataV1 array{
25- * __enc: 'v1',
26- * data: non-empty-string,
27- * method?: non-empty-string,
28- * iv?: non-empty-string,
21+ * @phpstan-type EncryptedDataArray array{
22+ * v: 1,
23+ * a: non-empty-string,
24+ * k: non-empty-string,
25+ * n?: non-empty-string, // base64
26+ * d: non-empty-string, // base64 ciphertext
27+ * t?: non-empty-string, // base64 (for AEAD)
2928 * }
3029 */
3130final class BaseCryptographer implements Cryptographer
3231{
32+ private const VERSION_KEY = 'v ' ;
33+ private const METHOD_KEY = 'a ' ;
34+ private const KEY_ID_KEY = 'k ' ;
35+ private const NONCE_KEY = 'n ' ;
36+ private const DATA_KEY = 'd ' ;
37+ private const TAG_KEY = 't ' ;
38+
3339 public function __construct (
3440 private readonly Cipher $ cipher ,
3541 private readonly CipherKeyStore $ cipherKeyStore ,
@@ -38,50 +44,71 @@ public function __construct(
3844 }
3945
4046 /**
41- * @return EncryptedDataV1
47+ * @return EncryptedDataArray
4248 *
4349 * @throws EncryptionFailed
4450 */
4551 public function encrypt (string $ subjectId , mixed $ value ): array
4652 {
4753 try {
48- $ cipherKey = $ this ->cipherKeyStore ->get ($ subjectId );
54+ $ cipherKey = $ this ->cipherKeyStore ->currentKeyFor ($ subjectId );
4955 } catch (CipherKeyNotExists ) {
50- $ cipherKey = ($ this ->cipherKeyFactory )();
51- $ this ->cipherKeyStore ->store ($ subjectId , $ cipherKey );
56+ $ cipherKey = ($ this ->cipherKeyFactory )($ subjectId );
57+ $ this ->cipherKeyStore ->store ($ cipherKey -> id , $ cipherKey );
5258 }
5359
54- return [
55- '__enc ' => 'v1 ' ,
56- 'data ' => $ this ->cipher ->encrypt ($ cipherKey , $ value ),
57- 'method ' => $ cipherKey ->method ,
58- 'iv ' => base64_encode ($ cipherKey ->iv ),
60+ $ parameter = $ this ->cipher ->encrypt ($ cipherKey , $ value );
61+
62+ $ result = [
63+ self ::VERSION_KEY => 1 ,
64+ self ::METHOD_KEY => $ parameter ->method ,
65+ self ::KEY_ID_KEY => $ cipherKey ->id ,
66+ self ::DATA_KEY => $ parameter ->data ,
5967 ];
68+
69+ if ($ parameter ->nonce !== null ) {
70+ $ result [self ::NONCE_KEY ] = $ parameter ->nonce ;
71+ }
72+
73+ if ($ parameter ->tag !== null ) {
74+ $ result [self ::TAG_KEY ] = $ parameter ->tag ;
75+ }
76+
77+ return $ result ;
6078 }
6179
6280 /**
63- * @param EncryptedDataV1 $encryptedData
81+ * @param EncryptedDataArray $encryptedData
6482 *
6583 * @throws CipherKeyNotExists
6684 * @throws DecryptionFailed
6785 */
6886 public function decrypt (string $ subjectId , mixed $ encryptedData ): mixed
6987 {
70- $ cipherKey = $ this ->cipherKeyStore ->get ($ subjectId );
88+ $ keyId = $ encryptedData [self ::KEY_ID_KEY ] ?? null ;
89+
90+ if ($ keyId === null ) {
91+ throw DecryptionFailed::missingKeyId ();
92+ }
93+
94+ $ cipherKey = $ this ->cipherKeyStore ->get ($ keyId );
7195
7296 return $ this ->cipher ->decrypt (
73- new CipherKey (
74- $ cipherKey ->key ,
75- $ encryptedData ['method ' ] ?? $ cipherKey ->method ,
76- isset ($ encryptedData ['iv ' ]) ? base64_decode ($ encryptedData ['iv ' ]) : $ cipherKey ->iv ,
97+ $ cipherKey ,
98+ new EncryptedData (
99+ $ encryptedData [self ::DATA_KEY ],
100+ $ encryptedData [self ::METHOD_KEY ],
101+ $ encryptedData [self ::NONCE_KEY ] ?? null ,
102+ $ encryptedData [self ::TAG_KEY ] ?? null ,
77103 ),
78- $ encryptedData ['data ' ],
79104 );
80105 }
81106
82107 public function supports (mixed $ value ): bool
83108 {
84- return is_array ($ value ) && array_key_exists ('__enc ' , $ value ) && $ value ['__enc ' ] === 'v1 ' ;
109+ return is_array ($ value )
110+ && isset ($ value [self ::VERSION_KEY ], $ value [self ::METHOD_KEY ], $ value [self ::KEY_ID_KEY ], $ value [self ::DATA_KEY ])
111+ && $ value [self ::VERSION_KEY ] === 1 ;
85112 }
86113
87114 /** @param non-empty-string $method */
0 commit comments