AesEncryptingStream.php 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  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()
  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()
  68. {
  69. return false;
  70. }
  71. public function read($length)
  72. {
  73. if ($length > strlen($this->buffer)) {
  74. $this->buffer .= $this->encryptBlock(
  75. self::BLOCK_SIZE * ceil(($length - strlen($this->buffer)) / self::BLOCK_SIZE)
  76. );
  77. }
  78. $data = substr($this->buffer, 0, $length);
  79. $this->buffer = substr($this->buffer, $length);
  80. return $data ? $data : '';
  81. }
  82. public function seek($offset, $whence = SEEK_SET)
  83. {
  84. if ($whence === SEEK_CUR) {
  85. $offset = $this->tell() + $offset;
  86. $whence = SEEK_SET;
  87. }
  88. if ($whence === SEEK_SET) {
  89. $this->buffer = '';
  90. $wholeBlockOffset
  91. = (int) ($offset / self::BLOCK_SIZE) * self::BLOCK_SIZE;
  92. $this->stream->seek($wholeBlockOffset);
  93. $this->cipherMethod->seek($wholeBlockOffset);
  94. $this->read($offset - $wholeBlockOffset);
  95. } else {
  96. throw new LogicException('Unrecognized whence.');
  97. }
  98. }
  99. private function encryptBlock($length)
  100. {
  101. if ($this->stream->eof()) {
  102. return '';
  103. }
  104. $plainText = '';
  105. do {
  106. $plainText .= $this->stream->read($length - strlen($plainText));
  107. } while (strlen($plainText) < $length && !$this->stream->eof());
  108. $options = OPENSSL_RAW_DATA;
  109. if (!$this->stream->eof()
  110. || $this->stream->getSize() !== $this->stream->tell()
  111. ) {
  112. $options |= OPENSSL_ZERO_PADDING;
  113. }
  114. $cipherText = openssl_encrypt(
  115. $plainText,
  116. $this->cipherMethod->getOpenSslName(),
  117. $this->key,
  118. $options,
  119. $this->cipherMethod->getCurrentIv()
  120. );
  121. $this->cipherMethod->update($cipherText);
  122. return $cipherText;
  123. }
  124. }