智能 UI 之:个人信息安全实战

avatar
阿里巴巴 前端委员会智能化小组 @阿里巴巴

文/ 阿里淘系 F(x)Team - 甄子

之前的文章介绍过我们前端智能化方向在智能 UI 上的一些思考,随着智能 UI 体系的完善和深入,愈发觉得需要提前布局个人信息安全,要让 UI 更智能势必需要更多用户信息,如何保护用户隐私的同时让整个体系持续迭代,成了近期学习和实践的方向。因此,就手把自己的学习和思考分享出来,有不足和错误还望大家批评指正。

在开始之前,对于个人信息来说,怎样才算是安全?要搞清楚这个问题,先要看看是什么原因造成个人信息不安全。我不会把网上的资料如:GDPR、个人信息安全保障法律、法规等罗列的概念照搬过来,而是从背后的思考去探究:为什么保护?又保护了什么?沿着这些思考去检查我们在使用用户个人信息时的技术方案,将更加容易发现问题、发现解决方案。(当然,这只是我的一家之言,不见得对,你可以选择跳过到后面的“干货”部分)

image.png

首先,要从社会公平正义的角度出发,审查个人信息使用的安全性及合理性。个人信息安全只是以政府为代表的公权力、以企业为代表的资本利益和普罗大众的人民私权利之间博弈的其中一个战场。企业无疑是推动时代进步的核心驱动力,在创造产品和服务的同时,还创造着工作机会。工作机会让人民能够获得收入,从而弥补生活的消费支出。由于个人和企业相比是弱势群体,政府制定劳动法律、法规来保障个人在就业和工作过程中不被歧视、压迫和不公正对待。这是三者在工作领域的博弈,在个人信息安全领域的博弈也是类似的。企业获取个人信息并向客户提供个性化服务,从而改善和提升客户体验,同时,企业也在用个人信息来提升服务效率,基于数据把服务供给和客户需求进行精准匹配。个性化的服务和服务效率提升,理论上应该降低客户获取服务的成本,但是资本是逐利的,不仅没有降低客户指出的服务成本,反而利用大数据杀熟,用价格歧视等手段进一步放大利益。这就像个人在某个企业工作,企业不仅强制要求 996 还经常加班,甚至不发加班工资。为了继续使用垄断的企业提供的服务,放任个人信息安全被损害,就像个人为了保住工作收入养家糊口选择默不作声。政府为了保障弱势群体不受侵害,替个人发声,制定各种法律、法规对企业和资本进行监管,从而保护了社会的公平正义。试想一下,今天如果个人拒绝所有基于个人信息的个性化服务,那么有效获取信息的可能性将大幅度降低,这就是为什么 GDPR 推出后,大家并没有一股脑把所有个人信息使用授权关闭的原因。大家关闭的更多是利用个人信息侵犯用户隐私的服务,比如无孔不入让人不胜其烦的广告。因此,在使用个人信息时关注社会的公平正义是基础要求,这个要求从根本上符合个人信息安全保护的要求。

其次,知情权是个人信息安全的关键。比较常见的情况是:先把个人信息搜集上来再说,既不告诉用户收集了哪些信息,又不告诉用户这些信息用来做什么。这种情况在互联网发展初期大量存在,主要是因为当时的技术还不成熟,推荐算法对数据的要求并没有共识。今天,在机器学习、深度神经网络的加持下,基于个人信息进行推荐所必要的信息,对数据的要求是比较明确的,应当以最小必要性为原则约束信息收集的范围。如果个人信息收集的范围以最小必要性为原则,就能够透明化、简单直接的跟用户公示,让用户对自己被收集的信息以及其用途知情。

最后,选择权是个人信息安全保障的核心手段。拿国家取缔学区房、校外培训为例,今天的人民如果因为没钱被固化在当前阶层,失去了改变人生和命运的机会,相当于失去了对人生的选择权。比如我没钱,我买不起学区房、无法参加校外培训,我的孩子就会在社会竞争中被淘汰。优质的企业(注意:这里不写优秀是因为其扼杀了能力改变命运惟学历论)对校招生学历的要求,会以好的中学教育、好的小学教育甚至学前教育一层层传导,在任何层级上不够钱就无法获得充分的教育资源,从而无法进入更高概率成功的层级。回到第一点中说到的三者博弈,企业体现了资本逐利的特点,为了保证企业用人的质量,卡学历无疑是投入产出比最高的手段,政府无法对此进行过多的监管。但是,为了保障人民选择权,政府可以取缔学区房和校外培训,在义务教育阶段保证教育资源的公平性,降低钱多、钱少带来的影响,给所有人一个机会实际上就是给人民选择权。然而,今天的一些互联网企业不愿给予用户选择权,因为他们在大数据杀熟,用价格歧视等手段进一步放大利益,所以,在应用启动的时候弹出一个窗口让用户授权,否则就退出应用,用垄断地位去强奸用户。

综上所述,这三点是环环相扣的,以公平正义为出发点、以最小必要性为原则设计个人信息收集和使用的方案,就可以大大方方的公示收集信息的内容和目的,当然也可以自信的给用户选择的开关。

掌握上述的原则,在技术选型、方案设计中发挥了关键作用,但是,必须指出的是这些方法有失效的风险。由于缺乏法律法规约束,过去中国很多互联网企业做的太过火,用户有杯弓蛇影的感觉,从而做出过激反应:给我开关我就关。尤其在智能 UI 消费链路中,深入基于场景的个性化方案推荐时,想要识别场景就需要更多用户个人信息为基础,来判断用户当前所处的场景,这本身就有侵犯用户隐私的风险,下面就来介绍一种新的解决方案:安全多方计算(Secure Muti-party Computation)。我会从零知识证明简介开始,介绍什么是安全计算。再从安全多方计算简介和 RSA 加密算法,介绍一下安全多方计算的原理。最后,用百万富翁问题的代码实践和 MP-SPDZ 工具使用方法来收官。

零知识证明简介

1985 年,Shafi Goldwasser、Silvio Micali 和 Charles Rackoff 在论文《互动式证明系统的知识复杂性》(The Knowledge Complexity of Interactive Proof Systems) 中首次提出交互式协议。四年后,即 1989 年他们三人在同名论文中首次对“零知识”(ZKP,Zero-Knowledge Proofs)进行了定义:证明者(prover)有可能在不透露具体数据的情况下让验证者(verifier)相信数据的真实性。零知识证明可以是交互式的,即证明者面对每个验证者都要证明一次数据的真实性。零知识证明也可以是非交互式的,即证明者创建一份证明,任何使用这份证明的人都可以进行验证。零知识证明目前有多种实现方式,如 zk-SNARKS、zk-STARKS、PLONK 以及 Bulletproofs ,每种方式在证明大小、证明者时间以及验证时间上都有自己的优缺点。

有两个经典的例子介绍零知识证明:

  • How to Explain Zero Knowledge Protocols to Your Children:通过阿里巴巴与四十大盗的故事讲解零知识证明原理。
  • Cryptographic and Physical Zero-Knowledge Proof Systems for Solutions of Sudoku Puzzles:作者通过数独的例子讲解零知识证明原理。

在阿里巴巴与四十大盗的故事里,四十大盗让阿里巴巴说出打开宝库的口令,阿里巴巴想:如果说出口令大盗肯定会杀死自己,但如果不说出口令,又怎么让大盗相信自己知道口令呢?如图 4-22 阿里巴巴随机藏在 A 或 B 山洞内,然后大盗在观察点要求阿里巴巴从 A 或 B 山洞走出来,如果重复几次每次阿里巴巴都能走出来,就能证明阿里巴巴知道打开门锁的方法。你可能会问:大盗让阿里巴巴从 A 进入后从 B 出来不行么?可以,但这暴露了山洞、门锁等知识,因此不是零知识证明。

image.png

图 4-22 阿里巴巴和四十大盗问题

第二个例子是数独,怎样才能在不暴露正确答案的情况下,证明自己能够正确完成数独游戏呢?假设 A 要向 B 证明自己能完成数独,如图 4-23 所示,A 可以把数字填好但反过来不让 B 看到答案,然后让 B 任意挑选行、列、块,挑好后将行、列、块内的卡片装进袋子,通过摇晃来随机打乱卡片顺序避免暴露答案,然后将袋子交给 B 验证,B 发现每个袋子都是 1~9 确信 A 能够完成数独。

image.png

图 4-23 数独零知识证明问题

从上面两个例子可以总结出零知识证明的性质:

  • 完整性(completeness):如果证明者是诚实的,那么她最终会说服验证者。
  • 可靠性(Soundness):证明者只能说服验证者该陈述是否属实。
  • 零知识性(Zero-knowledgeness):除了知道陈述是真实的,验证者不知道任何额外的信息。

总而言之,要创建零知识证明,验证者需要让证明者执行一系列操作,而证明者只有在得知底层信息的情况下才能正确执行。如果证明者乱蒙一个结果,那么验证者总能在验证中发现并证明他的错误。

想象有这么一种电路,传入数据,并输出某一抛物线上的值。如果用户能够对抛物线上的某一点连续给出正确答案,那么就可以确信他知道这条抛物线函数是什么,因为每一轮成功猜出正确答案的概率会越来越低。你可以把电路理解为阿里巴巴和四十大盗的山洞,如果阿里巴巴每次都能顺利通过电路,那么说明他极有可能知道穿过电路的“密码”(抛物线函数)。在不透露任何具体信息的情况下证明自己拥有数据,建改带来许多关键价值,尤其在保护个人信息安全的领域,可以最大限度避免用户暴露隐私。

回到智能 UI 的例子,我们希望知道用户处在什么场景下,从而给用户提供一个针对该场景优化的 UI 方案。但是,处于个人信息安全考虑,我们不希望收集用户个人信息,零知识证明就成了解决问题的有效手段。技术实现可以参考区块链,正是零知识证明让区块链不泄露接收方、发送方、交易额等交易细节的情况下,证明区块链上的资产转移是有效的。

由于零知识证明的性质,其最大的缺点就是需要交互过程及大量的算力,在零知识证明的启发下,安全多方计算粉墨登场,良好解决了部分问题。

安全多方计算简介和 RSA 加密算法

安全多方计算(Secure Muti-party Computation,简称 MPC,亦可简称 SMC 或 SMPC)问题,首先由华裔计算机科学家、图领奖获得者姚期智教授于1982年提出,也就是为人熟知的百万富翁问题:两个争强好胜的富翁 Alice 和 Bob 在街头相遇,如何在不暴露各自财富的前提下比较出谁更富有?

简单来说,安全多放计算作为密码学的一个子领域,允许多个数据所有者在互补新人的情况下进行协同计算,输出计算结果,并保证任何一方均无法得到计算结果外的其它信息。换句话说,MPC 技术可以获取数据使用价值,却不泄露原始数据内容。安全多方计算理论主要研究协同计算及隐私保护问题,其特点为:

  • 输入隐私性:安全多放计算过程中保证各方隐私输入独立,计算式不泄露任何本地数据。
  • 计算正确性:多放计算参与各方就某计算任务,通过约定 MPC 协议进行协同计算,计算结束后各方得到正确结果数据反馈。
  • 去中心化:传统的分布式计算由中心节点协调计算过程,收集各方输入信息,而安全多放计算中各参与方地位平等,不存在任何特权方获取和干预他方信息,从而提供去中心化计算。

要解决百万富翁问题,必须了解数据是如何加密传输的,这里简单介绍一下 RSA 算法。

第一步,寻找两个不相同的质数 p 和 q 令 p != q 计算 N = p*q 。这里捎带说一下质数定义:只能被 1 和自身整除的数,2、3、5、7、11 等都是质数。

第二步,根据欧拉函数计算 r = φ(N) = φ(p)φ(q) = (p-1)(q-1) 。欧拉函数 φ(N) 是小于或等于 N 的正整数中与 N 互质的数的数目,所谓互质是指两个或两个以上的整数如果最大公约数是 1 则称他们互质,例如 φ(8) = 4 因为 1、3、5、7 均和 8 互质。

1、如果 n = 1φ(1) = 1 小于等于 1 的正整数中唯一和 1 互质的数就是 1 本身。

2、如果_n_为质数 φ(n) = n - 1 ,因为质数和每一个比它小的数字都互质。比如5,比它小的正整数1,2,3,4都和他互质。

3、如果_n_是_a_的_k_次幂,则 φ(n) = φ(a^k) = a^k - a^(k-1) = (a-1)a^(k-1)

4、若_m_,_n_互质,则 φ(mn) = φ(m)φ(n)

**证明:**设 A, B, C 是跟 m, n, mn 互质的数的集,据中国剩余定理经常看数学典故的童鞋应该了解,剩余定理又叫韩信点兵,也叫孙子定理,A*B 和 C 可建立双射一一对应的关系。或者也可以从初等代数角度给出欧拉函数积性的简单证明,因此的 φ(n) 值使用算术基本定理即可证明。

第三步,选择一个与 r 互质的整数 e 令 e < r ,然后,求 e 关于 r 的 mod 反元素 d 令 ed = 1(mod r) 。所谓模反元素是指:如果两个正整数 a 和 n 互质,那么一定可以找到整数 b 令 ab - 1 能被 n 整除。比如 3 和 5 互质,3 关于 5 的模反元素可能是 2,因为 3 * 2 - 1 = 5 可以被 5 整除,你可能看出来了:2 加减 5 的整数倍都是 3 关于 5 的模反元素 {... -3,2,7,12...} 也就是公式 3 * 2 = 1(mod 5)。如果不看 mod 运算,这类似于 ab = 1 那么 a 和 b 互为倒数,所以也叫模倒数。对于模 12 来说,5 和 5 互为模 12 的逆元,这是因为 5 * 5 = 25 mod 12 = 1 所以也叫做模逆元。当你听到模反元素、模倒数和模逆元时,不要疑惑,它们是同一个概念。

之前介绍的欧拉函数用处实际上在于欧拉定理:

如果两个正整数 a 和 n 互质,则 n 的欧拉函数 φ(n) 可以让下面的等式成立:

aφ(n)=1(mod(n))a^φ(n) = 1(mod(n))

由此可得:a 的 φ(n - 1) 次方肯定是 a 关于 n 的模反元素。

欧拉定理就可以用来证明模反元素必然存在。由模反元素的定义和欧拉定理我们知道,a 的 φ(n) 次方减去 1 ,可以被 n 整除。比如 3 和 5 互质,而 5 的欧拉函数 φ(5) 等于 4,所以 343^4  为 81 减去 1 可以被 5 整除 80 / 5 = 16

小费马定理:

假设正整数 a 与质数 p 互质,因为质数 p 的 φ(p) 等于 p-1 则欧拉定理可以写成

a(p1)=1(mod(p))a^{(p-1)} = 1 (mod(p))

这其实是欧拉定理的一个特例。

第四步,以(N,e)为公钥(N,d)为私钥,把公钥发送给对方自己保存私钥,让对方用公钥加密数据发给自己,再用私钥对接收到的数据进行解密,就完成了 RSA 加密数据传输。接下来,我们用代码来实现自己的 RSA 加密传输,并以此来实现百万富翁问题安全多方计算的方案。

安全多方计算实战

首先,根据上一节中介绍的 RSA 加密原理准备一些 Utility。

import math
import random
# 获取小于等于指定数的素数数组
def get_prime_arr(max):
    prime_array = []
    for i in range(2, max):
        if is_prime(i):
            prime_array.append(i)
    return prime_array
# 判断是否为素数
def is_prime(num):
    if num == 1:
        raise Exception('1既不是素数也不是合数')
    for i in range(2, math.floor(math.sqrt(num)) + 1):
        if num % i == 0:
            # print("当前数%s为非素数,其有因子%s" % (str(num), str(i)))
            return False
    return True
# 找出一个指定范围内与n互质的整数e
def find_pub_key(n, max_num):
#     return 65537
    while True:
        # 这里是随机获取保证随机性
        e = random.randint(1, max_num)
        if gcd(e, n) == 1:
            break
    return e
# 求两个数的最大公约数
def gcd(a, b):
    if b == 0:
        return a
    else:
        return gcd(b, a % b)
# 根据e*d mod s = 1,找出d
def find_pri_key(e, s):
    for d in range(100000000):  # 随机太难找,就按顺序找到d,range里的数字随意
        x = (e * d) % s
        if x == 1:
            return d

有了上述工具函数,接下来定义生成公钥和私钥的过程。

def build_key():
    prime_arr = get_prime_arr(1000)
    p = random.choice(prime_arr)
    # 保证p和q不为同一个数
    while True:
        q = random.choice(prime_arr)
        if p != q:
            break
    print("随机生成两个素数p和q. p=", p, " q=", q)
    n = p * q
    s = (p - 1) * (q - 1)
    e = find_pub_key(s, 100)
    print("根据e和(p-1)*(q-1))互质得到: e=", e)
    d = find_pri_key(e, s)
    print("根据(e*d) 模 ((p-1)*(q-1)) 等于 1 得到 d=", d)
    print("公钥:   n=", n, "  e=", e)
    print("私钥:   n=", n, "  d=", d)
    return n, e, d

然后,定义加密和解密函数。

# 加密
def rsa_encrypt(content, ned):
    # 密文B = 明文A的e次方 模 n, ned为公钥
    # content就是明文A,ned【1】是e, ned【0】是n
    B = pow(content, ned[1]) % ned[0]
    return B

# 解密
def rsa_decrypt(encrypt_result, ned):
    # 明文C = 密文B的d次方 模 n, ned为私钥匙
    # encrypt_result就是密文, ned【1】是d, ned【0】是n
    C = pow(encrypt_result, ned[1]) % ned[0]
    return C

接下来,让我们简单试一下加密和解密是否有效。

pbvk = build_key()
pbk = (pbvk[0], pbvk[1])  # 公钥
pvk = (pbvk[0], pbvk[2])  # 私钥

test_n = random.randint(0,1000)
test_e = rsa_encrypt(test_n, pbk)
test_d = rsa_decrypt(test_e, pvk)
print("原数字:%s 加密后:%s 解密后:%s" % (test_n, test_e, test_d))

build_key() 函数打印的信息如下。

随机生成两个素数p和q. p= 563  q= 127
根据e和(p-1)*(q-1))互质得到: e= 13
根据(e*d) 模 ((p-1)*(q-1)) 等于 1 得到 d= 65365
公钥:   n= 71501   e= 13
私钥:   n= 71501   d= 65365

我们随机生成一个数字 104,用公钥(n=71501,e=13)加密后为 10273,用私钥(n=71501,d=65365) 解密后为 104,可见,我们的加密和解密算法是有效的,接下来进入百万富翁问题的解决。

image.png

图 4-24 百万富翁问题中财富安全比较过程

从生成 RSA 公钥和私钥开始,随机生成两个亿万富翁的财富值 i 和 j 分别为 5 亿和 3 亿。

pbvk = build_key()
pbk = (pbvk[0], pbvk[1])  # 公钥
pvk = (pbvk[0], pbvk[2])  # 私钥

# 生成两个亿万富翁
i = random.randint(1, 9)
j = random.randint(1, 9)
print("A 有 %s 亿,B 有 %s 亿" % (i, j))

输出:

随机生成两个素数p和q. p= 563  q= 127
根据e和(p-1)*(q-1))互质得到: e= 13
根据(e*d) 模 ((p-1)*(q-1)) 等于 1 得到 d= 65365
公钥:   n= 71501   e= 13
私钥:   n= 71501   d= 65365
A 有 5 亿,B 有 3 亿

B 接收到公钥 pbk 同时生成一个合适的随机数 X,利用公钥 pbk 加密 X 得到 K ,同时 K – 自己的财富 j 得到 c 传输给 A,为了方便实验这里并没有发生实际的传输,直接用变量代替,实际使用可以参考下一节提供的方案。

x = random.randint(1, 1000)
print("随机选取的大整数 x: %s" % (x))
K = rsa_encrypt(x, pbk)
print("大整数加密后得密文 K: %s" % (K))
c = K - j
print("B 发送给 A 的数字 c: %s" % (c))

输出:

随机选取的大整数 x: 750
大整数加密后得密文 K: 39601
B 发送给 A 的数字 c: 39598

A 尝试遍历序列 (c + m, c + m + 1, c + m + 2, …, c + n),对每个值使用私钥 pvk 进行解密,并将结果对 p 取余数(即mod),得到结果序列(dm, dm+1, dm+2, …, dn)

c_list = []
for k in range(1, 11):
    t = rsa_decrypt(c + k, pvk)
    c_list.append(t)
print("对c+1到c+10进行解密: %s" % c_list)

# 选取合适大小的p,这里简单选择 100 以内的随机数,生成的序列的值也要求小于 100
# 这个 p 是该算法的精华,在实际中选取 p 的策略要考虑到安全性和性能的因素
d_list = []
p = 0
while True:
    # 每次选取p重置列表
    d_list = []
    p = random.randint(1, 100)
    for k in range(0, 10):
        if c_list[k] % p <= 100:
            d_list.append(c_list[k] % p)
        else:
            break
    if len(d_list) >= 10:
        break
print("p的值为: %s" % p)
print("除以p后的余数为: %s" % d_list)

输出:

对c+1到c+10进行解密: [8078, 15085, 750, 3519, 36051, 60305, 38970, 8948, 70651, 37584]
p的值为: 58
除以p后的余数为: [16, 5, 54, 39, 33, 43, 52, 16, 7, 0]

从上帝视角我们可以发现 B 的 3 亿借助 X 也就是 750 正确出现在序列第三个位置,证明加密解密过程是正确的。接下来,A 将结果序列中自己财富值 i 位置开始的数字执行 +1 操作,将自己财富值隐藏在序列中。

for k in range(i, 10):
    d_list[k] = d_list[k] + 1
print("前i位不动后面数字+1后: %s" % d_list)

输出:

前i位不动后面数字+1后: [16, 5, 54, 39, 33, 44, 53, 17, 8, 1]

这里的输出就是 A 加工后的结果序列,B 接收到结果序列后,查找序列第 j - 1 位的值,也就是自己嵌入财富值的位置,如果该值等于 x mod p 的余数则说明 A 未对该值加 1,这将证明 a < b ,因为 A 的财富在序列之前且小于自己所在的位置,反之,则 b <= a 自己的财富所在位置被 A 加1了,证明 A 的财富位置在 B 位置之后。

# B 用自己财富位置的值 mod p 相等则 A 没有加 1 操作,由此可知 A 的财富小于自己
print("B 接收到第j个数字为: %s" % d_list[j - 1])
print("j-1: %s;x mod p: %s"%(d_list[j - 1],x % p))
if d_list[j - 1] == x % p:
    print("i >= j,A 财富 < B 财富")
    if i >= j:
        print("验证成功")
    else:
        print("代码存在错误")
else:
    print("i < j,A 财富 >= B 财富")
    print("i: %s;j: %s"%(i,j))
    if i < j:
        print("验证成功")
    else:
        print("代码存在错误")

输出:

B 接收到第j个数字为: 54
j-1: 54;x mod p: 54
i >= j,A 财富 < B 财富
验证成功

大功告成,至此,A 和 B 两个富翁在不暴露自己财富值的情况下成功完成了财富多寡的比较。由于我们在上帝视角,所以总有种错觉:位置不就暴露了财富值么?在实际操作中,A 和 B 其实都不知道对方财富值在序列的什么位置,B 只知道自己的位置是否被 A 操作了,所以,B 只能得到自己财富比对方多还是少的结果。

MP-SPDZ 简介

在上一节中,为了简化示例我们并没有实际对秘钥和加密数据进行传输,本节将以开源项目 MP-SPDZ 为例介绍一下安全多方计算的实践方法。MP-SPDZ 是用于对各种安全多方计算 (MPC) 协议进行基准测试的开源软件,例如 SPDZ、SPDZ2k、MASCOT、Overdrive、BMR 混淆电路、Yao 混淆电路(这个 Yao 就是前文介绍的姚期智老师),以及基于三方复制秘密共享和 Shamir 秘密共享的计算。之所以选择 MP-SPDZ 是因为其对于机器学习提供直接的、模块级支持,能够方便我们在智能 UI 领域以及端智能场景快速进行原型验证。

我们可以在 github.com/data61/MP-S… 找到这个开源软件,使用方法非常简单,根据 README 安装必要的编译环境和依赖包,执行 make 编译就可以使用了,非常方便。我在本地测试了 shamir-bmr-party,多放计算结果却不暴露自己的数据。

首先,对 shamir-bmr-party 进行编译(Apple M1 MBP 上测试通过)。

git clone --recursive https://github.com/data61/MP-SPDZ.git
cd MP-SPDZ
make -j 8 shamir-bmr-party.x

根据 MP-SPDZ 官方文档,我们需要在 Programs/Source 文件夹创建程序文件 testgp.mpc。

vi Programs/Source/testgp.mpc

在程序文件里输入如下内容:

a = sint.get_input_from(0)
b = sint.get_input_from(1)
c = sint.get_input_from(2)
sum = a + b + c
print_ln('Results =%s',sum.reveal())

保存退出 :x! 回车后,我们用 MP-SPDZ 提供的脚本编译一下,接着生成用于测试的证书,由于这里我们有三方进行安全计算给命令行参数提供 3 。

# 编译
./compile.py -B 32 testgp
# 打开 OpenSSL 加密传输能力必须要生成证书和秘钥
Scripts/setup-ssl.sh 3

接着,写入一些测试数据到每个 Player 下。

# Player 0 数据 11
echo 11 > Player-Data/Input-P0-0
# Player 1 数据 12
echo 12 > Player-Data/Input-p1-0
# Player 2 数据 13
echo 13 > Player-Data/Input-p2-0

至此,我们完成了所有准备工作,下一步,分别打开 3 个终端执行命令进行测试。

终端 1:

./shamir-bmr-party.x -N 3 0 testgp -pn 8188
Results =36
Significant amount of unused bits of Shamir gf2n_long. For more accurate benchmarks, consider reducing the batch size with -b.
Data sent = 0.572128 MB
Data sent: 0.530496 MB

终端 2:

./shamir-bmr-party.x -N 3 1 testgp -pn 8188
Results =36
Significant amount of unused bits of Shamir gf2n_long. For more accurate benchmarks, consider reducing the batch size with -b.
Data sent = 0.532064 MB
Data sent: 0.57056 MB
Time = 0.017402 seconds

终端 3:

./shamir-bmr-party.x -N 3 2 testgp -pn 8188
Results =36
Significant amount of unused bits of Shamir gf2n_long. For more accurate benchmarks, consider reducing the batch size with -b.
Data sent = 0.172 MB
Data sent: 0.210496 MB
Time = 0.017261 seconds

通过终端打印的信息可以看到,在相互不暴露数据的情况下,Shamir-bmr-party 成功完成了加和的安全多方计算,我们在 Results 中得到 36 = 11 + 12 + 13 从上帝视角来验证,结果是正确的。当然,你会在 README 看到诸多安全多方计算方法、协议,可以参考上述实践过程自己测试一下,这里就不展开了,只对 MP-SPDZ 的 Compiler.ml 模块简单介绍一下,因为这和我们的智能 UI 及端智能戚戚相关。

MP-SPDZ 支持使用选定的 TensorFlow 图进行推理,特别是 CrypTFlow 中使用的 DenseNet、ResNet 和 SqueezeNet。例如可以为 ImageNet 运行 SqueezeNet 推理,如下所示:

git clone https://github.com/mkskeller/EzPC
cd EzPC/Athos/Networks/SqueezeNetImgNet
axel -a -n 5 -c --output ./PreTrainedModel https://github.com/avoroshilov/tf-squeezenet/raw/master/sqz_full.mat
pip3 install scipy==1.1.0
python3 squeezenet_main.py --in ./SampleImages/n02109961_36.JPEG --saveTFMetadata True
python3 squeezenet_main.py --in ./SampleImages/n02109961_36.JPEG --scalingFac 12 --saveImgAndWtData True
cd ../../../..
Scripts/fixed-rep-to-float.py EzPC/Athos/Networks/SqueezeNetImgNet/SqNetImgNet_img_input.inp
./compile.py -R 64 tf EzPC/Athos/Networks/SqueezeNetImgNet/graphDef.bin 1 trunc_pr split
Scripts/ring.sh tf-EzPC_Athos_Networks_SqueezeNetImgNet_graphDef.bin-1-trunc_pr-split

这需要安装 TensorFlow 和 axel ,它使用三方半诚实计算进行推理,类似于 CrypTFlow 的 Porthos。将 最后两行中 1 替换为所需的线程数。如果使用其它协议运行,则需要删除 trunc_prsplit。另请注意,正确的运行可能需要使用 github.com/mkskeller/E… 中提供的补丁。

MP-SPDZ 的 Compiler.ml 模块包含机器学习功能。这是 MP-SPDZ 的实验功能,经过测试的训练功能仅包括逻辑回归,它可以按如下方式运行:

sgd = ml.SGD([ml.Dense(n_examples, n_features, 1),
              ml.Output(n_examples, approx=True)], n_epochs,
             report_loss=True)
sgd.layers[0].X.input_from(0)
sgd.layers[1].Y.input_from(1)
sgd.reset()
sgd.run()

这会加载来自用户 0 方的测量值和来自用户 1 方的标签 (0/1),运行后,模型存储在sgd.layers[0].W和 中sgd.layers[0].b,该approx参数决定是否使用近似 sigmoid 函数。

使用两个密集层的 MNIST 简单网络可以按如下方式训练:

sgd = ml.SGD([ml.Dense(60000, 784, 128, activation='relu'),
              ml.Dense(60000, 128, 10),
              ml.MultiOutput(60000, 10)], n_epochs,
             report_loss=True)
sgd.layers[0].X.input_from(0)
sgd.layers[1].Y.input_from(1)
sgd.reset()
sgd.run()

推理可以如下运行:

data = sfix.Matrix(n_test, n_features)
data.input_from(0)
res = sgd.eval(data)
print_ln('Results: %s', [x.reveal() for x in res])

对于推理/分类,该模块提供了 DenseNet、ResNet 和 SqueezeNet 等神经网络所需的层,使用用户 0 的输入和用户 1 的模型的最小示例如下所示:

graph = Optimizer()
graph.layers = layers
layers[0].X.input_from(0)
for layer in layers:
    layer.input_from(1)
graph.forward(1)
res = layers[-1].Y

写在最后

本文内容只是安全多方计算的一鳞半爪,也只是我学习实践的开端,希望通过分享能够给大家带来一些帮助。虽然只短短两周时间,但安全多方计算给我带来的震撼还是非常大的,尤其是那些在前端智能化领域让我挠头的用户隐私保护问题豁然开朗了。本文只整理了我学习实践的一小部分,我还对 Google 的 TensorFlow Federated 联邦学习体系进行了深入了解,借助联合数据、联邦计算等框架提供的能力,可以方便的把 TensorFlow 模型转换成联邦学习,在不上传用户数据的前提下、借助联合平均的方法安全高效的进行在线学习。随着智能 UI 在更多 BU 落地,割裂的数据、割裂的权重……等都可以借助联邦学习相互促进模型算法的提升,想想就觉得好开心!