PHP 项目隐私集合求交 (PSI) 实现方案
PSI(Private Set Intersection)允许双方在不泄露各自数据内容的情况下,找到集合的交集,以下是几种主流实现方案:

基于哈希和布隆过滤器(简单但非严格安全)
<?php
class SimplePSI {
private $salt;
public function __construct($salt = null) {
$this->salt = $salt ?? bin2hex(random_bytes(16));
}
// 哈希并求交
public function intersect($setA, $setB) {
$hashedA = array_map([$this, 'hashItem'], $setA);
$hashedB = array_map([$this, 'hashItem'], $setB);
$intersection = array_intersect($hashedA, $hashedB);
// 还原交集(需要双方都公开哈希值)
return array_keys(array_intersect($hashedA, $hashedB));
}
private function hashItem($item) {
return hash_hmac('sha256', $item, $this->salt);
}
}
// 使用示例
$psi = new SimplePSI();
$setA = ['user1@email.com', 'user2@email.com', 'user3@email.com'];
$setB = ['user2@email.com', 'user4@email.com', 'user5@email.com'];
$common = $psi->intersect($setA, $setB);
print_r($common); // ['user2@email.com']
基于OPRF(Oblivious Pseudo-Random Function)
<?php
require 'vendor/autoload.php';
use Mdanter\Ecc\EccFactory;
use Mdanter\Ecc\Crypto\Key\PrivateKeyInterface;
class OPRFPSI {
private $serverPrivateKey;
private $generator;
public function __construct() {
$adapter = EccFactory::getAdapter();
$this->generator = EccFactory::getSecgCurves()->generator256k1();
$this->serverPrivateKey = $this->generator->createPrivateKey();
}
// 服务器端:接收盲化值并签名
public function serverEvaluate(array $blindedValues): array {
$results = [];
foreach ($blindedValues as $blinded) {
$point = $this->generator->getPublicKeyFrom($blinded['x'], $blinded['y']);
$result = $point->mul($this->serverPrivateKey->getSecret());
$results[] = [
'x' => $result->getX(),
'y' => $result->getY()
];
}
return $results;
}
// 客户端:盲化元素并计算
public function clientEvaluate(array $data, array $serverResults): array {
$hashes = [];
foreach ($data as $i => $item) {
// 去盲化
$unblinded = $this->unblind($serverResults[$i], $this->clientRandomness[$i]);
$hashes[] = hash('sha256', gmp_strval($unblinded->getX()) . $item);
}
return $hashes;
}
}
基于OT(Oblivious Transfer)的实现
<?php
class OTBasedPSI {
// 使用Naor-Pinkas OT协议
public function otExtension(array $receiverInput, array $senderSet) {
$commonElements = [];
// 实现1-out-of-2 OT
foreach ($receiverInput as $item) {
$choiceBit = $this->hashToBit($item);
// Bob(接收方)获得一个消息
$received = $this->obliviousTransfer(
$item,
$choiceBit,
$senderSet
);
if ($received === $item) {
$commonElements[] = $item;
}
}
return $commonElements;
}
private function hashToBit($item) {
return hexdec(substr(hash('sha256', $item), 0, 1)) % 2;
}
private function obliviousTransfer($choice, $bit, $senderSet) {
// 简化实现,实际应使用密码学安全协议
return $senderSet[$bit] ?? null;
}
}
使用PHP扩展 - libPSI包装
<?php
// 假设有一个C扩展包装了libPSI
class LibPSIWrapper {
private $psiInstance;
public function __construct(int $role) {
// role: 0 = client, 1 = server
$this->psiInstance = new PSI($role);
}
public function setup(int $numItems) {
$this->psiInstance->setup($numItems);
}
public function addItems(array $items) {
foreach ($items as $item) {
$this->psiInstance->addItem($item);
}
}
public function getIntersection(): array {
$result = [];
$this->psiInstance->computeIntersection(function($item) use (&$result) {
$result[] = $item;
});
return $result;
}
}
完整示例:Web API实现
<?php
// PSIEndpoint.php
class PSIEndpoint {
private $redis;
private $psiServer;
public function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
$this->psiServer = new SimplePSI();
}
public function handleRequest() {
$action = $_POST['action'] ?? '';
switch ($action) {
case 'client_submit':
return $this->clientSubmit();
case 'server_submit':
return $this->serverSubmit();
case 'compute':
return $this->computeIntersection();
default:
throw new Exception('Invalid action');
}
}
private function clientSubmit() {
$sessionId = bin2hex(random_bytes(16));
$data = json_decode($_POST['data'], true);
// 存储客户端数据(盲化后)
$blindedData = array_map(function($item) {
return hash('sha256', $item . $this->getSalt());
}, $data);
$this->redis->setex("psi:client:{$sessionId}", 3600, json_encode($blindedData));
return json_encode(['session_id' => $sessionId]);
}
private function serverSubmit() {
$sessionId = $_POST['session_id'];
$data = json_decode($_POST['data'], true);
$blindedData = array_map(function($item) {
return hash('sha256', $item . $this->getSalt());
}, $data);
$this->redis->setex("psi:server:{$sessionId}", 3600, json_encode($blindedData));
return json_encode(['status' => 'ok']);
}
private function computeIntersection() {
$sessionId = $_POST['session_id'];
$clientData = json_decode($this->redis->get("psi:client:{$sessionId}"), true);
$serverData = json_decode($this->redis->get("psi:server:{$sessionId}"), true);
$commonHashes = array_intersect($clientData, $serverData);
return json_encode([
'intersection_count' => count($commonHashes),
'intersection_hashes' => array_values($commonHashes)
]);
}
private function getSalt() {
return "YOUR_SECURE_SALT";
}
}
性能优化与最佳实践
<?php
class OptimizedPSI {
// 使用批量处理提高性能
public function batchIntersect(array $clientSets, array $serverSet) {
$results = [];
// 1. 使用布隆过滤器预处理服务端数据
$bloom = new BloomFilter(1024, 3); // 1024 bits, 3 hash functions
foreach ($serverSet as $item) {
$bloom->add($item);
}
// 2. 快速过滤客户端数据
foreach ($clientSets as $clientId => $clientSet) {
$candidates = [];
foreach ($clientSet as $item) {
if ($bloom->has($item)) {
$candidates[] = $item;
}
}
// 3. 精确匹配
$exactMatch = array_intersect($candidates, $serverSet);
$results[$clientId] = $exactMatch;
}
return $results;
}
// 内存优化 - 处理大数据集
public function streamIntersection($clientStream, $serverSet) {
$hashedServer = array_map('sha256', $serverSet);
$serverHashSet = array_flip($hashedServer);
$intersection = [];
foreach ($clientStream as $item) {
$hash = hash('sha256', $item);
if (isset($serverHashSet[$hash])) {
$intersection[] = $item;
}
}
return $intersection;
}
}
安装与部署建议
-
生产环境要求:
- PHP 7.4+ 或 8.0+
- OpenSSL 扩展
- GMP 或 BCMath 扩展(用于大数运算)
- 建议使用 PHP FFI 调用 C 库
-
性能考虑:
- 小数据集(<10k):使用哈希+布隆过滤器方案
- 中等数据集(10k-100k):使用OPRF方案
- 大数据集(>100k):使用OT扩展或专用C库
-
安全注意事项:
- 始终使用 HTTPS 传输
- 使用安全的随机数生成器
- 实施速率限制防止暴力攻击
- 定期更换加密密钥
实现提供了不同安全级别和性能特征的PSI方案,你可以根据具体需求选择合适的实现,对于生产环境,建议使用经过审计的密码学库,如libPSI或emp-toolkit的PHP绑定。