Crypto.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. <?php
  2. namespace PhpMyAdmin\Crypto;
  3. use Exception;
  4. use phpseclib\Crypt\AES;
  5. use phpseclib\Crypt\Random;
  6. final class Crypto
  7. {
  8. /** @var bool */
  9. private $hasRandomBytesSupport;
  10. /** @var bool */
  11. private $hasSodiumSupport;
  12. /**
  13. * @param bool $forceFallback Force the usage of the fallback functions.
  14. */
  15. public function __construct($forceFallback = false)
  16. {
  17. $this->hasRandomBytesSupport = ! $forceFallback && is_callable('random_bytes');
  18. $this->hasSodiumSupport = ! $forceFallback
  19. && $this->hasRandomBytesSupport
  20. && is_callable('sodium_crypto_secretbox')
  21. && is_callable('sodium_crypto_secretbox_open')
  22. && defined('SODIUM_CRYPTO_SECRETBOX_NONCEBYTES')
  23. && defined('SODIUM_CRYPTO_SECRETBOX_KEYBYTES');
  24. }
  25. /**
  26. * @param string $plaintext
  27. *
  28. * @return string
  29. */
  30. public function encrypt($plaintext)
  31. {
  32. if ($this->hasSodiumSupport) {
  33. return $this->encryptWithSodium($plaintext);
  34. }
  35. return $this->encryptWithPhpseclib($plaintext);
  36. }
  37. /**
  38. * @param string $ciphertext
  39. *
  40. * @return string
  41. */
  42. public function decrypt($ciphertext)
  43. {
  44. if ($this->hasSodiumSupport) {
  45. return $this->decryptWithSodium($ciphertext);
  46. }
  47. return $this->decryptWithPhpseclib($ciphertext);
  48. }
  49. /**
  50. * @return string
  51. */
  52. private function getEncryptionKey()
  53. {
  54. global $PMA_Config;
  55. $keyLength = $this->hasSodiumSupport ? SODIUM_CRYPTO_SECRETBOX_KEYBYTES : 32;
  56. $key = $PMA_Config->get('URLQueryEncryptionSecretKey');
  57. if (is_string($key) && mb_strlen($key, '8bit') === $keyLength) {
  58. return $key;
  59. }
  60. $key = isset($_SESSION['URLQueryEncryptionSecretKey']) ? $_SESSION['URLQueryEncryptionSecretKey'] : null;
  61. if (is_string($key) && mb_strlen($key, '8bit') === $keyLength) {
  62. return $key;
  63. }
  64. $key = $this->hasRandomBytesSupport ? random_bytes($keyLength) : Random::string($keyLength);
  65. $_SESSION['URLQueryEncryptionSecretKey'] = $key;
  66. return $key;
  67. }
  68. /**
  69. * @param string $plaintext
  70. *
  71. * @return string
  72. */
  73. private function encryptWithPhpseclib($plaintext)
  74. {
  75. $key = $this->getEncryptionKey();
  76. $cipher = new AES(AES::MODE_CBC);
  77. $iv = $this->hasRandomBytesSupport ? random_bytes(16) : Random::string(16);
  78. $cipher->setIV($iv);
  79. $cipher->setKey($key);
  80. $ciphertext = $cipher->encrypt($plaintext);
  81. $hmac = hash_hmac('sha256', $iv . $ciphertext, $key, true);
  82. return $hmac . $iv . $ciphertext;
  83. }
  84. /**
  85. * @param string $encrypted
  86. *
  87. * @return string|null
  88. */
  89. private function decryptWithPhpseclib($encrypted)
  90. {
  91. $key = $this->getEncryptionKey();
  92. $hmac = mb_substr($encrypted, 0, 32, '8bit');
  93. $iv = mb_substr($encrypted, 32, 16, '8bit');
  94. $ciphertext = mb_substr($encrypted, 48, null, '8bit');
  95. $calculatedHmac = hash_hmac('sha256', $iv . $ciphertext, $key, true);
  96. if (! hash_equals($hmac, $calculatedHmac)) {
  97. return null;
  98. }
  99. $cipher = new AES(AES::MODE_CBC);
  100. $cipher->setIV($iv);
  101. $cipher->setKey($key);
  102. return $cipher->decrypt($ciphertext);
  103. }
  104. /**
  105. * @param string $plaintext
  106. *
  107. * @return string
  108. */
  109. private function encryptWithSodium($plaintext)
  110. {
  111. $key = $this->getEncryptionKey();
  112. $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
  113. $ciphertext = sodium_crypto_secretbox($plaintext, $nonce, $key);
  114. return $nonce . $ciphertext;
  115. }
  116. /**
  117. * @param string $encrypted
  118. *
  119. * @return string|null
  120. */
  121. private function decryptWithSodium($encrypted)
  122. {
  123. $key = $this->getEncryptionKey();
  124. $nonce = mb_substr($encrypted, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
  125. $ciphertext = mb_substr($encrypted, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
  126. try {
  127. $decrypted = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
  128. } catch (Exception $e) {
  129. return null;
  130. }
  131. if ($decrypted === false) {
  132. return null;
  133. }
  134. return $decrypted;
  135. }
  136. }