EncryptionTrait.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. <?php
  2. namespace Aws\Crypto;
  3. use GuzzleHttp\Psr7;
  4. use GuzzleHttp\Psr7\AppendStream;
  5. use GuzzleHttp\Psr7\Stream;
  6. trait EncryptionTrait
  7. {
  8. private static $allowedOptions = [
  9. 'Cipher' => true,
  10. 'KeySize' => true,
  11. 'Aad' => true,
  12. ];
  13. /**
  14. * Dependency to generate a CipherMethod from a set of inputs for loading
  15. * in to an AesEncryptingStream.
  16. *
  17. * @param string $cipherName Name of the cipher to generate for encrypting.
  18. * @param string $iv Base Initialization Vector for the cipher.
  19. * @param int $keySize Size of the encryption key, in bits, that will be
  20. * used.
  21. *
  22. * @return Cipher\CipherMethod
  23. *
  24. * @internal
  25. */
  26. abstract protected function buildCipherMethod($cipherName, $iv, $keySize);
  27. /**
  28. * Builds an AesStreamInterface and populates encryption metadata into the
  29. * supplied envelope.
  30. *
  31. * @param Stream $plaintext Plain-text data to be encrypted using the
  32. * materials, algorithm, and data provided.
  33. * @param array $cipherOptions Options for use in determining the cipher to
  34. * be used for encrypting data.
  35. * @param MaterialsProvider $provider A provider to supply and encrypt
  36. * materials used in encryption.
  37. * @param MetadataEnvelope $envelope A storage envelope for encryption
  38. * metadata to be added to.
  39. *
  40. * @return AesStreamInterface
  41. *
  42. * @throws \InvalidArgumentException Thrown when a value in $cipherOptions
  43. * is not valid.
  44. *
  45. * @internal
  46. */
  47. public function encrypt(
  48. Stream $plaintext,
  49. array $cipherOptions,
  50. MaterialsProvider $provider,
  51. MetadataEnvelope $envelope
  52. ) {
  53. $materialsDescription = $provider->getMaterialsDescription();
  54. $cipherOptions = array_intersect_key(
  55. $cipherOptions,
  56. self::$allowedOptions
  57. );
  58. if (empty($cipherOptions['Cipher'])) {
  59. throw new \InvalidArgumentException('An encryption cipher must be'
  60. . ' specified in the "cipher_options".');
  61. }
  62. if (!self::isSupportedCipher($cipherOptions['Cipher'])) {
  63. throw new \InvalidArgumentException('The cipher requested is not'
  64. . ' supported by the SDK.');
  65. }
  66. if (empty($cipherOptions['KeySize'])) {
  67. $cipherOptions['KeySize'] = 256;
  68. }
  69. if (!is_int($cipherOptions['KeySize'])) {
  70. throw new \InvalidArgumentException('The cipher "KeySize" must be'
  71. . ' an integer.');
  72. }
  73. if (!MaterialsProvider::isSupportedKeySize(
  74. $cipherOptions['KeySize']
  75. )) {
  76. throw new \InvalidArgumentException('The cipher "KeySize" requested'
  77. . ' is not supported by AES (128, 192, or 256).');
  78. }
  79. $cipherOptions['Iv'] = $provider->generateIv(
  80. $this->getCipherOpenSslName(
  81. $cipherOptions['Cipher'],
  82. $cipherOptions['KeySize']
  83. )
  84. );
  85. $cek = $provider->generateCek($cipherOptions['KeySize']);
  86. list($encryptingStream, $aesName) = $this->getEncryptingStream(
  87. $plaintext,
  88. $cek,
  89. $cipherOptions
  90. );
  91. // Populate envelope data
  92. $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] =
  93. $provider->encryptCek(
  94. $cek,
  95. $materialsDescription
  96. );
  97. unset($cek);
  98. $envelope[MetadataEnvelope::IV_HEADER] =
  99. base64_encode($cipherOptions['Iv']);
  100. $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER] =
  101. $provider->getWrapAlgorithmName();
  102. $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] = $aesName;
  103. $envelope[MetadataEnvelope::UNENCRYPTED_CONTENT_LENGTH_HEADER] =
  104. strlen($plaintext);
  105. $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER] =
  106. json_encode($materialsDescription);
  107. if (!empty($cipherOptions['Tag'])) {
  108. $envelope[MetadataEnvelope::CRYPTO_TAG_LENGTH_HEADER] =
  109. strlen($cipherOptions['Tag']) * 8;
  110. }
  111. return $encryptingStream;
  112. }
  113. /**
  114. * Generates a stream that wraps the plaintext with the proper cipher and
  115. * uses the content encryption key (CEK) to encrypt the data when read.
  116. *
  117. * @param Stream $plaintext Plain-text data to be encrypted using the
  118. * materials, algorithm, and data provided.
  119. * @param string $cek A content encryption key for use by the stream for
  120. * encrypting the plaintext data.
  121. * @param array $cipherOptions Options for use in determining the cipher to
  122. * be used for encrypting data.
  123. *
  124. * @return array returns an array with two elements as follows: [string, AesStreamInterface]
  125. *
  126. * @internal
  127. */
  128. protected function getEncryptingStream(
  129. Stream $plaintext,
  130. $cek,
  131. &$cipherOptions
  132. ) {
  133. switch ($cipherOptions['Cipher']) {
  134. case 'gcm':
  135. $cipherOptions['TagLength'] = 16;
  136. $cipherTextStream = new AesGcmEncryptingStream(
  137. $plaintext,
  138. $cek,
  139. $cipherOptions['Iv'],
  140. $cipherOptions['Aad'] = isset($cipherOptions['Aad'])
  141. ? $cipherOptions['Aad']
  142. : '',
  143. $cipherOptions['TagLength'],
  144. $cipherOptions['KeySize']
  145. );
  146. if (!empty($cipherOptions['Aad'])) {
  147. trigger_error("'Aad' has been supplied for content encryption"
  148. . " with " . $cipherTextStream->getAesName() . ". The"
  149. . " PHP SDK encryption client can decrypt an object"
  150. . " encrypted in this way, but other AWS SDKs may not be"
  151. . " able to.", E_USER_WARNING);
  152. }
  153. $appendStream = new AppendStream([
  154. $cipherTextStream->createStream()
  155. ]);
  156. $cipherOptions['Tag'] = $cipherTextStream->getTag();
  157. $appendStream->addStream(Psr7\Utils::streamFor($cipherOptions['Tag']));
  158. return [$appendStream, $cipherTextStream->getAesName()];
  159. default:
  160. $cipherMethod = $this->buildCipherMethod(
  161. $cipherOptions['Cipher'],
  162. $cipherOptions['Iv'],
  163. $cipherOptions['KeySize']
  164. );
  165. $cipherTextStream = new AesEncryptingStream(
  166. $plaintext,
  167. $cek,
  168. $cipherMethod
  169. );
  170. return [$cipherTextStream, $cipherTextStream->getAesName()];
  171. }
  172. }
  173. }