AesDecryptingStream.php 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. <?php
  2. namespace Aws\Crypto;
  3. use GuzzleHttp\Psr7\StreamDecoratorTrait;
  4. use \LogicException;
  5. use Psr\Http\Message\StreamInterface;
  6. use Aws\Crypto\Cipher\CipherMethod;
  7. /**
  8. * @internal Represents a stream of data to be decrypted with passed cipher.
  9. */
  10. class AesDecryptingStream implements AesStreamInterface
  11. {
  12. const BLOCK_SIZE = 16; // 128 bits
  13. use StreamDecoratorTrait;
  14. /**
  15. * @var string
  16. */
  17. private $buffer = '';
  18. /**
  19. * @var CipherMethod
  20. */
  21. private $cipherMethod;
  22. /**
  23. * @var string
  24. */
  25. private $key;
  26. /**
  27. * @var StreamInterface
  28. */
  29. private $stream;
  30. /**
  31. * @param StreamInterface $cipherText
  32. * @param string $key
  33. * @param CipherMethod $cipherMethod
  34. */
  35. public function __construct(
  36. StreamInterface $cipherText,
  37. $key,
  38. CipherMethod $cipherMethod
  39. ) {
  40. $this->stream = $cipherText;
  41. $this->key = $key;
  42. $this->cipherMethod = clone $cipherMethod;
  43. }
  44. public function getOpenSslName()
  45. {
  46. return $this->cipherMethod->getOpenSslName();
  47. }
  48. public function getAesName()
  49. {
  50. return $this->cipherMethod->getAesName();
  51. }
  52. public function getCurrentIv()
  53. {
  54. return $this->cipherMethod->getCurrentIv();
  55. }
  56. public function getSize(): ?int
  57. {
  58. $plainTextSize = $this->stream->getSize();
  59. if ($this->cipherMethod->requiresPadding()) {
  60. // PKCS7 padding requires that between 1 and self::BLOCK_SIZE be
  61. // added to the plaintext to make it an even number of blocks. The
  62. // plaintext is between strlen($cipherText) - self::BLOCK_SIZE and
  63. // strlen($cipherText) - 1
  64. return null;
  65. }
  66. return $plainTextSize;
  67. }
  68. public function isWritable(): bool
  69. {
  70. return false;
  71. }
  72. public function read($length): string
  73. {
  74. if ($length > strlen($this->buffer)) {
  75. $this->buffer .= $this->decryptBlock(
  76. (int) (
  77. self::BLOCK_SIZE * ceil(($length - strlen($this->buffer)) / self::BLOCK_SIZE)
  78. )
  79. );
  80. }
  81. $data = substr($this->buffer, 0, $length);
  82. $this->buffer = substr($this->buffer, $length);
  83. return $data ? $data : '';
  84. }
  85. public function seek($offset, $whence = SEEK_SET): void
  86. {
  87. if ($offset === 0 && $whence === SEEK_SET) {
  88. $this->buffer = '';
  89. $this->cipherMethod->seek(0, SEEK_SET);
  90. $this->stream->seek(0, SEEK_SET);
  91. } else {
  92. throw new LogicException('AES encryption streams only support being'
  93. . ' rewound, not arbitrary seeking.');
  94. }
  95. }
  96. private function decryptBlock($length)
  97. {
  98. if ($this->stream->eof()) {
  99. return '';
  100. }
  101. $cipherText = '';
  102. do {
  103. $cipherText .= $this->stream->read((int) ($length - strlen($cipherText)));
  104. } while (strlen($cipherText) < $length && !$this->stream->eof());
  105. $options = OPENSSL_RAW_DATA;
  106. if (!$this->stream->eof()
  107. && $this->stream->getSize() !== $this->stream->tell()
  108. ) {
  109. $options |= OPENSSL_ZERO_PADDING;
  110. }
  111. $plaintext = openssl_decrypt(
  112. $cipherText,
  113. $this->cipherMethod->getOpenSslName(),
  114. $this->key,
  115. $options,
  116. $this->cipherMethod->getCurrentIv()
  117. );
  118. $this->cipherMethod->update($cipherText);
  119. return $plaintext;
  120. }
  121. }