8395db17eba7a27f9b4663cb9e94871c3fc712ae
[GitHub/WoltLab/WCF.git] /
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Jose\Component\Encryption\Algorithm\ContentEncryption;
6
7 use Jose\Component\Encryption\Algorithm\ContentEncryptionAlgorithm;
8 use ParagonIE\ConstantTime\Base64UrlSafe;
9 use RuntimeException;
10 use function extension_loaded;
11 use const OPENSSL_RAW_DATA;
12
13 abstract class AESCBCHS implements ContentEncryptionAlgorithm
14 {
15 public function __construct()
16 {
17 if (! extension_loaded('openssl')) {
18 throw new RuntimeException('Please install the OpenSSL extension');
19 }
20 }
21
22 public function allowedKeyTypes(): array
23 {
24 return []; //Irrelevant
25 }
26
27 public function encryptContent(
28 string $data,
29 string $cek,
30 string $iv,
31 ?string $aad,
32 string $encoded_protected_header,
33 ?string &$tag = null
34 ): string {
35 $k = mb_substr($cek, $this->getCEKSize() / 16, null, '8bit');
36 $result = openssl_encrypt($data, $this->getMode(), $k, OPENSSL_RAW_DATA, $iv);
37 if ($result === false) {
38 throw new RuntimeException('Unable to encrypt the content');
39 }
40
41 $tag = $this->calculateAuthenticationTag($result, $cek, $iv, $aad, $encoded_protected_header);
42
43 return $result;
44 }
45
46 public function decryptContent(
47 string $data,
48 string $cek,
49 string $iv,
50 ?string $aad,
51 string $encoded_protected_header,
52 string $tag
53 ): string {
54 if (! $this->isTagValid($data, $cek, $iv, $aad, $encoded_protected_header, $tag)) {
55 throw new RuntimeException('Unable to decrypt or to verify the tag.');
56 }
57 $k = mb_substr($cek, $this->getCEKSize() / 16, null, '8bit');
58
59 $result = openssl_decrypt($data, $this->getMode(), $k, OPENSSL_RAW_DATA, $iv);
60 if ($result === false) {
61 throw new RuntimeException('Unable to decrypt or to verify the tag.');
62 }
63
64 return $result;
65 }
66
67 public function getIVSize(): int
68 {
69 return 128;
70 }
71
72 protected function calculateAuthenticationTag(
73 string $encrypted_data,
74 string $cek,
75 string $iv,
76 ?string $aad,
77 string $encoded_header
78 ): string {
79 $calculated_aad = $encoded_header;
80 if ($aad !== null) {
81 $calculated_aad .= '.' . Base64UrlSafe::encodeUnpadded($aad);
82 }
83 $mac_key = mb_substr($cek, 0, $this->getCEKSize() / 16, '8bit');
84 $auth_data_length = mb_strlen($encoded_header, '8bit');
85
86 $secured_input = implode('', [
87 $calculated_aad,
88 $iv,
89 $encrypted_data,
90 pack('N2', ($auth_data_length / 2_147_483_647) * 8, ($auth_data_length % 2_147_483_647) * 8),
91 ]);
92 $hash = hash_hmac($this->getHashAlgorithm(), $secured_input, $mac_key, true);
93
94 return mb_substr($hash, 0, mb_strlen($hash, '8bit') / 2, '8bit');
95 }
96
97 protected function isTagValid(
98 string $encrypted_data,
99 string $cek,
100 string $iv,
101 ?string $aad,
102 string $encoded_header,
103 string $authentication_tag
104 ): bool {
105 return hash_equals(
106 $authentication_tag,
107 $this->calculateAuthenticationTag($encrypted_data, $cek, $iv, $aad, $encoded_header)
108 );
109 }
110
111 abstract protected function getHashAlgorithm(): string;
112
113 abstract protected function getMode(): string;
114 }