APSI - 1
最近在看【Labeled PSI from Homomorphic Encryption with Reduced
Computation and Communication】的论文,看完后头大,现结合自身的开源库APSI来学习,希望能豁然开朗!
Introduction
unbalanced-PSI和labled-PSI
私人集合交集(PSI)指的是一种功能,其中双方各自持有一组私有数据,可以检查他们有哪些共同数据,而不向对方透露任何其他信息。集合大小的上限假定为公开,不受保护。
APSI(非对称PSI)库根据原论文中描述的协议,为非对称集合提供PSI功能。例如,在许多情况下,一方可能持有一个包含数百万条记录的大型数据集,而另一方希望查询数据集中的一条数据或者多条数据。我们称之为非平衡(unbalanced)模式下的APSI。
然而,在许多情况下,查询者还希望检索出每个匹配数据所对应的其他的一些信息。可以将其视为输入键,想要键所对应的值。我们使用item和 label来指代这样一个键值存储中的键和值,我们称它为labled模式下的APSI。
注:labeled-PSI在非平衡模式下通信和计算更加高效。
Sender and Receiver
我们使用发送方和接收方来表示APSI协议中的双方:发送方将结果发送给接收方。例如,在一个常见的用例中,服务器中存储一个查找表,多个客户端可以使用加密的数据查询该表。在这种情况下,服务器充当发送方,客户端充当(独立的)接收方。
How APSI Works
Homomorphic Encryption
APSI使用一种相对较新的加密技术,称为同态加密,允许直接对加密数据执行计算。这种计算的结果仍然是加密的,只能由密钥所有者解密。当前有许多具有不同性质的同态加密方案;APSI使用在Microsoft SEAL库中实现的BFV加密方案。
Computation on Encrypted Data
Microsoft SEAL允许使用深度有限的算术电路(即Leveled-FHE加密方案)(执行有限次的密文加法和乘法,而不是对加密数据进行任意计算。这些计算可以以批处理的方式进行,其中单个Microsoft SEAL密文是对一个长的向量(多个明文)进行加密得到的,并对向量中的每个值同时独立地进行计算;批处理对于APSI提升性能至关重要。
Noise Budget
通过每个密文携带的噪声预算来确定加密数据的计算能力。新加密的密文有一定数量的噪声预算,然后在计算中消耗,尤其是乘法计算。一旦密文的噪声预算被完全消耗,它就无法再被正确解密。为了支持更大乘法深度的计算,有必要从更大的初始噪声预算开始,这可以通过适当更改加密参数(encryption parameters)来完成。
Encryption Parameters
同态加密方案(如BFV)难以配置以获得最佳性能。APSI要求用户明确提供Microsoft SEAL加密参数。所以我们需要在这里简单描述一下。有关更多详细信息,请读者参考Microsoft SEAL存储库中的示例。我们描述了用户应该熟悉的三个重要加密参数。
1、plain_modulus
明文模数,是一个素数,满足模 2*poly_modulus_degree=1
,并在在BFV方案中定义为有限域的数据类型,例如,如果plain_modulus=65537
,一个16bit的素数,则该方案中的明文最大值为65537,一个很大的plain_modulus
会导致很快的消耗噪音预算,推荐在使用较小的plain_modulus
。
2、poly_modulus_degree
模多项式次数,是一个2的次幂,决定了一个SEAL明文中可以编码多少个原始明文。一般取值:2048, 4096, 8192 和16384。在批处理(batch)中可以理解为:计算数千次原始明文相当于计算一次SEAL明文的代价。poly_modulus_degree
也影响加密方案的安全性,如果其他参数保持不变,poly_modulus_degree
越大,方案越安全!
3、coeff_modulus
密文模数,是一组素数,决定了"新鲜密文""的噪音预算。在SEAL库中,coeff_modulus
很少会被直接给出,而是根据位数去生成。在APSI中,coeff_modulus
至少有两个素数组成,最好是少的,最合理的使用2~8个素数,单个素数可以达到60bit,噪音预算和这些素数的总比特呈线性关系。coeff_modulus
也会影响方案的安全性:如果其他参数保持不变,总比特越大,安全性越低。所以为了获得更大的计算力,也就是说要提升噪音预算,就要增加coeff_modulus
的总比特数,为了安全性,也可以增大poly_modulus_degree
,这可能会对批处理的性能产生影响,所以计算性能也会改变。
幸运的是,SEAL为防止用户修改不安全的参数,它会进行检查,如果给定poly_modulus_degree
,那么coeff_modulus
总比特数不能超过以下上限:
在APSI中,用户需要给定coeff_modulus
总比特数,因此,上表对于避免Microsoft SEAL抛出不必要的异常非常有帮助。
Theory
Naive Idea
APSI的基本思想如下:假设sender持有一组\({Y_i}\)——每个项都模plain_modulus
——receiver持有一个X——也是模plain_modulus
;receiver选择一个密钥,加密X,获得密文\(Q=Enc(X)\),然后发送给sender;sender计算这个匹配多项式\(M(x)=(x-Y_0)(x-Y_1)...(x-Y_n)\),其中\(x=Q\),这里的\(Y_i\)是sender持有的未加密的数据,所以这里是(密文-明文)(密文*密文)用到了加法和乘法同态,所以结果解密后如果为0,则表示匹配上了,都则就是没有匹配上。sender无法获知X的信息,因为是加密的,只有密钥持有方可以解密。
下面图中sender的数据集为X,Receiver是Y。
上面提到的一个问题是,计算的乘法深度非常高。sender拥有数百万甚至数亿件数据并不罕见。这将需要非常高的初始噪声预算,随后需要非常大的加密参数,需要非常大的计算开销。
Lowering the Depth
【Partition】第一步是找出降低计算乘法深度的方法。首先,sender刚开始就把集合分成s份等大小的子集,然后分别计算每一小份的求交多项式,最后生成s个结果\(M_i(Q)\),所有的结果发送给receiver。但是通信量增加了s倍。事实证明,该方法可以减少加密参数的大小。
【Batch】第二步就是使用批处理(batch),sender将上面的拆分的每一个集合进一步分解为poly_modulus_degree
个大小相同的小集合,然后使用batch,将每一个小集合打包为一个明文多项式;同样receiver也使用batch,将自己的集合打包-加密为到一个SEAL密文\(Q=Enc([X,X,...,X])\)中。sender就可以“批量”计算\(Q\),结果就是计算复杂度提升了poly_modulus_degree
倍,显著降低了乘法深度。
【Window】第三步是让receiver计算更多的次幂,然后加密这些幂,发送给sender。假设sender希望计算的求交多项式\(M(x)\)的次数为d,然后sender需要知道\(Q\)的所有次幂,直到d次幂。虽然sender可以根据给出的Q,总是能计算出\(Q^2,....,Q^d\) ,但是虽给出了上面两个优化,但是乘法深度依旧很高。相反,如果receiver预先计算查询的部分次幂,然后加密发送给sender(包括\(Q\)),如果选用的次幂得当,sender可以用很小的计算深度计算出所有的次幂。双方的通信成本增加的部分就是receiver自己加密的次幂。使用这种方法减少求交多项式的乘法深度,进而减少加密参数的大小!
Cuckoo Hashing
上述的receiver给出的数据是一个,当receiver拥有更多数据时,似乎每个receiver的数据都需要重复一次查询,因此如果receiver持有10000个项目,通信和计算成本将大幅增加。
下面使用一种叫做布谷鸟hash的技术实现。布谷鸟hash使用多个hash函数(通常为2–4)来实现非常高的压缩率,以获得每个箱子(bin)为1的散列表。而不是像上面描述的那样,通过反复将单个item\(X\)在每一个batching slot中进而打包-加密为\(Q\)。receiver通过布谷鸟hash将多个数据\({X_i}\)插入到大小为poly_modulus_degree
的hash表中,其中每个箱子(bin)大小为1。最后将hash表加密为密文Q,发送给sender。
sender 使用不同的布谷鸟hash函数将它的数据集\({Y_i}\)插入到一个更大hash表中,其中每个箱子(bin)的大小是任意的。(这里,没有利用布谷鸟的“踢出”特性),因为这里使用多个布谷鸟hash函数。sender不知道receiver发送来的加密数据是用哪个布谷鸟hash函数。如果布谷鸟hash函数的个数为H,这将会使得sender方的数据集的大小增加H倍。sender方在使用布谷鸟hash后,将生成的hash表按照上面的“降低乘法深度”的方法分解为若干部分,并在收到receiver发来的Q后继续执行。
使用布谷鸟hash的好处是巨多的,布谷鸟hash允许将多个receiver的item打包到一个密文数据Q中,例如:receiver可以将数千个查询item\({X_i}\)插入到一个密文查询Q中。sender可以同时对这些密文查询Q进行匹配查询,只是sender方的数据量增加了H倍。
large items
每一个item都需要模plain_modulus
,不幸的的是,plain_modulus
通常为16-30bit,在SEAL中总是小于60bit,大的plain_modulus
会消耗很多的噪音预算,减缓计算的性能。另一方面,我们还需要支持任意长度的item,例如:一个item可能是一个完整的文档、一个电子邮件、一个地址或者车牌号,两个办法可以解决:
第一个办法是对双方的item都是进行一次hash(CCS1017中用到的方法),这样就会有一个标准上限长度。我们hash到128 bit,并根据需要将hash值截断到较短的长度(足够大以防冲突)。所支持截断后的hash值最短长度为80bit,这依旧超出了plain_modulus
。
第二个办法是将每个item分成多份,并将其分别编码到连续的batching slots中,也就是说,如果plain_modulus
是一个B bit的素数,我们将一个item的B-1 bit写入一个batching slots中,下一个B-1 bit写入下一个batching slots中。【这里为什么是\(B-1\)bit,我不是很清楚,一般都是取n个为一个batch】缺点是一个批处理的明文/密文只能保存poly_modulus_degree
个item【poly_modulus_degree即slot的个数】,通常一个item使用4或8个slot。例如:plain_modulus
是一个20 bit的素数,每4个slot可以将长度为80 bit的item编码,poly_modulus_degree/4
个的item可以打包-加密为一个密文\(Q\),现在receiver要查询的item就少了很多了。
这上面描述的batch,和我理解的有些出入,不太能理解
OPRF
不幸的是,还存在一下问题:
1、是否允许receiver知道查询是否匹配?
2、即使没有匹配上,匹配多项式的结果能反映出sender的数据信息,这是不可接受的!
解决的办法就是使用一个Oblivious Pseudo-Random Function,简单的点说就是OPRF,一个OPRF可以被认为是一个带密钥的hash函数OPRF(s, -)
,只有sender知道s,这里指的是sender的密钥,此外receiver可以获得OPRF(s, X)
,不知道s和带关键词的hash函数OPRF(s, -)
,而sender也不知道X。
这种方法是很简单的,receiver将自己的itemX
hash到一个比较安全的椭圆曲线上的点A上。接着,receiver选择一个秘密数字r
,计算出点\(B=rA\)(多倍点),将其发送给sender。sender使用自己的密钥s
,计算出\(C=sB\),将其发送给receiver。收到C后,receiver计算其逆\(r^{-1}\) mod 椭圆曲线的阶,然后进一步计算\(r^{-1}C=r^{-1}srA=sA\),然后receiver就从该点提取出OPRF(s, X)
。
上面大致介绍了OPRF的原理
sender知道s,所以能轻易获得{OPRF(s, Y_i)}
;receiver需要和sender通信来获得{OPRF(s, X_i)}
,一旦receiver获得了这些值,该协议就如上进行。使用OPRF,receiver学习"检索结果这个问题就解决了。由于所有的item都是用了sender才知道的hash函数,所以receiver将从sender发送hash 后的item中得不到任何信息。事实上,sender上的数据集不是隐私信息,原则上是可以发送给receiver,同态加密仅仅保护了receiver的数据隐私。(OPRF保护了两方的隐私)
另外注意:我们选用的hash函数OPRF(s, -)
有256 bit的输出,用ItemHash(s, -)
来表示前128 bit,我们使用{ItemHash(s, X_i)}
来表示items,而不是{OPRF(s, X_i)}
,原因在 Label Encryption
中给出。
Paterson-Stockmeyer
在某些情况下,sender可以使用Paterson Stockmeyer算法来计算匹配和标签多项式。以下面一个简答的匹配多项式为例:
对于一个加密请求\(Q=Enc(X)\),为了计算\(M(Q)\),sender必须计算\(Q\)的所有次幂,\(Q-Q^{14}\),接下来,将Q的次幂乘以M中对应的系数,然后将结果相加起来。说点人话就是:
1、首先计算\(Q ,Q^2 ,... , Q^{14}\)
2、然后计算\(1+2Q+...+15Q^{14}\)
注意:在第一步中,计算Q得所有次幂时,涉及到大量的密文乘密文,第二步中,涉及到密文乘明文,这比第一步快很多。如果sender将数据集拆成S等份,那么次幂就需要计算一次,对于S份计算时可以重复使用。(每一份计算都能用到这些次幂)
这是Paterson-Stockmeyer算法的特例:
为了计算\(M(Q)\),sender需要计算\(X, X^2, X^3, X^4, X^8, X^{12}\)的加密,一共需要计算6个次幂,而不是之前的14个;接下来计算四个三次多项式(密文明文、密文+明文、密文+密文),然后将上面三个多项式的结果分别乘以\({x^4, X^8, X^{12}}\)(密文密文),然后将其结果相加(密文+密文)。与之前方法的区别就是,密文密文的次数减少了:之前的方法是计算14个次幂,当前的方法是计算6个次幂。另外一个不同之处,是密文密文的次数与S
成正比。
将上面的三次多项式整合一起就行,但是我们能够选择具有更低次或更高次的多项式,不同的选择会导致更好的通信-计算效果。我们将这些内部多项式(比如:\(1 + 2x + 3x^2 + 4x^3)\))的阶叫做(Paterson-Stockmeyer)低阶low-degree
(比如:\({x, X^2, X^3}\));显而易见,sender还需要计算low-degree + 1
次幂,我们把low-degree + 1
叫做(Paterson-Stockmeyer) 高阶high-degree
(比如:\({x^4, X^8, X^{12}}\))。
用户想要确保计算内部多项式的乘法深度与计算高阶次幂的乘法深度相匹配(需要额外考虑明文*密文的深度),这将是最优的乘法深度!
False Positives
直译: 假阳性?我是在修仙么?可以看成错误率吧
在某些情况下,协议可能会导致假阳性匹配。例如,一个item被分成P份parts,每份长度为\(B-1\) bit。在参数初始化时,sender的hash表中箱子(bin)的大小K可能很大,以至于每一份part出现在对应箱子(bin)中的概率很大(因为P很大,所以每一份就很小),这是随机的,而不是真正的匹配,这种概率还是很大的。如果P很小,则receiver的每份part很有可能出现在sender的hash表中的概率是不可忽略的(很大的)。若receiver每次查询发送上千个item,则协议执行多次后,False Positives就变成了一个常见的现象!
有多种方法解决:receiver的单个item的假阳率的负对数大概为P(B - 1 - log2(K))
,所以我们可以额通过增加plain_modulus
和item的分割数P,或者减少sender的箱子(bin)的大小K来避免(降低)假阳率!
Practice
我们现在开始说明该理论是如何在APSI中实现的。我们的讨论只考虑unlabeled mode;labeled mode并没有太大的不同,我们将在后面讨论。为了简单起见,我们假设已经执行了OPRF步骤,并且简化情况:receiver仅需要单个密文(二次幂)。例如:poly_modulus_degree=16
,每个item使用2个批处理槽(slot),布谷鸟hash表的大小为8,这些参数太小,在实际中不会这么使用,但会帮助我们理解。假设receiver想进行如下查询:
Receiver's query vector
[ item92 ]
[ item14 |
[ item79 ]
[ item3 ]
[ item401 ]
经过布谷鸟hash后,receiver看到的是这样。16个向量打包加密为一个SEAL密文
前面说hash表的大小是8,现在怎么成16了?
Receiver's cuckoo hash table
[ item79-part1 ]
[ item79-part2 ]
[ empty ]
[ empty ]
[ item14-part1 ]
[ item14-part2 ]
[ item92-part1 ]
[ item92-part2 ] ==> query-ctxt
[ item401-part1 ]
[ item401-part2 ]
[ empty ]
[ empty ]
[ empty ]
[ empty ]
[ item3-part1 ]
[ item3-part2 ]
sender创建一个大的表,并将其item独立的分到若干个bin bundles中,对于每个bin bundle,单独计算查询query-ctxt,计算匹配多项式,这是按照Lowering the Depth中所提的第一种方法,为了简单起见,我们忽略了一个事实,即sender必须使用所有布谷鸟hash函数来插入每个项。
这里的桶(bundle)表示有“几张表”,其实还是一张表;箱子(bin)表示一行中能放几个item
Sender's big hash table
[ item416-part1 | item12-part1 ][ item71-part1 | item611-part1 ]
[ item416-part2 | item12-part2 ][ item71-part2 | item611-part2 ]
[ item125-part1 | item9-part1 ][ item512-part1 | empty ]
[ item125-part2 | item9-part2 ][ item512-part2 | empty ]
[ item500-part1 | item277-part1 ][ item14-part1 | item320-part1 ]
[ item500-part2 | item277-part2 ][ item14-part2 | item320-part2 ]
[ item92-part1 | empty ][ empty | empty ]
[ item92-part2 | empty ][ empty | empty ]
[ item498-part1 | item403-part1 ][ item88-part1 | item5-part1 ]
[ item498-part2 | item403-part2 ][ item88-part2 | item5-part2 ]
[ item216-part1 | empty ][ empty | empty ]
[ item216-part2 | empty ][ empty | empty ]
[ item315-part1 | item491-part1 ][ item262-part1 | empty ]
[ item315-part2 | item491-part2 ][ item262-part1 | empty ]
[ item100-part1 | item37-part1 ][ item90-part1 | item3-part1 ]
[ item100-part2 | item37-part2 ][ item90-part2 | item3-part2 ]
\-------------------------------/\-------------------------------/
Bin bundle 1 Bin bundle 2
sender的表是首先从一个bin bundle开始创建的。可以想像,sender逐渐插入:item416、item500、item125、item12、item9和item512
APSI允许sender可以指定将多少item放入每个bin bundle中,为了这个例子,我们假设这个值是2,但实际上它会更大。
一旦需要更多的空间,就会创建一个新的。sender从Bin bundle 1开始,item512会放置与item125 和 item9相同的箱子(bin)中,但是此时已经满了,所以APSI会创建一个新的Bin bundle 2 ,并将其插入;接着item277被插入到Bin bundle 1 ,因为还有空间;最后,我们可能会得到几十个或数百个bin bundle,最后添加的一些bin bundle可能会留下许多空位置。
当做匹配查询时,加密查询query-ctxt
与Bin bundle 1
和Bin bundle 2
一一匹配后,产生的结果分别为result-ctxt-1
和result-ctxt-2
,将这些发送给receiver。receiver解密该值,得到想要查询结果。
Receiver decrypting the result
[ item79-no-match ] [ item79-no-match ]
[ empty ] [ empty ]
[ item14-no-match ] [ item14-match ]
result-ctxt-1 ==> [ item92-match ] result-ctxt-2 ==> [ item92-no-match ]
[ item401-no-match ] [ item401-no-match ]
[ empty ] [ empty ]
[ empty ] [ empty ]
[ item3-no-match ] [ item3-match ]
APSI计算每个结果密文的匹配值的逻辑OR,并根据原始查询中出现的项的顺序对结果排序,例如,结果向量如下所示。查询中item的顺序是任意的和无关的。
Receiver's query vector Receiver's result vector
[ item92 ] [ match ]
[ item14 ] [ match ]
[ item79 ] [ no-match ]
[ item3 ] [ match ]
[ item401 ] [ no-match ]
在本例中,receiver得出结论,item92、item14和item3都存在于sender的数据库中,而其他item则不存在。
上面的描述中省略了一些重要的细节。首先,任何一方的原始item都不会直接插入hash表中,而是使用它们的OPRF值。
其次,发送方需要多次插入每个item,每次使用布谷鸟hash函数一次。例如,如果使用三个布谷鸟哈希函数,发送方将插入,即Hash1(item92), Hash2(item92), 和Hash3(item92);另一方面,receiver使用了哪个hash,sender是不知道的,即Hash?(item92)。无论如何,都会被匹配上的,因此,我们上面的图表具有误导性:我们应该使用以下名称:item92-hash1-part1
。
第三,在许多情况下,receiver的查询由多个密文组成——而不仅仅是像上面这样的一个密文。例如,假设我们使用大小为32的布谷鸟哈希表,而不是大小为8的布谷鸟哈希表。现在,接收者的查询无法编码为单一的明文。相反,查询被分解成4个密文,每个密文编码一个更大的布谷鸟哈希表的连续块。(形成四个密文)
Receiver's cuckoo hash table
[ item79-part1 ]
[ item79-part2 ]
[ empty ]
[ empty ]
[ item14-part1 ]
[ item14-part2 ]
[ item92-part1 ]
[ item92-part2 ] ==> query-ctxt0
[ item401-part1 ]
[ item401-part2 ]
[ empty ]
[ empty ]
[ empty ]
[ empty ]
[ item3-part1 ]
[ item3-part2 ]
[ ... ] ==> query-ctxt1
[ ... ] ==> query-ctxt2
[ ... ] ==> query-ctxt3
sender也发生了类似的拆分,形成了一个锯齿状的bin bundles.。下面是一个sender看到的:
布谷鸟hash长这个样子,就看不太懂了,一行是一个bin么?
+------------++------------++------------+
| || || |
Bundle index 0 | Bin bundle || Bin bundle || Bin bundle |
| || || |
+------------++------------++------------+
+------------++------------+
| || |
Bundle index 1 | Bin bundle || Bin bundle |
| || |
+------------++------------+
+------------++------------++------------++------------+
| || || || |
Bundle index 2 | Bin bundle || Bin bundle || Bin bundle || Bin bundle |
| || || || |
+------------++------------++------------++------------+
+------------++------------++------------+
| || || |
Bundle index 3 | Bin bundle || Bin bundle || Bin bundle |
| || || |
+------------++------------++------------+
当sender收到query-ctxt0
,它必须在bundle index 0 处计算每个bin bundle的匹配,简单点说,query-ctxt1
必须在bundle index 1处与每个bin bundle匹配,依此类推。receiver获得的结果密文数将等于发送方持有的bin bundles总数;客户不能提前知道这个数量。