AesGcm.php 6.8 KB

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