$bits, 'private_key_type' => OPENSSL_KEYTYPE_RSA, ]); if ($res === false) { fwrite(STDERR, "生成密钥失败:" . openssl_error_string() . "\n"); exit(1); } if (! openssl_pkey_export($res, $privatePem)) { fwrite(STDERR, "导出私钥失败\n"); exit(1); } $details = openssl_pkey_get_details($res); $publicPem = $details['key'] ?? ''; if ($publicPem === '') { fwrite(STDERR, "导出公钥失败\n"); exit(1); } if (! is_dir(dirname($privFile))) { mkdir(dirname($privFile), 0755, true); } if (! is_dir(dirname($pubFile))) { mkdir(dirname($pubFile), 0755, true); } file_put_contents($privFile, $privatePem); chmod($privFile, 0600); file_put_contents($pubFile, $publicPem); // 写回内置公钥常量:文件可被整包升级覆盖,常量是最后的信任锚 $builtinOk = false; if (is_file($builtin)) { $src = (string) file_get_contents($builtin); // 用正则而不是字符串定位:Windows 下仓库可能检出 CRLF, // 硬匹配 "\nPEM;\n" 会漏。$count 用于判断是否真的替换到了。 $new = preg_replace_callback( "/(private const BUILTIN_PUBLIC_KEY\s*=\s*<<<'PEM'\R)(.*?)(\R\s*PEM;)/s", static function (array $m) use ($publicPem): string { $raw = trim($publicPem); // PEM 里含 - 与字母数字,作为 replacement 无需转义,但统一按行尾惯例 CRLF/LF 保持不变 return $m[1] . $raw . $m[3]; }, $src, 1, $count ); if ($count > 0 && $new !== null) { file_put_contents($builtin, $new); $builtinOk = true; } } echo "=== 发布包签名密钥已生成 ===\n"; echo "[私钥] {$privFile} (仅打包机保留,切勿入库/进包)\n"; echo "[公钥] {$pubFile} (随版本发布给客户机)\n"; echo "[内置] {$builtin} → " . ($builtinOk ? '已写入 BUILTIN_PUBLIC_KEY' : '写入失败,请手工粘贴公钥到该常量') . "\n"; echo "[位数] {$bits}\n\n"; echo "下一步:\n"; echo " 1) 备份私钥到离线介质(丢了无法签发新版本,只能换 key 并强制轮换)\n"; echo " 2) php scripts/package_framework.php 1.1.2 # 打包时会自动给三个 zip 追加签名\n"; echo " 3) php scripts/package_verify.php build/framework/ywxapp-1.1.2.zip 1.1.2 # 发布前自检\n";