Files
zyt/server/tests/QywxPromotionCodeCipherTest.php
T
2026-08-31 15:17:34 +08:00

77 lines
3.2 KiB
PHP

<?php
declare(strict_types=1);
use app\common\service\qywx\QywxPromotionCodeCipher;
require dirname(__DIR__) . '/vendor/autoload.php';
require dirname(__DIR__) . '/vendor/topthink/framework/src/helper.php';
// 独立临时App根目录,不加载项目配置或业务数据库。
if (($argv[1] ?? '') === '--worker') {
new think\App($argv[2]);
echo (new QywxPromotionCodeCipher())->encrypt('cipher-concurrency-test');
exit(0);
}
function cipherCheck(bool $ok, string $message): void
{
if (!$ok) {
throw new RuntimeException($message);
}
}
$root = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'qywx_cipher_test_' . bin2hex(random_bytes(8));
mkdir($root, 0700);
new think\App($root);
$processes = [];
try {
// 多个进程首次启动必须共享同一完整密钥,不能读到空文件或覆盖对方的密钥。
for ($i = 0; $i < 6; $i++) {
$pipes = [];
$process = proc_open([PHP_BINARY, __FILE__, '--worker', $root],
[0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes,
null, null, ['bypass_shell' => true]);
cipherCheck(is_resource($process), 'start isolated cipher worker');
fclose($pipes[0]);
$processes[] = [$process, $pipes];
}
$cipher = new QywxPromotionCodeCipher();
$encrypted = [];
foreach ($processes as [$process, $pipes]) {
$value = stream_get_contents($pipes[1]);
$error = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
cipherCheck(proc_close($process) === 0 && $error === '', 'cipher worker completed without error');
cipherCheck($cipher->decrypt($value) === 'cipher-concurrency-test', 'concurrent processes share one persisted key');
$encrypted[] = $value;
}
$processes = [];
cipherCheck(count(array_unique($encrypted)) === 6, 'fresh nonce for every encryption');
$raw = base64_decode($encrypted[0], true);
$raw[15] = chr(ord($raw[15]) ^ 1);
$rejected = false;
try { $cipher->decrypt(base64_encode($raw)); } catch (RuntimeException) { $rejected = true; }
cipherCheck($rejected, 'tampered authentication tag rejected');
$rejected = false;
try { (new QywxPromotionCodeCipher(str_repeat('wrong-key', 8)))->decrypt($encrypted[0]); }
catch (RuntimeException) { $rejected = true; }
cipherCheck($rejected, 'wrong key cannot decrypt');
file_put_contents($root . '/runtime/qywx_promotion_private/welcome.key', 'incomplete-key');
$rejected = false;
try { (new QywxPromotionCodeCipher())->encrypt('test'); } catch (RuntimeException) { $rejected = true; }
cipherCheck($rejected, 'damaged persisted key fails closed instead of silently rotating');
} finally {
foreach ($processes as [$process, $pipes]) {
foreach ($pipes as $pipe) { if (is_resource($pipe)) { fclose($pipe); } }
if (is_resource($process)) { proc_close($process); }
}
$keyPath = $root . '/runtime/qywx_promotion_private/welcome.key';
if (is_file($keyPath)) { unlink($keyPath); }
if (is_dir(dirname($keyPath))) { rmdir(dirname($keyPath)); }
if (is_dir($root . '/runtime')) { rmdir($root . '/runtime'); }
rmdir($root);
}
echo "QYWX_PROMOTION_CODE_CIPHER_OK\n";