DecryptionTrait.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. <?php
  2. namespace Aws\Crypto;
  3. use GuzzleHttp\Psr7;
  4. use GuzzleHttp\Psr7\LimitStream;
  5. use Psr\Http\Message\StreamInterface;
  6. trait DecryptionTrait
  7. {
  8. /**
  9. * Dependency to reverse lookup the openssl_* cipher name from the AESName
  10. * in the MetadataEnvelope.
  11. *
  12. * @param $aesName
  13. *
  14. * @return string
  15. *
  16. * @internal
  17. */
  18. abstract protected function getCipherFromAesName($aesName);
  19. /**
  20. * Dependency to generate a CipherMethod from a set of inputs for loading
  21. * in to an AesDecryptingStream.
  22. *
  23. * @param string $cipherName Name of the cipher to generate for decrypting.
  24. * @param string $iv Base Initialization Vector for the cipher.
  25. * @param int $keySize Size of the encryption key, in bits, that will be
  26. * used.
  27. *
  28. * @return Cipher\CipherMethod
  29. *
  30. * @internal
  31. */
  32. abstract protected function buildCipherMethod($cipherName, $iv, $keySize);
  33. /**
  34. * Builds an AesStreamInterface using cipher options loaded from the
  35. * MetadataEnvelope and MaterialsProvider. Can decrypt data from both the
  36. * legacy and V2 encryption client workflows.
  37. *
  38. * @param string $cipherText Plain-text data to be encrypted using the
  39. * materials, algorithm, and data provided.
  40. * @param MaterialsProviderInterface $provider A provider to supply and encrypt
  41. * materials used in encryption.
  42. * @param MetadataEnvelope $envelope A storage envelope for encryption
  43. * metadata to be read from.
  44. * @param array $cipherOptions Additional verification options.
  45. *
  46. * @return AesStreamInterface
  47. *
  48. * @throws \InvalidArgumentException Thrown when a value in $cipherOptions
  49. * is not valid.
  50. *
  51. * @internal
  52. */
  53. public function decrypt(
  54. $cipherText,
  55. MaterialsProviderInterface $provider,
  56. MetadataEnvelope $envelope,
  57. array $cipherOptions = []
  58. ) {
  59. $cipherOptions['Iv'] = base64_decode(
  60. $envelope[MetadataEnvelope::IV_HEADER]
  61. );
  62. $cipherOptions['TagLength'] =
  63. $envelope[MetadataEnvelope::CRYPTO_TAG_LENGTH_HEADER] / 8;
  64. $cek = $provider->decryptCek(
  65. base64_decode(
  66. $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER]
  67. ),
  68. json_decode(
  69. $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER],
  70. true
  71. )
  72. );
  73. $cipherOptions['KeySize'] = strlen($cek) * 8;
  74. $cipherOptions['Cipher'] = $this->getCipherFromAesName(
  75. $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER]
  76. );
  77. $decryptionStream = $this->getDecryptingStream(
  78. $cipherText,
  79. $cek,
  80. $cipherOptions
  81. );
  82. unset($cek);
  83. return $decryptionStream;
  84. }
  85. private function getTagFromCiphertextStream(
  86. StreamInterface $cipherText,
  87. $tagLength
  88. ) {
  89. $cipherTextSize = $cipherText->getSize();
  90. if ($cipherTextSize == null || $cipherTextSize <= 0) {
  91. throw new \RuntimeException('Cannot decrypt a stream of unknown'
  92. . ' size.');
  93. }
  94. return (string) new LimitStream(
  95. $cipherText,
  96. $tagLength,
  97. $cipherTextSize - $tagLength
  98. );
  99. }
  100. private function getStrippedCiphertextStream(
  101. StreamInterface $cipherText,
  102. $tagLength
  103. ) {
  104. $cipherTextSize = $cipherText->getSize();
  105. if ($cipherTextSize == null || $cipherTextSize <= 0) {
  106. throw new \RuntimeException('Cannot decrypt a stream of unknown'
  107. . ' size.');
  108. }
  109. return new LimitStream(
  110. $cipherText,
  111. $cipherTextSize - $tagLength,
  112. 0
  113. );
  114. }
  115. /**
  116. * Generates a stream that wraps the cipher text with the proper cipher and
  117. * uses the content encryption key (CEK) to decrypt the data when read.
  118. *
  119. * @param string $cipherText Plain-text data to be encrypted using the
  120. * materials, algorithm, and data provided.
  121. * @param string $cek A content encryption key for use by the stream for
  122. * encrypting the plaintext data.
  123. * @param array $cipherOptions Options for use in determining the cipher to
  124. * be used for encrypting data.
  125. *
  126. * @return AesStreamInterface
  127. *
  128. * @internal
  129. */
  130. protected function getDecryptingStream(
  131. $cipherText,
  132. $cek,
  133. $cipherOptions
  134. ) {
  135. $cipherTextStream = Psr7\Utils::streamFor($cipherText);
  136. switch ($cipherOptions['Cipher']) {
  137. case 'gcm':
  138. $cipherOptions['Tag'] = $this->getTagFromCiphertextStream(
  139. $cipherTextStream,
  140. $cipherOptions['TagLength']
  141. );
  142. return new AesGcmDecryptingStream(
  143. $this->getStrippedCiphertextStream(
  144. $cipherTextStream,
  145. $cipherOptions['TagLength']
  146. ),
  147. $cek,
  148. $cipherOptions['Iv'],
  149. $cipherOptions['Tag'],
  150. $cipherOptions['Aad'] = isset($cipherOptions['Aad'])
  151. ? $cipherOptions['Aad']
  152. : '',
  153. $cipherOptions['TagLength'] ?: null,
  154. $cipherOptions['KeySize']
  155. );
  156. default:
  157. $cipherMethod = $this->buildCipherMethod(
  158. $cipherOptions['Cipher'],
  159. $cipherOptions['Iv'],
  160. $cipherOptions['KeySize']
  161. );
  162. return new AesDecryptingStream(
  163. $cipherTextStream,
  164. $cek,
  165. $cipherMethod
  166. );
  167. }
  168. }
  169. }