拓冰建站拓冰建站
首页 / 资讯中心 / 正文

安全多方计算如何实现隐私保护的加密训练与电子投票

简介一份基于安全多方计算的隐私保护系统完整毕设项目针对大规模电子投票与人工智能加密训练场景实现数据不出域即可完成联合计算与模型推理。项目面向计算机、通信、人工智能、自动化等专业的学生、教师或从业者尤其适合作为课程设计、大作业或毕业设计的参考范本。压缩包共110个文件以55个Python源码文件与30个HTML展示页面为主体辅以CSS样式、CSV格式的标准数据集、GIF动态演示及XML配置等整体体积仅1.13MB结构清晰便于查阅。目前已有386人学习该资源项目代码均经过调试测试可直接运行或二次开发。配套文档说明、可视化监控页面和动图演示能够直观呈现安全多方计算在机器学习中的落地流程对理解隐私计算、密码协议及Python工程实现有较高借鉴价值。1. 从98分的答辩现场说起为什么MPC能同时搞定电子投票和加密训练这套基于安全多方计算的隐私保护系统设计与实现答辩拿到98分不是因为PPT华丽而是评委当场看到三件事同时跑通framingham.csv在密文状态下完成了逻辑回归训练heart.csv和breast_cancer.csv的预测结果在不暴露明文的前提下正常返回电子投票模块在无法解析单个选票的情况下正确统计出各候选人票数。安全多方计算MPC允许多个参与方在互不泄露私有输入的前提下共同完成一次计算这套毕设把它拆成两个典型落地场景人工智能加密训练和大规模电子投票。LR.gif里那条收敛曲线就是逻辑回归在密文上训练的损失下降过程两个可视化页面把训练和测试过程完整呈现。适合正在做隐私计算课设、毕设的学生也适合想搞懂MPC协议层与业务层如何衔接的研发人员。2. MPC协议层加性秘密共享与Beaver三元组的工程落地2.1 为什么选加性秘密共享而不是Shamir门限方案MPC协议层是第一道技术门槛。工程上最常用的两种线性秘密共享是Shamir门限方案和加性秘密共享。Shamir支持(t, n)门限重构n个参与方里任意t个凑齐就能恢复秘密这在授权恢复场景里很有吸引力但它每次乘法都需要拉格朗日插值逻辑回归一个epoch要跑几百次乘法插值开销会直接拖垮训练节奏。加性秘密共享把秘密s拆成s1s2…sn mod p所有参与方各持一份分片加法直接本地完成乘法借助Beaver三元组也只需要一轮掩码交换工程实现简单对必须完整跑通的毕业设计更合适。这套系统选的是Mersenne素数域p2^127-1原因很直接Python大整数在这个域上的模运算速度快且所有中间结果都限制在固定位数内方便后续转成前端可展示的指标。分片数量默认3方单机演示时用多线程模拟多个参与方改配置也能直接扩展成多进程或多机部署。2.2 医疗数据集预处理三个csv各自承担什么角色项目里的framingham.csv是包含4240条记录的Framingham心脏病风险数据breast_cancer.csv是569个样本的威斯康星乳腺癌数据heart.csv是303条记录的UCI心脏病数据。三个数据集都是二分类任务但在系统里的分工不同framingham作为主训练集预测十年内冠心病风险breast_cancer和heart.csv用于跨数据集验证证明模型不是只在单一数据上有效。预处理的关键是特征统一和缺失值处理。framingham原始16列里education、BPMeds、prevalentStroke缺失率差别很大我取age、totChol、sysBP、diaBP、BMI、heartRate、glucose七个连续特征加TenYearCHD标签缺失值用中位数填充。比较关键的一点MPC训练时会频繁做特征归一化但归一化的均值和方差必须在明文域先算好因为密文上做除法和开方代价太高属于工程取舍而非理论限制。数据集样本数特征数任务在系统中的角色framingham.csv424016十年冠心病风险二分类主训练集breast_cancer.csv56930乳腺肿瘤良恶性分类跨数据集验证heart.csv30314心脏病存在性分类跨数据集验证2.2.1 特征归一化在进入MPC前的固定点量化归一化后的特征值都在0到1之间但秘密共享域是整数浮点数不能直接分片。做法是固定点量化把浮点数乘上缩放因子后取整再送入MPC协议层。import pandas as pd import numpy as np SCALE 1 16 # 65536保留16位小数精度 def preprocess_and_quantize(path, feature_cols, label_col): df pd.read_csv(path) for col in feature_cols: df[col] df[col].fillna(df[col].median()) mean, std df[col].mean(), df[col].std() # 均值和方差在明文域计算密文上做除法代价过高 df[col] (df[col] - mean) / (std 1e-9) df[col] (df[col] * SCALE).astype(np.int64) df[label_col] df[label_col].astype(np.int32) return df[feature_cols].values, df[label_col].values量化缩放因子取65536是固定点训练的常见取值。缩放因子太小会丢失梯度逻辑回归的每轮更新量本身就在0.001量级小于1/65536的部分会被直接截断缩放因子太大会让中间结果快速逼近p域上限模运算后数值错乱。训练过程中所有中间结果保持在这个整数域里只在输出层做反量化还原成浮点。2.3 秘密分片与重构随机源比分片算法更值得注意预处理完成后特征矩阵每一行都要拆成分片。加性秘密共享的分片逻辑很简洁一个随机数加一个差值。但随机数必须来自密码学安全随机源Python内置random模块的Mersenne Twister不适用它的状态可预测攻击者拿到若干连续输出后可能恢复整个序列。import os P 2**127 - 1 # Mersenne素数域 def split_secret(secret: int, n_parties: int 3) - list: 把整数秘密拆成n份重构时全部相加即可恢复 if not (0 secret P): raise ValueError(secret out of field) shares [] acc secret % P for _ in range(n_parties - 1): r int.from_bytes(os.urandom(32), big) % P shares.append(r) acc (acc - r) % P shares.append(acc) return shares def reconstruct(shares: list) - int: return sum(shares) % Psplit_secret尾部那个acc是核心它保证所有分片相加等于原始秘密而每个单独分片在域上均匀分布任何一方都无法从自己持有的分片反推明文。n_parties默认3对应系统里的三个计算参与方改成2或5不需要动重构逻辑。secret大于等于P时直接抛异常是为了防止模运算静默吞掉明文信息。2.4 密文乘法Beaver三元组的正确打开方式逻辑回归训练里最频繁的操作是sigmoid的逐元素乘法和梯度计算中的矩阵乘法。加性秘密共享下乘法不能本地完成因为两个分片和的乘积会裂解出交叉项而这些交叉项无法只由本地信息算出。Beaver三元组的思路是预处理阶段生成满足ca*b的随机三元组每方只持有a、b、c各自的分片计算乘法时交换一次掩码后的差值把乘法转成域上的标量组合。def beaver_mul(x_share: int, y_share: int, beaver: tuple, party_idx: int, n_parties: int 3) - int: beaver (a_share, b_share, c_share)满足 c a * b mod P 所有参与方本地组合各自结果后求和即得 x * y a_share, b_share, c_share beaver d_share (x_share - a_share) % P e_share (y_share - b_share) % P # 真实网络环境中这里把 d_share/e_share 广播给其他参与方 # 并接收其他方的分片下面用占位符表示收到的完整值 d_all 0 e_all 0 for j in range(n_parties): # 伪代码d_recv, e_recv network.recv(j, d_e_pair) d_all (d_all receive(j, d)) e_all (e_all receive(j, e)) z_share (c_share d_all * b_share e_all * a_share) % P if party_idx 0: z_share (z_share d_all * e_all) % P return z_share安全性逻辑在于d和e是经过随机掩码后的值与原始x、y之间隔着随机a、b的干扰单独拿到d或e无法反推明文。d_all和e_all对所有参与方可见但掩码后的数值在统计意义上不泄露输入。真正需要保密的数据x和y从未以明文形式出现在任何一方。代码里的receive函数是占位接实际网络层时用gRPC或消息队列替换即可。sigmoid的指数运算在密文上无法精确计算工程里用3阶多项式在[-5, 5]区间内逼近精度足够训练收敛而且多项式求值只有乘法和加法正好落在Beaver框架能力范围内。3. 电子投票模块Paillier同态聚合与可验证审计3.1 五阶段投票状态机同样是隐私计算电子投票和加密训练的需求完全相反。训练关心梯度不泄露投票关心选票不可追踪、结果可验证、不能重复投票。这套系统把投票流程拆成五个阶段注册、投票权认证、选票编码、密文聚合、公开验票。注册阶段每个投票者拿到一次性token认证阶段通过签名验证token合法性编码阶段把选择转为同态加密密文聚合阶段在密文上直接做加法验票阶段由计票方解密汇总结果并公开哈希链。投票模块用Paillier同态加密而不是秘密共享是因为投票的计算形态是典型的单输入多输出加性聚合正好落在Paillier的加法同态性质覆盖范围内而且解密只需要计票方一个角色不需要多方同时在线。阶段输入输出形式安全保障注册投票者身份信息一次性tokentoken与身份绑定投票权认证token nonce签名凭证防止重复投票选票编码候选人编号Paillier密文向量CPA安全密文聚合密文集合聚合密文不泄露单张选票公开验票聚合密文 哈希链明文票数可审计3.2 选票的向量编码与同态累加Paillier加密的核心性质是D(E(m1) * E(m2)) m1 m2密文相乘对应明文相加。把这个性质用在投票上每个投票者把选择编码成One-Hot向量每个分量单独加密计票方在密文上逐项累加得到每个候选人的总票数。# 依赖 phe 库项目中也内置了等价实现 from phe import paillier pub_key, priv_key paillier.generate_paillier_keypair(n_length2048) def cast_ballot(choice: int, num_options: int 5): One-Hot编码后逐项加密返回密文向量 if not 0 choice num_options: raise ValueError(choice index out of range) plain_vec [0] * num_options plain_vec[choice] 1 return [pub_key.encrypt(x) for x in plain_vec] def tally_ballots(ballot_list: list, num_options: int 5): 密文域内累加全程不接触单张明文票 agg [pub_key.encrypt(0) for _ in range(num_options)] for ballot in ballot_list: for i in range(num_options): agg[i] agg[i] ballot[i] return agg def publish_result(agg): return [priv_key.decrypt(c) for c in agg]cast_ballot里加密0和1时Paillier的加密算法自带随机因子同一明文在不同加密调用下密文完全不同外界无法通过比对密文识别两张相同的选票。tally的双重循环时间复杂度是O(票数乘以候选人数)10万张选票、5个候选人的场景约50万次同态加法2048位密钥下单次加法在亚毫秒量级统计过程能控制在分钟级。3.3 防止重复投票与选票可验证性同态加密保护了选票机密性但解决不了投票者是否重复投票的问题。系统在投票权认证阶段为每个token绑定一个nonce计票阶段检查nonce是否已被使用。投票者提交选票后拿到一个收据哈希验票时把收据与公开的哈希链根节点比对能确认自己的票进了票箱又无法向第三方证明投给了谁。3.3.1 哈希链与批次验票Merkle树在这个场景里可以做简化处理用增量哈希链替代。每批次256张选票的密文拼接后与上一批次哈希值串接再做一次SHA256。echo -n ${ballot_cipher_b64}:${prev_hash} | sha256sumballot_cipher_b64是当前批次256张选票密文的base64拼接prev_hash是上一批次计算出的哈希值两者之间用冒号分隔冒号作为分隔符避免拼接歧义。任何人拿到公开的参与方公钥和批次顺序都能重算这条链验证是否有选票被插入或删除。批次大小选256主要是平衡审计粒度和计算量批次太大单批内选票被篡改时定位困难批次太小验票文件膨胀审计时间变长。这里有个面试常追问的点既然哈希链能防篡改为什么还需要Paillier答案是要防内部攻击。计票员即使看到全部密文也无法判断每张票投给谁哈希链只负责完整性同态加密负责机密性两者互相补齐缺一不可。4. 训练与测试可视化AI_TRAIN_SHOW和AI_TEST_SHOW怎么联动后端4.1 两层数据流解密结果出节点聚合指标上页面训练过程的实时展示最难的不是画图而是让页面看到的是MPC跑出来的真实数据而不是mock值。系统的做法是把后端拆成两层MPC节点层负责密文训练聚合服务层在每轮epoch结束后解密出loss和accuracy通过异步GET接口暴露给浏览器。聚合层用一个带过期时间的缓存每5秒刷新一次避免前端轮询直接打到MPC节点上干扰训练线程。数据流是MPC训练、每个epoch结束、聚合层拉取各参与方分片并解密、写入缓存、前端fetch轮询接口。解密只在聚合层出现一次训练过程中参与方之间交换的全是密文或掩码后的差值可视化页面看到的已经是聚合后的明文指标。4.2 训练页面的loss曲线与LR.gif的对应关系AI_TRAIN_SHOW.html负责把训练过程可视化为三块信息右上角损失曲线、左上角准确率数字、下方最近五个epoch的参数分布柱状图。LR.gif就是训练过程中录制的损失下降动画截帧。页面用递归setTimeout完成轮询而不是setInterval因为setInterval在请求响应慢时会堆积回调递归调用天然规避了这个问题。let currentEpoch 0; async function pollTrainingMetrics() { try { const resp await fetch(/api/training/epoch/${currentEpoch 1}); if (resp.status ! 200) { return; // 聚合层缓存还没有新epoch的数据静默跳过 } const data await resp.json(); appendLossPoint(currentEpoch, data.loss); updateAccuracyBanner(data.train_acc); currentEpoch 1; } catch (err) { console.error(polling epoch ${currentEpoch 1} failed:, err); } finally { setTimeout(pollTrainingMetrics, 2000); } } function appendLossPoint(epoch, loss) { const el document.getElementById(loss-curve); const arr el.dataset.points ? JSON.parse(el.dataset.points) : []; arr.push({ epoch, loss }); el.dataset.points JSON.stringify(arr); drawLossLine(arr); }fetch里用resp.status ! 200做提前返回因为聚合层在缓存未命中时返回204前端看到204就静默跳过不报错也不产生噪音数据。轮询间隔设2000毫秒与聚合层缓存刷新周期错开避免每次轮询都穿透到后端。appendLossPoint每次把新点追加进dataset再用SVG polyline重绘折线数据量上限设1000个点超出后丢弃最早的数据点防止长时间训练占用浏览器内存。4.3 测试页面混淆矩阵与跨数据集对比AI_TEST_SHOW.html展示三个数据集上的预测结果布局是三栏对比表加一个主混淆矩阵。页面加载时请求一次/api/evaluate后端返回三个数据集各自的准确率、精确率、召回率和特征重要度排序。breast_cancer和heart.csv规模小预测结果几乎瞬时返回正式演示时我会把framingham的测试批次设成32让页面进度条有一点滚动感体现等待密文计算的过程。4.3.1 演示环境的跨域与静态服务答辩时常用演示机直接开html文件本地file协议下fetch跨域是最常见的翻车点。解决方法是起一个轻量HTTP服务把两个html和css放在同源目录下python3 -m http.server 8080 --bind 0.0.0.0在项目根目录执行后浏览器访问http://localhost:8080/AI_TRAIN_SHOW.html即可正常fetch。配合awesome-bootstrap-checkbox.css和MPC_style.css两套样式文件页面基本不需要改就能适配不同分辨率的屏幕。如果只在服务器本机演示把--bind改成127.0.0.1更安全需要局域网其他机器访问时再用0.0.0.0。4.4 可视化相关参数速查参数取值作用调优方向SCALE65536固定点量化缩放梯度爆炸时降为4096EPOCHS100训练轮次医疗小数据集50即可收敛BATCH_SIZE32每批样本数增大可减少训练噪声轮询间隔2000ms前端刷新频率网络抖动时升高到5000ms缓存过期5s聚合层保护MPC节点增多时升高到10sEPOCHS和BATCH_SIZE应该进配置文件不要硬编码。复用这套源码时需要注意把三个数据集同时塞进训练流程后BATCH_SIZE设成64的话framingham的最后一个batch会不足训练代码里需要drop_lastTrue丢掉尾部否则shape不匹配会直接报错。5. 复现与调整分片粒度、浮点精度和并发安全5.1 浮点精度是MPC训练里最先爆炸的地方直接照搬明文逻辑把浮点权重转成整数参与秘密共享两个epoch就出NaN。根因是逻辑回归的梯度更新是累积运算每轮乘法后都要把结果约回量化域而加性秘密共享的域是模P的循环群数值越过P就会回绕。复现时先跑单特征最小实验把SCALE从65536降到4096跑通收敛再逐步恢复能快速定位是精度问题还是协议实现问题。另一个验证技巧是明文对照同一份数据同时跑明文逻辑回归和MPC版本损失曲线近似、最终准确率差在2个百分点以内说明协议层实现和量化参数都没有问题。5.2 分片数量与通信开销的爬升曲线3方和5方在协议层几乎没有差别但Beaver三元组的分发量随参与方数量增长很快。2方乘法只需要一组三元组5方时掩码交换的配对关系增加到C(5,2)10组通信量接近线性翻倍训练耗时会明显拉长。毕设答辩用3方就足够说明问题展示可扩展性时把n_parties改成5但要在训练开始前预热生成足够多的三元组不要让训练进程等待三元组生成。三元组生成是离线步骤可以和训练线程完全解耦用单独进程提前跑完存盘。5.3 并发安全与随机源隔离训练线程和投票服务如果共用一个随机数生成器存在状态竞争风险。每个线程必须独立持有自己的随机源用os.urandom包装的SystemRandom实例不要用全局random.Random。我写过一个自查测试并发条件下对同一个秘密分片1000次断言所有分片互不相同且重构后等于原值这个测试可以挂在CI里协议层改动后第一时间发现回归。投票token的过期时间设成会话级每周跑一次过期token清理任务防止长驻内存占用积累。这三处修完从高分毕设到可产品化的隐私计算系统只差一个审计日志的运维界面。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门