Skip to content

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

Closed as duplicate

Description

@DUQIA

Description

It has been confirmed that:

  • PHP 8.1.29
  • The private key matches the public key
  • Data transfer is complete
  • Base64 encoding used for transmission

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

Activity

  1. cmb69 commented on Aug 21, 2024

    @cmb69
    Member

    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.

  2. DUQIA commented on Aug 22, 2024

    @DUQIA
    Author

    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
    
  3. cmb69 commented on Aug 23, 2024

    @cmb69
    Member

    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).

  4. DUQIA commented on Aug 24, 2024

    @DUQIA
    Author

    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
    
  5. kamil-tekiela commented on Nov 9, 2026

    @kamil-tekiela
    Member

    This is not a bug in PHP. WebCrypto's RSA-OAEP with hash: 'SHA-256' uses SHA-256 for OAEP. Before PHP 8.5, openssl_private_decrypt() always used OpenSSL's default, SHA-1. PHP 8.5.0 added a $digest_algo parameter (GH-19223, see GH-19076):

    openssl_private_decrypt($encrypted, $key, $privateKey, OPENSSL_PKCS1_OAEP_PADDING, 'sha256');
    

    Also, new TextEncoder().encode(rawKey) encrypts the text "151,15,54,..." and not the 32 raw key bytes. Pass rawKey directly to crypto.subtle.encrypt().

    Closing as a duplicate of GH-19076.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions