Repository navigation
Closed as duplicate
Closed as duplicate
Copy link
Description
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
cmb69 commentedon Aug 21, 2024
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 commentedon Aug 22, 2024
PHP 8.3.9
It's the same code.
error
cmb69 commentedon Aug 23, 2024
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 commentedon Aug 24, 2024
It can be used normally in PHP
PHP
key
kamil-tekiela commentedon Nov 9, 2026
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):
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.