| 1 |
efrain |
1 |
<?php
|
|
|
2 |
namespace Aws\Crypto\Polyfill;
|
|
|
3 |
|
|
|
4 |
use Aws\Exception\CryptoPolyfillException;
|
|
|
5 |
use InvalidArgumentException;
|
|
|
6 |
use RangeException;
|
|
|
7 |
|
|
|
8 |
/**
|
|
|
9 |
* Class AesGcm
|
|
|
10 |
*
|
|
|
11 |
* This provides a polyfill for AES-GCM encryption/decryption, with caveats:
|
|
|
12 |
*
|
|
|
13 |
* 1. Only 96-bit nonces are supported.
|
|
|
14 |
* 2. Only 128-bit authentication tags are supported. (i.e. non-truncated)
|
|
|
15 |
*
|
|
|
16 |
* Supports AES key sizes of 128-bit, 192-bit, and 256-bit.
|
|
|
17 |
*
|
|
|
18 |
* @package Aws\Crypto\Polyfill
|
|
|
19 |
*/
|
|
|
20 |
class AesGcm
|
|
|
21 |
{
|
|
|
22 |
use NeedsTrait;
|
|
|
23 |
|
|
|
24 |
/** @var Key $aesKey */
|
|
|
25 |
private $aesKey;
|
|
|
26 |
|
|
|
27 |
/** @var int $keySize */
|
|
|
28 |
private $keySize;
|
|
|
29 |
|
|
|
30 |
/** @var int $blockSize */
|
|
|
31 |
protected $blockSize = 8192;
|
|
|
32 |
|
|
|
33 |
/**
|
|
|
34 |
* AesGcm constructor.
|
|
|
35 |
*
|
|
|
36 |
* @param Key $aesKey
|
|
|
37 |
* @param int $keySize
|
|
|
38 |
* @param int $blockSize
|
|
|
39 |
*
|
|
|
40 |
* @throws CryptoPolyfillException
|
|
|
41 |
* @throws InvalidArgumentException
|
|
|
42 |
* @throws RangeException
|
|
|
43 |
*/
|
|
|
44 |
public function __construct(Key $aesKey, $keySize = 256, $blockSize = 8192)
|
|
|
45 |
{
|
|
|
46 |
/* Preconditions: */
|
|
|
47 |
self::needs(
|
|
|
48 |
\in_array($keySize, [128, 192, 256], true),
|
|
|
49 |
"Key size must be 128, 192, or 256 bits; {$keySize} given",
|
|
|
50 |
InvalidArgumentException::class
|
|
|
51 |
);
|
|
|
52 |
self::needs(
|
|
|
53 |
\is_int($blockSize) && $blockSize > 0 && $blockSize <= PHP_INT_MAX,
|
|
|
54 |
'Block size must be a positive integer.',
|
|
|
55 |
RangeException::class
|
|
|
56 |
);
|
|
|
57 |
self::needs(
|
|
|
58 |
$aesKey->length() << 3 === $keySize,
|
|
|
59 |
'Incorrect key size; expected ' . $keySize . ' bits, got ' . ($aesKey->length() << 3) . ' bits.'
|
|
|
60 |
);
|
|
|
61 |
$this->aesKey = $aesKey;
|
|
|
62 |
$this->keySize = $keySize;
|
|
|
63 |
}
|
|
|
64 |
|
|
|
65 |
/**
|
|
|
66 |
* Encryption interface for AES-GCM
|
|
|
67 |
*
|
|
|
68 |
* @param string $plaintext Message to be encrypted
|
|
|
69 |
* @param string $nonce Number to be used ONCE
|
|
|
70 |
* @param Key $key AES Key
|
|
|
71 |
* @param string $aad Additional authenticated data
|
|
|
72 |
* @param string &$tag Reference to variable to hold tag
|
|
|
73 |
* @param int $keySize Key size (bits)
|
|
|
74 |
* @param int $blockSize Block size (bytes) -- How much memory to buffer
|
|
|
75 |
* @return string
|
|
|
76 |
* @throws InvalidArgumentException
|
|
|
77 |
*/
|
|
|
78 |
public static function encrypt(
|
|
|
79 |
$plaintext,
|
|
|
80 |
$nonce,
|
|
|
81 |
Key $key,
|
|
|
82 |
$aad,
|
|
|
83 |
&$tag,
|
|
|
84 |
$keySize = 256,
|
|
|
85 |
$blockSize = 8192
|
|
|
86 |
) {
|
|
|
87 |
self::needs(
|
|
|
88 |
self::strlen($nonce) === 12,
|
|
|
89 |
'Nonce must be exactly 12 bytes',
|
|
|
90 |
InvalidArgumentException::class
|
|
|
91 |
);
|
|
|
92 |
|
|
|
93 |
$encryptor = new AesGcm($key, $keySize, $blockSize);
|
|
|
94 |
list($aadLength, $gmac) = $encryptor->gmacInit($nonce, $aad);
|
|
|
95 |
|
|
|
96 |
$ciphertext = \openssl_encrypt(
|
|
|
97 |
$plaintext,
|
|
|
98 |
"aes-{$encryptor->keySize}-ctr",
|
|
|
99 |
$key->get(),
|
|
|
100 |
OPENSSL_NO_PADDING | OPENSSL_RAW_DATA,
|
|
|
101 |
$nonce . "\x00\x00\x00\x02"
|
|
|
102 |
);
|
|
|
103 |
|
|
|
104 |
/* Calculate auth tag in a streaming fashion to minimize memory usage: */
|
|
|
105 |
$ciphertextLength = self::strlen($ciphertext);
|
|
|
106 |
for ($i = 0; $i < $ciphertextLength; $i += $encryptor->blockSize) {
|
|
|
107 |
$cBlock = new ByteArray(self::substr($ciphertext, $i, $encryptor->blockSize));
|
|
|
108 |
$gmac->update($cBlock);
|
|
|
109 |
}
|
|
|
110 |
$tag = $gmac->finish($aadLength, $ciphertextLength)->toString();
|
|
|
111 |
return $ciphertext;
|
|
|
112 |
}
|
|
|
113 |
|
|
|
114 |
/**
|
|
|
115 |
* Decryption interface for AES-GCM
|
|
|
116 |
*
|
|
|
117 |
* @param string $ciphertext Ciphertext to decrypt
|
|
|
118 |
* @param string $nonce Number to be used ONCE
|
|
|
119 |
* @param Key $key AES key
|
|
|
120 |
* @param string $aad Additional authenticated data
|
|
|
121 |
* @param string $tag Authentication tag
|
|
|
122 |
* @param int $keySize Key size (bits)
|
|
|
123 |
* @param int $blockSize Block size (bytes) -- How much memory to buffer
|
|
|
124 |
* @return string Plaintext
|
|
|
125 |
*
|
|
|
126 |
* @throws CryptoPolyfillException
|
|
|
127 |
* @throws InvalidArgumentException
|
|
|
128 |
*/
|
|
|
129 |
public static function decrypt(
|
|
|
130 |
$ciphertext,
|
|
|
131 |
$nonce,
|
|
|
132 |
Key $key,
|
|
|
133 |
$aad,
|
|
|
134 |
&$tag,
|
|
|
135 |
$keySize = 256,
|
|
|
136 |
$blockSize = 8192
|
|
|
137 |
) {
|
|
|
138 |
/* Precondition: */
|
|
|
139 |
self::needs(
|
|
|
140 |
self::strlen($nonce) === 12,
|
|
|
141 |
'Nonce must be exactly 12 bytes',
|
|
|
142 |
InvalidArgumentException::class
|
|
|
143 |
);
|
|
|
144 |
|
|
|
145 |
$encryptor = new AesGcm($key, $keySize, $blockSize);
|
|
|
146 |
list($aadLength, $gmac) = $encryptor->gmacInit($nonce, $aad);
|
|
|
147 |
|
|
|
148 |
/* Calculate auth tag in a streaming fashion to minimize memory usage: */
|
|
|
149 |
$ciphertextLength = self::strlen($ciphertext);
|
|
|
150 |
for ($i = 0; $i < $ciphertextLength; $i += $encryptor->blockSize) {
|
|
|
151 |
$cBlock = new ByteArray(self::substr($ciphertext, $i, $encryptor->blockSize));
|
|
|
152 |
$gmac->update($cBlock);
|
|
|
153 |
}
|
|
|
154 |
|
|
|
155 |
/* Validate auth tag in constant-time: */
|
|
|
156 |
$calc = $gmac->finish($aadLength, $ciphertextLength);
|
|
|
157 |
$expected = new ByteArray($tag);
|
|
|
158 |
self::needs($calc->equals($expected), 'Invalid authentication tag');
|
|
|
159 |
|
|
|
160 |
/* Return plaintext if auth tag check succeeded: */
|
|
|
161 |
return \openssl_decrypt(
|
|
|
162 |
$ciphertext,
|
|
|
163 |
"aes-{$encryptor->keySize}-ctr",
|
|
|
164 |
$key->get(),
|
|
|
165 |
OPENSSL_NO_PADDING | OPENSSL_RAW_DATA,
|
|
|
166 |
$nonce . "\x00\x00\x00\x02"
|
|
|
167 |
);
|
|
|
168 |
}
|
|
|
169 |
|
|
|
170 |
/**
|
|
|
171 |
* Initialize a Gmac object with the nonce and this object's key.
|
|
|
172 |
*
|
|
|
173 |
* @param string $nonce Must be exactly 12 bytes long.
|
|
|
174 |
* @param string|null $aad
|
|
|
175 |
* @return array
|
|
|
176 |
*/
|
|
|
177 |
protected function gmacInit($nonce, $aad = null)
|
|
|
178 |
{
|
|
|
179 |
$gmac = new Gmac(
|
|
|
180 |
$this->aesKey,
|
|
|
181 |
$nonce . "\x00\x00\x00\x01",
|
|
|
182 |
$this->keySize
|
|
|
183 |
);
|
|
|
184 |
$aadBlock = new ByteArray($aad);
|
|
|
185 |
$aadLength = $aadBlock->count();
|
|
|
186 |
$gmac->update($aadBlock);
|
|
|
187 |
$gmac->flush();
|
|
|
188 |
return [$aadLength, $gmac];
|
|
|
189 |
}
|
|
|
190 |
|
|
|
191 |
/**
|
|
|
192 |
* Calculate the length of a string.
|
|
|
193 |
*
|
|
|
194 |
* Uses the appropriate PHP function without being brittle to
|
|
|
195 |
* mbstring.func_overload.
|
|
|
196 |
*
|
|
|
197 |
* @param string $string
|
|
|
198 |
* @return int
|
|
|
199 |
*/
|
|
|
200 |
protected static function strlen($string)
|
|
|
201 |
{
|
|
|
202 |
if (\is_callable('\\mb_strlen')) {
|
|
|
203 |
return (int) \mb_strlen($string, '8bit');
|
|
|
204 |
}
|
|
|
205 |
return (int) \strlen($string);
|
|
|
206 |
}
|
|
|
207 |
|
|
|
208 |
/**
|
|
|
209 |
* Return a substring of the provided string.
|
|
|
210 |
*
|
|
|
211 |
* Uses the appropriate PHP function without being brittle to
|
|
|
212 |
* mbstring.func_overload.
|
|
|
213 |
*
|
|
|
214 |
* @param string $string
|
|
|
215 |
* @param int $offset
|
|
|
216 |
* @param int|null $length
|
|
|
217 |
* @return string
|
|
|
218 |
*/
|
|
|
219 |
protected static function substr($string, $offset = 0, $length = null)
|
|
|
220 |
{
|
|
|
221 |
if (\is_callable('\\mb_substr')) {
|
|
|
222 |
return \mb_substr($string, $offset, $length, '8bit');
|
|
|
223 |
} elseif (!\is_null($length)) {
|
|
|
224 |
return \substr($string, $offset, $length);
|
|
|
225 |
}
|
|
|
226 |
return \substr($string, $offset);
|
|
|
227 |
}
|
|
|
228 |
}
|