更新
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
<?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";
|
||||
Reference in New Issue
Block a user