php / php-src

The PHP Interpreter
https://www.php.net
Other
37.95k stars 7.73k forks source link

Help, I use crypto asymmetric encryption symmetric key, passed to PHP there is an error! #15517

Open DUQIA opened 3 weeks ago

DUQIA commented 3 weeks ago

Description

It has been confirmed that:

js:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>test</title>
</head>
  <form id="login_form" method="post" action="<?php echo htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES); ?>">
    <body>
      <div class="container">
        <input type="text" name="ad_name" id="ad_name" maxlength="20" size="20" required autofocus><br>
        <input type="password" name="ad_pass" id="pass" maxlength="20" size="20" autocomplete="off" required><br>
        <input type="hidden" id="encryptedData" name="encryptedData">
        <input type="submit" id="submit" value="submit">
      </div>
    </body>
  </form>  
  <script>
    // 数据处理
    async function sendEncryptedMessage(data) {
      try {
        const key = await generateKey();
        const rawKey = await exportKey(key);
        console.log('密钥:', key);
        console.log('原始密钥:', rawKey);
        // 公钥接收
        const publicKeyBase64 = "<?php echo $publickey_base64; ?>";
        const publicKeyPem = atob(publicKeyBase64);
        // 公钥加密
        const publicKeyArrayBuffer = pemToArrayBuffer(publicKeyPem);
        const encrypted = await asymmetricEncrypt(rawKey, publicKeyArrayBuffer);
        return {encrypted: encrypted};
      } catch (error) {
            console.error('数据处理失败:', error);
        }
    }

    // 表单提交
    document.getElementById('login_form').addEventListener('submit', async function(event) {
            event.preventDefault(); // 阻止明文提交

            const formData = new FormData(this);
            const data = {};
            formData.forEach((value, key) => {
                data[key] = value;
            });

            const encryptedData = await sendEncryptedMessage(JSON.stringify(data));
            document.getElementById('encryptedData').value = JSON.stringify(encryptedData);
            HTMLFormElement.prototype.submit.call(this); // 确保正确调用表单的 submit 方法
        });

    // 生成AES-GCM对称密钥。
    async function generateKey() {
      try {
        const key = await crypto.subtle.generateKey(
            { name: 'AES-GCM', length: 256 },
            true,
            ['encrypt', 'decrypt']
        );
        return key;
      } catch (error) {
        console.error('密钥生成失败:', error);
      }
    }

    // 导出密钥为原始格式
    async function exportKey(key) {
      try {
        const exportedKey = await crypto.subtle.exportKey('raw', key);
        return new Uint8Array(exportedKey);
      } catch (error) {
        console.error('密钥导出失败:', error);
      }
    }

    // 将PEM格式的公钥转换为ArrayBuffer
    function pemToArrayBuffer(pem) {
      const b64Lines = pem.replace(/-----[^-]+-----/g, "").replace(/\s+/g, "");
      const b64 = atob(b64Lines);
      const buffer = new ArrayBuffer(b64.length);
      const view = new Uint8Array(buffer);
      for (let i = 0; i < b64.length; i++) {
        view[i] = b64.charCodeAt(i);
      }
      return buffer;
    }

    // ArrayBuffer转换为Base64
    function arrayBufferToBase64(buffer) {
      let binary = '';
      const bytes = new Uint8Array(buffer);
      const len = bytes.byteLength;
      for (let i = 0; i < len; i++) {
          binary += String.fromCharCode(bytes[i]);
      }
      return btoa(binary);
    }

    // 公钥加密
    async function asymmetricEncrypt(data, publicKey) {
      try {
        const keyObject = await crypto.subtle.importKey(
            'spki',
            publicKey,
            { name: 'RSA-OAEP', hash: 'SHA-256' },
            false,
            ['encrypt']
        );
        const encrypted = await crypto.subtle.encrypt({ name: 'RSA-OAEP' }, keyObject, new TextEncoder().encode(data));

        const base64String = arrayBufferToBase64(encrypted);
        return base64String;
      } catch (error) {
        console.error('公钥加密失败:', error);
      }
    }
  </script>
</html>

PHP:

<?php
session_start();

// 如果会话中没有RSA密钥对,则生成新的密钥对
if (!isset($_SESSION['private_key']) || !isset($_SESSION['public_key'])) {
  // 生成 RSA 密钥对
  $config = array(
      'digest_alg' => 'sha256',
      'private_key_bits' => 2048,
      'private_key_type' => OPENSSL_KEYTYPE_RSA,
  );
  $res = openssl_pkey_new($config);

  // 获取私钥和公钥
  openssl_pkey_export($res, $privateKey);
  $publicKey = openssl_pkey_get_details($res)['key'];

  // 将密钥对存储在会话中
  $_SESSION['private_key'] = $privateKey;
  $_SESSION['public_key'] = $publicKey;
}

// 从会话中获取私钥和公钥
$privateKey = $_SESSION['private_key'];
$publicKey = $_SESSION['public_key'];
$publickey_base64 = base64_encode($publicKey); // base64编码传送

// 私钥解密
function decryptWithPrivateKey($encryptedKeyBase64, $privateKeyPem) {
  $encryptedKey = base64_decode($encryptedKeyBase64);
  // 将PEM格式的私钥转换为OpenSSL可用的格式
  $privateKey = openssl_pkey_get_private($privateKeyPem);
  echo 'Base64解码后的加密密钥: ' . bin2hex($encryptedKey) . PHP_EOL;
  if (!$privateKey) {
      throw new Exception('加载私钥失败');
  } else {
    echo '私钥加载成功' . PHP_EOL;
  }

  // 使用私钥解密AES-GCM密钥
  $decryptedKey = '';
  $result = openssl_private_decrypt($encryptedKey, $decryptedKey, $privateKey, OPENSSL_PKCS1_OAEP_PADDING); // OPENSSL_PKCS1_OAEP_PADDING OPENSSL_NO_PADDING
  if (!$result) {
      throw new Exception('解密失败: ' . openssl_error_string());
  }
  // 打印解密后的AES-GCM密钥
  echo "解密后的AES-GCM密钥: " . bin2hex($decryptedKey);
  return $decryptedKey;
}

// 消息接收
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  $hashedPassword = json_decode($_POST['encryptedData'], true);
  echo 'cryptoBase64:' . PHP_EOL . bin2hex(base64_decode($hashedPassword['encrypted'])) . PHP_EOL;

  // 私钥解密
  $decryptedKey = decryptWithPrivateKey($hashedPassword['encrypted'], $privateKey);
};
?>

error:

Fatal error: Uncaught Exception: 解密失败: error:0200009F:rsa routines::pkcs decoding error in /home/runner/GigaLowestTransversal/index.php:44 Stack trace: #0 /home/runner/GigaLowestTransversal/index.php(57): decryptWithPrivateKey('MibsVVKMmL1uN9w...', '-----BEGIN PRIV...') #1 {main} thrown in /home/runner/GigaLowestTransversal/index.php on line 44
cmb69 commented 3 weeks ago

PHP 8.1.29

Note that for PHP 8.1 there is only security support, so it's unlikely that the bug (if there is any) would be fixed.

I suggest you try a newer version, and also to provide a SSCCE.

DUQIA commented 3 weeks ago

PHP 8.1.29

Note that for PHP 8.1 there is only security support, so it's unlikely that the bug (if there is any) would be fixed.-----------Deepl翻译-----------请注意,PHP 8.1 只提供安全支持,因此不太可能修复漏洞(如果有的话)。

I suggest you try a newer version, 和 also to provide a SSCCE。-----------Deepl翻译-----------我建议您尝试更新的版本,并提供 SSCCE。

PHP 8.3.9

It's the same code.

error

Fatal error: Uncaught Exception: 解密失败: error:02000079:rsa routines::oaep decoding error in /usr/home/06xinika/domains/vtrab.us.kg/public_html/login.php:44 Stack trace: #0 /usr/home/06xinika/domains/vtrab.us.kg/public_html/login.php(57): decryptWithPrivateKey('lTqECMoIl4OAzaY...', '-----BEGIN PRIV...') #1 {main} thrown in /usr/home/06xinika/domains/vtrab.us.kg/public_html/login.php on line 44
cmb69 commented 3 weeks ago

If you want someone to have a look at this issue, I suggest you provide a simple reproduce case, i.e. without the JavaScript code, but only a simple PHP script, and all the required data (e.g. the keys).

DUQIA commented 3 weeks ago

If you want someone to have a look at this issue, I suggest you provide a simple reproduce case, i.e. without the JavaScript code, but only a simple PHP script, and all the required data (e.g. the keys).

It can be used normally in PHP

PHP

<?php
session_start();

if (!isset($_SESSION['private_key']) || !isset($_SESSION['public_key'])) {
  $config = array(
      'digest_alg' => 'sha256',
      'private_key_bits' => 2048,
      'private_key_type' => OPENSSL_KEYTYPE_RSA,
  );
  $res = openssl_pkey_new($config);

  openssl_pkey_export($res, $privateKey);
  $publicKey = openssl_pkey_get_details($res)['key'];

  $_SESSION['private_key'] = $privateKey;
  $_SESSION['public_key'] = $publicKey;
}

$privateKey = $_SESSION['private_key'];
$publicKey = $_SESSION['public_key'];

$key = openssl_random_pseudo_bytes(32);
echo 'key:' . bin2hex($key) . PHP_EOL;

openssl_public_encrypt($key, $encryptedData, $publicKey, OPENSSL_PKCS1_OAEP_PADDING);

$privateKeyPem = openssl_pkey_get_private($privateKey);
openssl_private_decrypt($encryptedData, $decryptedKey, $privateKeyPem, OPENSSL_PKCS1_OAEP_PADDING);
echo "decryption AES-GCM: " . bin2hex($decryptedKey) . PHP_EOL;
?>

key

key:
533a64cbcfb7be6dde676a748c2ce38b228c8954c5ecead8ac2f7a16b5f9b933 
decryption AES-GCM: 
533a64cbcfb7be6dde676a748c2ce38b228c8954c5ecead8ac2f7a16b5f9b933