AesEncryptingStream.php 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  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 encrypted with a passed cipher.
  9. */
  10. class AesEncryptingStream 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 $plainText
  32. * @param string $key
  33. * @param CipherMethod $cipherMethod
  34. */
  35. public function __construct(
  36. StreamInterface $plainText,
  37. $key,
  38. CipherMethod $cipherMethod
  39. ) {
  40. $this->stream = $plainText;
  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() && $plainTextSize !== null) {
  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.
  62. $padding = self::BLOCK_SIZE - $plainTextSize % self::BLOCK_SIZE;
  63. return $plainTextSize + $padding;
  64. }
  65. return $plainTextSize;
  66. }
  67. public function isWritable(): bool
  68. {
  69. return false;
  70. }
  71. public function read($length): string
  72. {
  73. if ($length > strlen($this->buffer)) {
  74. $this->buffer .= $this->encryptBlock(
  75. (int)
  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): void
  84. {
  85. if ($whence === SEEK_CUR) {
  86. $offset = $this->tell() + $offset;
  87. $whence = SEEK_SET;
  88. }
  89. if ($whence === SEEK_SET) {
  90. $this->buffer = '';
  91. $wholeBlockOffset
  92. = (int) ($offset / self::BLOCK_SIZE) * self::BLOCK_SIZE;
  93. $this->stream->seek($wholeBlockOffset);
  94. $this->cipherMethod->seek($wholeBlockOffset);
  95. $this->read($offset - $wholeBlockOffset);
  96. } else {
  97. throw new LogicException('Unrecognized whence.');
  98. }
  99. }
  100. private function encryptBlock($length)
  101. {
  102. if ($this->stream->eof()) {
  103. return '';
  104. }
  105. $plainText = '';
  106. do {
  107. $plainText .= $this->stream->read((int) ($length - strlen($plainText)));
  108. } while (strlen($plainText) < $length && !$this->stream->eof());
  109. $options = OPENSSL_RAW_DATA;
  110. if (!$this->stream->eof()
  111. || $this->stream->getSize() !== $this->stream->tell()
  112. ) {
  113. $options |= OPENSSL_ZERO_PADDING;
  114. }
  115. $cipherText = openssl_encrypt(
  116. $plainText,
  117. $this->cipherMethod->getOpenSslName(),
  118. $this->key,
  119. $options,
  120. $this->cipherMethod->getCurrentIv()
  121. );
  122. $this->cipherMethod->update($cipherText);
  123. return $cipherText;
  124. }
  125. }