[隐私计算实训营学习笔记] 第6讲 匿踪查询和隐语PIR的介绍及开发实践

Rabbit_QL 2024-03-31 23:06:50
加精

主讲老师:张磊、冯骏

学习链接:https://www.bilibili.com/video/BV13M4m1R7gp

补充知识学习:https://zhuanlan.zhihu.com/p/542933339

一、匿踪查询PIR

Private Information Retrieval PIR,官名【隐私信息检索

1. 定义

​ 顾名思义,匿踪查询首先是要查询,但是又希望隐匿踪迹,不能让被查询方知道你查到了什么。假设检查部门正在对某些可疑人员进行调查,但是由于被调查者具备一定的反侦察经验,如果检查部门直接去各个系统里面对这个人进行精准查询,会激发被调查者的警觉,进而出现不可控的局面。所以在这种情况下检查部门需要「一种不对外泄露查询条件和查询结果的隐私计算技术」

​ 例如,用户查询服务端数据库中的数据,但服务端不知道用户查询的是哪些数据。

img

​ PIR是多方安全计算中非常实用的一门技术与应用,可以用来保护用户的查询隐私,进而也可以保护用户的查询结果。其目标是保证用户向数据源方提交查询请求时,在查询信息不被感知与泄漏的前提下完成查询。即对于数据源方来说,「只知道有查询到来,但是不知道真正的查询条件、也就不知道对方查了什么」

2. PIR的分类

  • 按服务器数量分类

    • 单服务器方案(Single Server)
    • 多服务器方案(Multi-Server)
  • 按查询类型分类

    • Index PIR
    • Keyword PIR
  • 按照实现技术分类

    • 基于不经意传输(Oblivious Transfer,OT)的PIR实现
    • 基于同态加密的PIR实现
    • 基于keyword PIR实现。

    ​ 其中基于不经意传输的PIR与基于同态加密的PIR方案是需要检索用户提前感知被检索数据在数据库中位置(索引号),这块可以通过隐私求交的方式实现。

  • 性能分析

    • 在计算开销上,由于同态加密计算开销大于RSA计算开销,所以基于OT的PIR方案计算开销有优势;
    • 通信开销上,如果服务端每条明文数据都较长,如数千字节,则基于同态或keyword的通信开销较小。

3. 基于不经意传输的PIR

基于index的PIR

​ 基于不经意传输的PIR实现过程如下,使用的是n选1的OT协议。(注:已知查询元素的位置)

  1. 首先,假设Server端有$n$条数据,那么将会生成与$n$条数据一一对应的$n$个RSA公私钥对$((d_{1}^{PK},d_{1}^{SK}),…,(d_{n}^{PK}, d_{n}^{SK}))$,并将$n$个私钥保留,将$n$个公钥$(d_1^{PK}, …, d_n^{PK})$发送给Client端,私钥保留在Server。

  2. 然后,Client端随机生成一个对称加密的秘钥$key$,假设用户要检索第$t$条数据,则用收到的第$t$个RSA公钥$d_{t}^{PK}$加密这个$key$,将加密结果$R$发送给Server

  3. 然后,Server端用保留的$n$个RSA私钥$(d_1^{SK}, …, d_n^{SK})$,依次尝试解密$R$,获得$n$个解密结果,依次为$(key_1,key_2,…, key_t, …,key_n)​$。

  4. 然后,$Server​$端利用对称加密算法(此处为AES算法),利用$(s_1,s_2,…,s_{t},…,s_{n})​$针对$n​$条数据进行一一对应加密,将产生的密文消息$(s_1,s_2,…,s_t,…,s_n)​$发送给用户。

  5. 最后,客户端使用第2步中的$key$,对$(s_1,s_2,…,s_t,…,s_n)$消息中的第$t$条密文$s_t$进行对称解密,则得到想要检索的第$t$条原始明文消息$$m_t$$。

4. 基于同态加密的PIR实现

基于Index的PIR

​ 基于同态加密的PIR实现采用Paillier加法半同态加密算法。

  • 此处简述下Paillier算法的三个重要特点:

    • 可以实现两个密文加法计算;
    • 可以实现一个密文与一个明文相乘;
    • 由于加密时用到随机数,所以相同的明文、相同的密钥,可以产生很多个不同的密文,这些不同的密文解密后都能得到相同的原始明文。
  • 基于paillier同态加密的PIR实现过程有五个重要步骤

    1. 首先,Client端生成同态加密公私钥$$(h^{PK}, h^{SK})$$

    2. 然后,假设Server端有$n$条数据$(m_1,…,m_t,…,m_n)$,客户端要检索第$t$条数据。那么Client产生一个$n$维密文向量$(s_1,…, s_t, …, s_n)$,生成规则如下:

      • 第$t$项:使用公钥$h^{PK}$加密数字1后的结果作为$s_t$
      • 其他项:使用公钥$h^{PK}$加密数字0后的结果作为非$s_t$项。(根据Paillier算法第三个特点加密的随机数保障$n$条密文不重复,进而无法从加密结果区分出0和1的不同分布)
    3. 然后,Client将$n$维密文向量$(s_1, …,s_t,…,s_n)$和公钥$h^{PK}$发送给服务端;

    4. 然后,Server将$n$维密文向量$(s_1,…,s_t,…,s_n)$和$n$条明文数据集做向量内积运算,得到密文结果$R$,并且将$R$发送给Client。(同态加密算法的性质是密文计算结果解密后与明文计算结果相等),密文的计算公式相当于是$R=E(m_1\times0+…+m_t \times1+…+m_n\times 0)=E(m_t)$

    5. 最后,Client利用私钥$h^{PK}$对$R$进行解密,得到想要检索的第$t$条原始明文消息。

5. 基于keyword的PIR实现

​ 前面的场景中,我们都是先通过隐私求交进行获取待查询数据在数据源中的位置,但是如果没有位置信息,是否可以进行查询呢?答案就是根据关键词进行查询,此类方案又称keyword PIR,可以利用Paillier同态加密+拉格朗日插值多项式实现。

  1. 首先,假设Server端有明文数据集$((k_1,v_1), …, (k_t, v_t), …, (k_n,v_n))$。接下来对此明文数据集进行拉格朗日多项式插值法生成最终多项式$H(X)$,同时生成标识多项式$F(X)$。(当取存在的点的时候,计算结果为0,否则为1);
    $$
    H(X)=a_0+a_1x+a_2x^2+...+a_nx^n
    $$
    ​ 那么,$H(k_1)=v_1​$,$H(k_2)=v_2​$,$H(k_n)=v_n​$。
    $$
    F(x)=(x-k_1)(x-k_2)...(x-k_n)=c_0+c_1x+c_2x^2+...+c_nx_n
    $$
    ​ 因此,$F(k_1)=0, F(k_2)=0, …, F(k_n)=0$

  2. 然后,Client生成同态加密公私钥$(h^{PK}, h^{SK})$

  3. 然后,假设Client要查询$k_t​$,同时使用$h^{PK}​$分别加密$k_t​$的1次方到n次方$(E(k_t), E(k_t^2), …E(k_t^n))​$,发送给Server。

  4. 然后,Server利用密文向量$(E(k_t), E(k_t^2), …E(k_t^n))$,代入到函数$F(X)$和$H(x)$,分别计算同态密文$E(F(x_t))$和$E(H(x_t))​$,将计算结果发送给用户。

  5. 最后,Client通过私钥$h^{SK}$对两条密文进行解密,如果$F(x_t)=0$,则$H(x_t)$即为检索结果;否则检索结果为空。

二、隐语实现PIR总体介绍

Private Information Retrieval PIR

  • 隐语目前支持的PIR方式
    • Single Server Index PIR : SealPIR
    • Single Server Keyword PIR:Labeled PSI

1. 隐语PIR实现位置

主要位于SPU的代码库

img

lesson6_隐语psi位置

2. 隐语实现的PIR总体介绍

(1) pir_setup 离线阶段进行数据预处理

https://github.com/secretflow/secretflow/blob/v1.5.0.dev240319/secretflow/device/device/spu.py#L1191

  • 参数说明:

    • server:Server端对应的party_name

    • input_path:服务端数据文件路径,建议绝对路径

    • key_columns:key对应的列名

    • label_columns:Label对应的列名,若多列则用逗号分开

    • opera_key_path:服务端ecc密钥文件,32B,二进制文件

    • setup_path:Offline/Setup phase output data dir. Use an absolute path

    • num_per_query:每次查询的id数量,一般设置为1.

      目前如果设置为大于1的数$k$,则会调用$k$次该接口。

    • label_max_len:Label数据拼接后填充到固定的长度大小,也就是label的最大字节数

    • bucket_size: 不可区分度,Split data bucket to do pir query

  • 示例:

reports = spu.pir_setup(
        server='bob', input_path='/path/B_PIR_DATA.csv', key_columns='id',
        label_columns=['register_date','age'],
        oprf_key_path='/path/oprf_key.bin',     
        setup_path='/path/setup_path', 
        num_per_query=1, 
        label_max_len=18
)
  • 提示:pir_setup 单方任务
  • 可以使用的secretflow单机模拟配置,或者直接调用spu的python接口

(2) pir_query 双方在线查询阶段

https://github.com/secretflow/secretflow/blob/v1.5.0.dev240319/secretflow/device/device/spu.py#L1253

  • 配置:

    • server:Server端对应的party_name
    • client:Client端对应的party_name
    • client_input_path:Client端id对应的csv文件路径
    • client_key_columns: Key对应的列名
    • client_output_path:PIR查询结果输出的文件路径
    • server_setup_path : 服务端ecc密钥文件,32B,二进制文件
  • 示例

    spu.pir_query(
        server='alice',
        client='bob',
        server_setup_path=f'{current_dir}/alice_setup', 
        client_key_columns=["name"],
        client_input_path=f"{current_dir}/bob_pir_dataset.csv",
        client_output_path=f"{current_dir}/bob_pir_result.csv"
    )
    

三、Index PIR-SealPIR介绍

基于BFV的同态方案

1. 参数

  • 多项式次数$N$

  • 明文模$t$

  • 密文模$q$

  • Expansion Rate:$2\times log(q)/log(t)$

  • 明文
    $$
    R_t=\mathbb{Z}_t[x]/(x^N+1)
    $$

$\mathbb{Z}_n$表示模n的整数环

$$
a_0+a_1x+...+a_{N-1}x^{N-1}
$$

  • 密文$$(c_0,c_1)\in R_q\times R_q​$$
    $$
    R_q = \mathbb{Z}_q[x]/(x^{N}+1)
    $$

2. 主要运算

  • 密文加法:若明文$p_1(x), p_2(x)$的密文为$c_1$和$c_2$,则$c_1+c_2$是$p_1(x)+p_2(x)$的密文

  • 明文乘密文:明文$p_1(x)$的密文为$c$,$p_2(x)$是另一个数的明文,则$p_2(x) \times c$是$p_1(x) \times p_2(x)$的密文。

  • 替换:明文$p(x)$的密文为$c$,奇数为$k$

    $Sub(c,k)$是$p(x^k)$的密文,例如:$p(x)=7+x^2+2x^3$

    $Sub(c,3)$,得到$p(x^3)=7+(x^3)^2+2(x^3)^3=7+x^6+2x^9$的密文

  • 同态计算的噪声增长

    同态乘法,噪声增长较大,而且用时相对较久。SealPIR中尽量避免使用乘法。

img

3. 基于同态密码实现Index PIR的基本原理

img

​ a. Client端将查询向量使用同态算法进行加密得到$[E(0), …,E(1), E(0)]$,将加密后信息发给Server端;

​ b. Server将本方数据$B_1, …, B_{i-1}, B_{i}, B_{i+1},…B_{n}$与Client端发送的数据进行内积。将结果$E(B_i)$发送给Client。

​ c. Client对$E(B_i)$解密,获得结果。

  • 问题:查询请求的消息密文太大,包含了$n$个密文向量

4. SealPIR主要贡献:

(1)多个数据pack到一个HE Plaintext

​ 查询的db_index转换为plaintext_index

img

  1. Server端Setup

​ 例如将原始数据库中的$[B_1,B_2,B_3]$压缩到明文多项式$P_1=a_0+a_1x+…+a_{N-1}x^{N-1}$,$[B_4,B_5,B_6]$压缩到明文数据库$P_2 = b_0+b_1x+…+b_{N-1}x^{N-1}$,等等。

  1. Client端查询,需要将数据库查询的index,转换为多项式查询的index

    例如对$B_4$进行查询,需要转换为查询多项式$P_2$

  2. Server端返回$P_2$的加密值给Client端

  3. Client端进行同态解密,解密后得到一个明文多项式$b_0+b_1x+…+b_{N-1}x^{N-1}$,根据pack的偏移,找到$B_4$对应的系数。将其拼接为一个明文数据。

  • 多项式次数:8192

  • 明文模:17bit

  • DB数据长度:288B

  • Ceil($\frac{288*8}{16}=144$)

  • Floor($\frac{8192}{144}=56​$)

    每个多项式可以pack 56个原始数据。

实现Symmetric PIR时,不使用packing

(2) 查询向量压缩到一个密文

​ 显著减少通信量,server端可通过计算expand得到查询密文向量

  • 客户端生成查询密文

    查询向量${0,0,…,1,…,0}​$转换为同态明文plaintext:$x^{query_{index}}=a_0+a_1x+…+a_{N-1}x^{N-1}​$

    $a_i=0, i\ne query_{index}$,$a_i = 1, i = query_{index}$

    加密$Enc(a_0+a_1x+…+a_{N-1}x^{N-1})=Enc(x^{query_{index}})$

  • Server端通过执行Expand算法,得到密文向量:$Enc(0), Enc(0), …, Enc(1), …, Enc(0)$

​ 每个明文可以pack多个原始数据,例如上述可以pack56个。通过一个明文查询,可以对$8192 \times 56=458752$个原始数据进行查询。

(3)支持多维查询

以二维数据为例

​ 2维查询将数据转换为$\sqrt{n} \times \sqrt{n}$的矩阵,减少expand计算量

img

  • $\sqrt{n} \times \sqrt{n}$的矩阵$M$,其中$\sqrt{n} \le 8192$

  • $V_c$是密文查询向量,$A_c=M·V_c$,$A_c$是$\sqrt{n}\times 1$的密文列向量。

  1. 将$A_c$中的每个密文拆分为$F$份,得到$\sqrt{n} \times F$的明文矩阵$AS_c$
  2. $V_r$是密文查询行向量,$AS_c^T ·V_r$得到$F·1$的密文向量
  3. 客户端收到$F$个密文向量,解密后,将其组合成密文$Enc(M[r,c])$,再次解密得到$M[r,c]$

$F=2\times log(q)/log(t)$是扩张因子,$F=2\times log(218)/log(16) \approx 28$

  • 通信量比较大

(4)支持多个查询

使用cuckoo hash支持同时进行多个查询

img

​ SealPIR 中给出了通过cuckooHash进行多个查询的算法,可以提高查询效率。cuckooHash选取3个hash,bin的个数为$1.5k$,具体算法,如下:

  • Server setup

    对DB中n个元素,分别计算cuckooHash的3个Hash,得到3个bin index,插入到 3个bin index中

  • Client query

    • 将k个查询,通过cuckooHash插入到b=1.5k个bin中,对空的bin进行随机填充
    • 执行每个bin执行一个PIR,共计b个PIR。这里的难点是如何将query_index转换为bin_index

5. 隐语实现的Index SealPIR位置

  • 实现位置
    • libspu/pir/seal_pir.h libspu/pir/seal_pir.cc
    • libspu/pir/seal_mpir.h libspu/pir/seal_mpir.cc
  • 外部库依赖
    • bazel/repositories.bzl
    • bazel/ seal.BUILD
  • 单元测试
    • libspu/pir/seal_pir_test.cc
    • libspu/pir/seal_mpir_test.cc

四、Keyword PIR - Labeled PSI介绍

1. 基本原理

  • 核心思想:点值表示得到插值多项式系数表示

    • 匹配多项式
    • label插值多项式

    在匹配多项式$P(key_i)=0$的情况下,对应的插值多项式$Q(key_i)=w_i$对应带查询的value

img

2. BFV SHE:packing and SIMD

img

3. 论文中的主要思想

​ Labeled PSI的原理可参考下面的论文:

  • [CLR17] Fast Private Set Intersection from Homomorphic Encryption

img

  • [CLHR18] Labeled PSI from Fully Homomorphic Encryption with Malicious Security

img

  • [CMCD+21]Labeled PSI from Homomorphic Encryption with ReducedComputation and Communication

img

4. 减少乘法次数和计算量

  • 窗口(windowing)
  • 分区(partitioning)

img

  • bundle

img

5. 减少通信量

​ 使用extremal postage stamp bases减少通信量

  • 给定$k$个正整数$A_k={a_1=1\lt a_2\lt ... \lt a_k}$ ,$[1,n]$中的所有整数可以有最多$h$个$a_j$相加得到。

img

6. Paterson-Stockmeyer算法减少密文乘法

img

7. 隐语label PSI的主要工作

  • 以微软的开源代码功能为核心

  • OPRF采用隐语的实现:

支持的ecc曲线包括:FourQ, Secp256k1, SM2

  • Label的自动填充
  • 增加了服务的预处理结果保存功能
  • 可以支持离线和查询(多次)两个阶段

(1)服务端预处理Setup

img

  1. 选择参数
  2. 对id数据进行prf计算(256bit),前128bit根据截取用于匹配, 后128bit作为对称算法密钥加密label
  3. 根据prf前128bit将数据插入SimpleHash
  4. 对SimpleHash每一行分别划分bin bundle,并计算 matching polynomial和label polynomial
  5. 将插值多项式系统packing到同态算法明文

(2) 客户端和服务端query阶段

img

  1. 客户端向服务端请求参数

  2. 执行oprf协议

  3. 计算查询值的同态密文幂集合

  4. 使用同态私钥解密服务端返回的同态密文

  5. 满足匹配条件时,使用oprf的后128bit解密得到label

8. Label PSI主要参数

img

9. 实现位置

img

五、隐语PIR后续计划

  • PIR协议开发
    • Spiral PIR(2022)
    • Simple PIR(2023)
  • PIR调用框架
    • PSI/PIR独立代码库
  • PIR产品化
    • 了解产品需求
    • 设计落地方案

六、代码实践

1. secretnote使用

  • 下载secretnote

    pip install secretnote -i https://pypi.tuna.tsinghua.edu.cn/simple
    
  • 每个参与方需要在容器中手动启动secretnote

    这里,由于我们使用的是host模式,端口被其他服务占用,额外修改了端口

    secretnote --port=35000
    
  • 端口统计

    alicebob
    ray head3500035600
    Secretnote2550025002
  • 数据集文件CSV

​ secretnote启动的目录下的所有csv文件,会显示在前端页面上:

  • 资源占用情况

    右上角有一个小图标,可以查看各个计算节点的状态情况。

    img

    img

2. python环境

Python 3.10

  • secretflow==1.5.0.dev20240321
  • secretflow-rayfed==0.2.1a1
  • secretflow-serving-lib==0.2.0.dev20240311

3. SF和SPU配置

  • 寻找未使用的port,这些端口会被用于alice和bob的ray-fed通信地址

    import socket
    from contextlib import closing
    from typing import cast
    
    def unused_tcp_port() -> int:
        """Return an unused port"""
        with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
            sock.bind(("", 0))
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            return cast(int, sock.getsockname()[1])
    
    print(unused_tcp_port())
    

    可以看到alice和bob随机找到的未使用的端口是不同的:

img

  • Ray-fed的启动

    cluster_configaddress声明每个参与方进行ray-fed通信的地址,listen_addr用于参与方自己用。

    sf.init()中的address为ray的head节点的ip和端口号。如果填入local,会在本地启动一个ray集群。

    • alice

      import secretflow as sf
      
      cluster_config = {
          "parties": {
              "alice": {
                  # replace with alice's real address
                  "address": "Alice_IP:42113",
                  "listen_addr": "0.0.0.0:42113"
      
              },
              "bob": {
                  "address": "Bob_IP:47555",
                  "listen_addr": "0.0.0.0:47555"
              },
          },
          'self_party': 'alice'
      }
      
      sf.shutdown()
      sf.init(address="Alice_RAY_IP:RAY_PORT", cluster_config=cluster_config)
      
      • Alice_IP为alice的真实IP
      • Alice_RAY_IP为alice的ray head的IP,RAY_PORT为ray启动的端口。
    • bob

      import secretflow as sf
      
      cluster_config = {
          "parties": {
              "alice": {
                  # replace with alice's real address
                  "address": "Alice_IP:42113",
                  "listen_addr": "0.0.0.0:42113"
      
              },
              "bob": {
                  "address": "Bob_IP:47555",
                  "listen_addr": "0.0.0.0:47555"
              },
          },
          'self_party': 'bob'
      }
      
      sf.shutdown()
      sf.init(address="BOB_RAY_IP:RAY_PORT", cluster_config=cluster_config)
      
    • Bob_IP为Bob的真实IP

    • Bob_RAY_IP为bob的ray head的IP,RAY_PORT为ray启动的端口。

注意:执行时需要选择两个cell同时运行

  • SPU 配置

    这里的address为新的地址,不要和之前设置的冲突了。

import spu

cluster_conf = {
    "nodes": [
        {
            "party": "alice",
            "address": "alice:36247"
        },
        {
            "party": "bob",
            "address": "bob:53635"
        },
    ],
    "runtime_config": {
        "protocol": spu.spu_pb2.SEMI2K,
        "field": spu.spu_pb2.FM128,
        "sigmoid_mode": spu.spu_pb2.RuntimeConfig.SIGMOID_REAL,
    },
}
spu = sf.SPU(
    cluster_def=cluster_conf,
    link_desc={
        "connect_retry_times": 60,
        "connect_retry_interval_ms": 1000
    },
)
  • runtime_config设置PSI/PIR相关的配置
  • link_desc配置网络不佳时的一些处理措施。

4. 生成数据

​ Server(Sender)一般为数据库,Client(Receiver)为请求方。demo中将Alice作为Server,Bob作为Client。

  • alice数据:作为Sender方,有两列特征,name和age,name作为主键,age将作为待查询项。

img

  • 准备alice方需要的secret key

    进入alice所在的容器,在alice的工作目录下调用openssl rand 32 > ~/alice_oprf_key

img

  • bob数据

img

5. PIR demo

​ demo中将Alice作为Server,Bob作为Client

(1) pir_setup

​ 两方一起运行,并设置相关参数。具体参数说明请看之前的介绍。

img

​ 可以看到Alice设定的setup_path目录下,多了一些文件。而Bob是没有的。

img

(2) pir_query

​ 设定相关的参数后,执行下列代码:

img

​ 查看client_output_path对应的目录,可以看到:

img

...全文
288 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

562

社区成员

发帖
与我相关
我的任务
社区描述
隐语开源社区,隐私计算开发者交流和讨论的平台。
密码学可信计算技术安全 企业社区
社区管理员
  • 隐语SecretFlow
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

【最新活动】

3月18日:隐私计算实训营第一期

试试用AI创作助手写篇文章吧