AesDecryptingStream.php 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  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()
  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()
  69. {
  70. return false;
  71. }
  72. public function read($length)
  73. {
  74. if ($length > strlen($this->buffer)) {
  75. $this->buffer .= $this->decryptBlock(
  76. self::BLOCK_SIZE * ceil(($length - strlen($this->buffer)) / self::BLOCK_SIZE)
  77. );
  78. }
  79. $data = substr($this->buffer, 0, $length);
  80. $this->buffer = substr($this->buffer, $length);
  81. return $data ? $data : '';
  82. }
  83. public function seek($offset, $whence = SEEK_SET)
  84. {
  85. if ($offset === 0 && $whence === SEEK_SET) {
  86. $this->buffer = '';
  87. $this->cipherMethod->seek(0, SEEK_SET);
  88. $this->stream->seek(0, SEEK_SET);
  89. } else {
  90. throw new LogicException('AES encryption streams only support being'
  91. . ' rewound, not arbitrary seeking.');
  92. }
  93. }
  94. private function decryptBlock($length)
  95. {
  96. if ($this->stream->eof()) {
  97. return '';
  98. }
  99. $cipherText = '';
  100. do {
  101. $cipherText .= $this->stream->read($length - strlen($cipherText));
  102. } while (strlen($cipherText) < $length && !$this->stream->eof());
  103. $options = OPENSSL_RAW_DATA;
  104. if (!$this->stream->eof()
  105. && $this->stream->getSize() !== $this->stream->tell()
  106. ) {
  107. $options |= OPENSSL_ZERO_PADDING;
  108. }
  109. $plaintext = openssl_decrypt(
  110. $cipherText,
  111. $this->cipherMethod->getOpenSslName(),
  112. $this->key,
  113. $options,
  114. $this->cipherMethod->getCurrentIv()
  115. );
  116. $this->cipherMethod->update($cipherText);
  117. return $plaintext;
  118. }
  119. }