文章目录
- 一. 概述
- 1.1 CP-ABE基础介绍
- 1.2 访问控制树与BSW方案原理
- 1.2.1 系统模型与角色
- 1.2.2 主要算法
- 1.2.3 访问结构与访问树
- 1.2.4 数学基础
- 二. 具体算法流程
- 2.1 Setup(初始化)
- 2.2 KeyGen(密钥生成)
- 2.3 Encrypt(加密)
- 2.4 Decrypt(解密)
- 2.5 安全性分析
- 2.5.1 选择明文安全性(Chosen-Plaintext Security)
- 2.5.2 抗合谋攻击(Collusion Resistance)
- 2.6 优缺点
- 2.7 总结
- 三. 基于C++、PBC库的源码实现
- 3.1 bswabe.h
- 3.2 bswabe.cpp
- 3.3 main.cpp
- 3.4 编译 & 运行
- 3.5 执行结果
- 3.6 github源码链接
- 四. 参考文献
- 五. 感谢支持
一. 概述
1.1 CP-ABE基础介绍
属性基加密(Attribute-Based Encryption,ABE)是一种公钥加密技术,它允许数据加密和解密基于用户的属性集。ABE主要分为两种类型:密文策略属性基加密(Ciphertext-Policy ABE,CP-ABE)和密钥策略属性基加密(Key-Policy ABE,KP-ABE)。在CP-ABE中,数据加密者定义访问策略,并将其嵌入到密文中;用户的解密密钥与其属性集相关联。只有当用户的属性集满足密文中的访问策略时,才能成功解密。
1.2 访问控制树与BSW方案原理
访问控制树的结构应用于 J. Bethencourt, A. Sahai 和 B. Waters在 IEEE Symposium on Security and Privacy (SP '07) [1], 2007年上发布基于密文策略的属性加密上提及的。这个方案现在也常被简称为BSW(取其三位作者名字的首字母)方案。该方案其实是改善了Sahai等人在2005年发表的基于身份的模糊加密方案(FIBE),该方案将用户身份分解成一系列描述用户身份的属性,加密者加密数据时指定一个属性集合和阈值d,解密者必须拥有至少d个给定的属性才能正确解密密文。FIBE方案是BSW方案的雏形。因为BSW方案是对FIBE方案的改进,在算法设计中也使用了FIBE方案的思想和运算原理,在其基础上添加了访问控制树,以及递归求解访问控制树。所以这里就只介绍BSW方案,只要理解了BSW方案也就理解了FIBE方案。
1.2.1 系统模型与角色
- 属性授权中心(Authority):负责系统的初始化和用户私钥的生成。
- 数据加密者(Encryptor):定义访问策略并加密数据。
- 数据解密者(Decryptor):持有属性集的用户,尝试解密密文。
1.2.2 主要算法
BSW方案包含以下四个主要算法:
- Setup(初始化):生成公共参数和主密钥。
- KeyGen(密钥生成):根据用户的属性集生成私钥。
- Encrypt(加密):根据指定的访问策略加密消息。
- Decrypt(解密):如果用户的属性集满足访问策略,则可以成功解密密文。
1.2.3 访问结构与访问树
BSW方案使用访问树来表示访问策略。访问树的叶子节点是属性,内部节点是逻辑门(如AND、OR)。访问树可以表示复杂的逻辑关系,定义哪些属性组合可以解密密文。
1.2.4 数学基础
- 双线性映射:设
G
0
G_0
G0 和
G
T
G_T
GT是两个有限循环群,阶为素数
p
p
p,
e
:
G
0
×
G
0
→
G
T
e: G_0 \times G_0 \rightarrow G_T
e:G0×G0→GT 是一个双线性映射,具有以下性质:
- 双线性性:对于任意 a , b ∈ Z p a,b \in \mathbb{Z}_p a,b∈Zp, e ( g a , h b ) = e ( g , h ) a b e(g^a, h^b) = e(g,h)^{ab} e(ga,hb)=e(g,h)ab。
- 非退化性: e ( g , g ) ≠ 1 e(g,g) \neq 1 e(g,g)=1。
- 可计算性:映射 e e e 在计算上是高效的。
二. 具体算法流程
该方案用到的基本条件:令 G 0 G_0 G0 为一个阶为素数 p p p 的双线性群, g g g 为 G 0 G_0 G0 的生成元。此外,令 e : G 0 × G 0 → G 1 e: G_0 \times G_0 \rightarrow G_1 e:G0×G0→G1 表示双线性映射。群的大小由安全参数 λ \lambda λ 决定。对于 i ∈ Z p i \in Z_p i∈Zp 和 Z p Z_p Zp 中元素组成的集合 S S S ,我们定义拉格朗日系数 L i ( x ) = Δ i , S ( x ) = ∏ j ∈ S , j ≠ i x − j i − j L_i(x) = \Delta_{i, S}(x)=\prod_{j \in S, j \neq i} \frac{x-j}{i-j} Li(x)=Δi,S(x)=∏j∈S,j=ii−jx−j。此外,我们应用了一个哈希函数 + H : { 0 , 1 } ∗ → G 0 { }^{+} H:\{0,1\}^* \rightarrow G_0 +H:{0,1}∗→G0 ,该函数能将任意字符串描述的属性映射为一个随机的群元素。
2.1 Setup(初始化)
该算法以隐含安全参数 λ \lambda λ 作为输入,输出系统公钥 P K P K PK 和 主私钥 M K M K MK 。
- 首先选择一个阶为素数 p p p 的双线性群 G 0 G_0 G0 ,该群的生成元为 g g g 。
- 然后,随机选择加密指数 α , β ∈ Z p \alpha, \beta \in Z_p α,β∈Zp 。
- 输出的系统公钥为 P K = ( G 0 , g , h = g β , f = g 1 β , e ( g , g ) α ) P K=\left(G_0, g, h=g^\beta, f=g^{\frac{1}{\beta}}, e(g, g)^\alpha\right) PK=(G0,g,h=gβ,f=gβ1,e(g,g)α)
- 系统主密钥为 M K = ( β , g α ) M K=\left(\beta, g^\alpha\right) MK=(β,gα) 。
2.2 KeyGen(密钥生成)
KeyGen ( M K , P K , S ) → S K \operatorname{KeyGen}(M K, P K, S) \rightarrow S K KeyGen(MK,PK,S)→SK :该算法以系统主私钥 M K M K MK ,公钥 P K P K PK 和属性集合 S S S 作为输入,输出用户私钥 S K S K SK。
- 首先选择一个随机数 r ∈ Z p r \in Z_p r∈Zp (随机数用于抵抗合谋攻击),然后对于每个属性 j ∈ S j \in S j∈S ,随机选择 r j ∈ Z p r_j \in Z_p rj∈Zp 。这个过程是属性集合 S S S 里有几个属性,那就在 Z p Z_p Zp 里面随机找几个数(即 r j r_j rj )。
- 然后再按照如下过程计算用户私钥:
S K = ( D = g α + r β , ∀ j ∈ S : D j = g r ⋅ H ( j ) r j , D j ′ = g r j ) S K=\left(D=g^{\frac{\alpha+r}{\beta}}, \forall j \in S: D_j=g^r \cdot H(j)^{r_j}, D_j^{\prime}=g^{r_j}\right) SK=(D=gβα+r,∀j∈S:Dj=gr⋅H(j)rj,Dj′=grj)
我们利用 P K P K PK 和 M K M K MK 里的相关参数和属性集合 S S S 来计算私钥。 ∀ j ∈ S : D j = g r ⋅ H ( j ) r j , D j ′ = g r j \forall j \in S: D_j=g^r \cdot H(j)^{r_j}, D_j^{\prime}=g^{r_j} ∀j∈S:Dj=gr⋅H(j)rj,Dj′=grj ,则表示的是属性集合 S S S 里含有几个属性,我们就从 Z p Z_p Zp 里选出几个随机数来计算相应的 D j D_j Dj 和 D j ′ D_j^{\prime} Dj′ 。
2.3 Encrypt(加密)
加密和解密是算法中设计的关键部分!
Encrypt ( P K , M , A ) → C T \operatorname{Encrypt}(P K, M, A) \rightarrow C T Encrypt(PK,M,A)→CT :该算法输入系统公钥 P K P K PK 、明文 M M M 和基于属性的访问(树)结构 A A A(包含所有属性) ,输出对明文 M M M 加密后密文 C T C T CT ,且只有用户拥有的属性集合满足访问结构 A A A 才能正确解密密文 C T C T CT 。
- 该算法首先对访问结构 A A A 中的访问控制树 T T T 中的每个节点 x x x (包括叶子节点) 选择一个多项式 q x q_x qx 。这些多项式自上向下从根节点 R R R 进行选择。对于树中的每一个节点 x x x ,设多项式 q x q_x qx 的阶 d x d_x dx 为节点 x x x 的阈值 k x k_x kx 减1,即 d x = k x − 1 d_x=k_x-1 dx=kx−1 。
- 从根节点 R R R 开始随机选择 s ∈ Z p s \in Z_p s∈Zp 并设置 q R ( 0 ) = s q_R(0)=s qR(0)=s 。然后随机选择多项式 q R q_R qR 的其他 d R d_R dR 个点来定义该多项式。对于任何其他节点 x x x ,令 q x ( 0 ) = q parent ( x ) ( index ( x ) ) q_x(0)=q_{\text {parent }(x)}(\operatorname{index}(x)) qx(0)=qparent (x)(index(x)) 且随机选择多项式 q x q_x qx 的其他 d x d_x dx 个点来定义该多项式。访问树的结构实例如下图所示:其中根节点隐藏的秘密值就是s=5,然后逐级向下进行秘密分享,根据下面这个示例图详细分析BSW秘密分享方案的原理:(1)随机生成根节点秘密值s=5;(2)根据上面的规则生成随机多项式 q R ( x ) = 5 + 3 x q_R(x)=5+3x qR(x)=5+3x;(3)将index值代入上述 q R ( i n d e x ) q_R(index) qR(index)分别计算得出3个子节点的秘密分享值为8,11,14;(4)重复上面的过程,对于有子节点的中间节点,继续生成随机多项式(如: q R ( x ) = 8 + 4 x + 7 x 2 q_R(x)=8+4x+7x^2 qR(x)=8+4x+7x2 和 q R ( x ) = 14 q_R(x)=14 qR(x)=14),然后继续给子节点进行秘密分配;(5)直至分享到叶子节点(属性节点),作为解密使用的 q y ( 0 ) q_y(0) qy(0)用在密文生成过程中。
- 令
Y
Y
Y 为访问控制树
T
T
T 的所有叶子节点的集合,那么通过访问控制结构构造的密文为:
C T = ( T , C ~ = M e ( g , g ) α s , C = h s , ∀ y ∈ Y : C y = g q y ( 0 ) , C y ′ = H ( att ( y ) ) q y ( 0 ) ) C T=\left(T, \tilde{C}=M e(g, g)^{\alpha s}, C=h^s, \forall y \in Y: C_y=g^{q_y(0)}, C_y^{\prime}=H(\operatorname{att}(y))^{q_y(0)}\right) CT=(T,C~=Me(g,g)αs,C=hs,∀y∈Y:Cy=gqy(0),Cy′=H(att(y))qy(0))
密文里面,首先给了这个访问控制的结构,那会不会造成隐私泄露嘞?严格来说,会,所以后来的一些研究就有如何在密文中隐藏访问结构。本文只说BSW方案,所以,其他的暂且不论。密文中的第二部分就是 C ~ = M e ( g , g ) α s , M \tilde{C}=M e(g, g)^{\alpha s} , M C~=Me(g,g)αs,M 表示明文,这个 s s s 又是干嘛的? s s s 就是根节点处想要隐藏的秘密值(在上面例子中就是根节点处的5),且这个值是在 Z p Z_p Zp 中随机选的。 C = h s C=h^s C=hs ,直观理解即可。 ∀ y ∈ Y : C y = g q y ( 0 ) , C y ′ = H ( att ( y ) ) q y ( 0 ) \forall y \in Y: C_y=g^{q_y(0)}, C_y^{\prime}=H(\operatorname{att}(y))^{q_y(0)} ∀y∈Y:Cy=gqy(0),Cy′=H(att(y))qy(0) ,这是对叶子节点的操作,每个叶子节点按照上面例子说的那种计算方法算下来,都会有一个值,比如上面例子中的"计算机学院"对应的值就是 19 ,也就是 g g g 头上的 q y ( 0 ) q_y(0) qy(0) 这个东东,"计算机学院"这个属性的 q y ( 0 ) q_y(0) qy(0) 就是19。为什么? 因为文中的这个式子 q x ( 0 ) = q parent ( x ) ( i n d e x ( x ) ) q_x(0)=q_{\text {parent }(x)}(index \left.(x)\right) qx(0)=qparent (x)(index(x)) ,这个式子的意思就是这个叶子节点的属性取在其父节点的index的值,作为其自变量的值带入其父节点的多项式,算出的结果就是这个叶子节点的值,即 q y ( 0 ) 。 C y ′ = H ( att ( y ) ) q y ( 0 ) q_y(0) 。 C_y^{\prime}=H(\operatorname{att}(y))^{q_y(0)} qy(0)。Cy′=H(att(y))qy(0) 中 att ( y ) \operatorname{att}(y) att(y) 在文中表示的是一个函数,这个函数表示 y y y 是叶子节点且与 y y y 的属性关联,最后再做一次哈希运算。
2.4 Decrypt(解密)
Decrypt ( P K , C T , S K ) → M \operatorname{Decrypt}(P K, C T, S K) \rightarrow M Decrypt(PK,CT,SK)→M :该算法输入系统公钥 P K P K PK 、密文 C T C T CT 和用户私钥 S K S K SK 作为输入,若 S S S满足 A A A ,则能够正确解密密文并输出明文 M 。 M_。 M。
解密过程是一个递归算法 D e c r y p t N o d e ( C T , S K , x ) DecryptNode (C T, S K, x) DecryptNode(CT,SK,x) 。该算法输入为一个密文 C T = ( T , C ~ , C , ∀ y ∈ Y : C y , C y ′ ) C T=\left(T, \tilde{C}, C, \forall y \in Y: C_y, C_y^{\prime}\right) CT=(T,C~,C,∀y∈Y:Cy,Cy′) ,一个基于属性集 S S S 的私钥 S K S K SK 和树 T T T 中的一个节点 x x x
假如 x x x 是一个叶子节点,令 i = att ( x ) i=\operatorname{att}(x) i=att(x) ,假如 i ∈ S i \in S i∈S , 定义:
DecryptNode ( C T , S K , x ) = e ( D i , C x ) e ( D i ′ , C x ′ ) = e ( g r ⋅ H ( i ) r i , g q x ( 0 ) ) e ( g r i , H ( i ) q x ( 0 ) ) = e ( g , g ) r ⋅ q x ( 0 ) \operatorname{DecryptNode}(C T, S K, x)=\frac{e\left(D_i, C_x\right)}{e\left(D_i^{\prime}, C_x^{\prime}\right)}=\frac{e\left(g^r \cdot H(i)^{r_i}, g^{q_x(0)}\right)}{e\left(g^{r_i}, H(i)^{q_x(0)}\right)}=e(g, g)^{r \cdot q_x(0)} DecryptNode(CT,SK,x)=e(Di′,Cx′)e(Di,Cx)=e(gri,H(i)qx(0))e(gr⋅H(i)ri,gqx(0))=e(g,g)r⋅qx(0)
这个计算过程需要用到双线性映射的性质。双线性对映射具有如下性质:
(1)
∀
u
1
,
u
2
∈
G
\forall u_1, u_2 \in G
∀u1,u2∈G ,有
e
(
u
1
u
2
,
v
)
=
e
(
u
1
,
v
)
e
(
u
2
,
v
)
e\left(u_1 u_2, v\right)=e\left(u_1, v\right) e\left(u_2, v\right)
e(u1u2,v)=e(u1,v)e(u2,v) ;
(2)
∀
u
,
v
∈
G
,
∀
a
,
b
∈
Z
p
∗
\forall u, v \in G, \forall a, b \in Z_p^*
∀u,v∈G,∀a,b∈Zp∗ ,有
e
(
u
a
,
v
b
)
=
e
(
u
,
v
)
a
b
=
e
(
u
b
,
v
a
)
e\left(u^a, v^b\right)=e(u, v)^{a b}=e\left(u^b, v^a\right)
e(ua,vb)=e(u,v)ab=e(ub,va) 。
如果 i ∉ S i \notin S i∈/S 的话,那么这个算法就输出 ⊥ \perp ⊥ ,表示控制结构中的属性不在个人私钥对应的属性集合中,则访问控制结构不满足私钥属性集合,则不能解密。
现在考虑非叶子节点时的递归情况,当 x x x 是非叶子节点时,算法的计算情况如下:对于节点 x x x 的所有孩子节点 z z z ,调用函数 Decrypt Node ( C T , S K , z ) \operatorname{Decrypt} \operatorname{Node}(C T, S K, z) DecryptNode(CT,SK,z) 并存储其结果为 F z F_z Fz 。令 S x S_x Sx 为一个任意的大小为 k x k_x kx 的孩子节点 z z z 的集合,并且满足 F z ≠ ⊥ F_z \neq \perp Fz=⊥ 。如果不存在这样的集合,函数就返回 ⊥ \perp ⊥ 。否则,我们就计算:
F x = ∏ z ∈ S x F z Δ i , S x ′ ( 0 ) = ∏ z ∈ S x ( e ( g , g ) r ⋅ q parent ( z ) ( index ( z ) ) ) Δ i , S x ′ ( 0 ) = ∏ z ∈ S x e ( g , g ) r ⋅ q x ( i ) ⋅ Δ i , S x ′ ( 0 ) = e ( g , g ) r ⋅ q x ( 0 ) \begin{aligned} F_x & =\prod_{z \in S_x} F_z^{\Delta_{i, S'_x}(0)}=\prod_{z \in S_x}\left(e(g, g)^{r \cdot q_{\text {parent }(z)}(\text { index }(z))}\right)^{\Delta_{i, S'_x}(0)} \\ & =\prod_{z \in S_x} e(g, g)^{r \cdot q_x(i) \cdot \Delta_{i, S'_x}(0)}=e(g, g)^{r \cdot q_x(0)} \end{aligned} Fx=z∈Sx∏FzΔi,Sx′(0)=z∈Sx∏(e(g,g)r⋅qparent (z)( index (z)))Δi,Sx′(0)=z∈Sx∏e(g,g)r⋅qx(i)⋅Δi,Sx′(0)=e(g,g)r⋅qx(0)
其中 i = index ( z ) , S x ′ = { index ( z ) : z ∈ S x } i=\operatorname{index}(z), S_x^{\prime}=\left\{\operatorname{index}(z): z \in S_x\right\} i=index(z),Sx′={index(z):z∈Sx} 。
F z F_z Fz 是非叶子节点 x x x 的所有子节点带入 D e c r y p t N o d e ( C T , S K , z ) Decrypt Node(C T, S K, z) DecryptNode(CT,SK,z) 后的结果,那不就是把 x x x节点的所有子节点的多项式的值(即 q parent ( z ) ( q_{\text {parent }(z)}( qparent (z)( index ( z ) ) (z)) (z)) ) 都加入进去进行拉格朗日插值法计算,得到非叶子节点 x x x 的多项式,直到了它的多项式,也就知道这个节点要隐藏的秘密值。然后层层迭代计算,直到算到根节点的多项式,也就知道了根节点隐藏的秘密值 s s s ,即上面式子结果中的 q x ( 0 ) = 5 q_x(0) = 5 qx(0)=5 。结合上面示例图多看几遍就可理解了。
所以最后的解密步骤只需要非常简单且舒服的进行一步就行。其中 A = e ( g , g ) r s A=e(g, g)^{r s} A=e(g,g)rs 。
M = C ~ / ( e ( C , D ) / A ) = ( M ⋅ e ( g , g ) α s ) / ( e ( h s , g ( α + r ) / β ) / e ( g , g ) r s ) M=\tilde{C} /(e(C, D) / A)=(M\cdot e(g,g)^{\alpha s}) /\left(e\left(h^s, g^{(\alpha+r) / \beta}\right) / e(g, g)^{r s}\right) M=C~/(e(C,D)/A)=(M⋅e(g,g)αs)/(e(hs,g(α+r)/β)/e(g,g)rs)
这个计算过程就比较简单了,稍微手算一下,即可得到最后的明文 M M M 。
2.5 安全性分析
2.5.1 选择明文安全性(Chosen-Plaintext Security)
BSW方案在随机预言机模型下是选择明文安全的。
1. 安全模型
BSW方案的安全性基于判定性双线性Diffie-Hellman假设(Decisional Bilinear Diffie-Hellman,DBDH)。在随机预言机模型下,BSW方案被证明是选择明文安全的。
2. DBDH问题
给定 ( g , g a , g b , g c ) (g, g^a, g^b, g^c) (g,ga,gb,gc),判定 e ( g , g ) a b c e(g, g)^{abc} e(g,g)abc与随机元素 e ( g , g ) z e(g, g)^z e(g,g)z是否相同。
3. 安全性证明思路
假设存在一个攻击者 A \mathcal{A} A,能够在选择明文攻击(Chosen-Plaintext Attack,CPA)下破坏BSW方案的安全性,我们可以构造一个模拟器 B \mathcal{B} B,利用 A \mathcal{A} A来解决DBDH问题,从而与DBDH假设矛盾。
4. 具体论证
-
初始化
模拟器 B \mathcal{B} B接收到一个DBDH问题实例 ( g , g a , g b , g c , Z ) (g, g^a, g^b, g^c, Z) (g,ga,gb,gc,Z),其中 Z Z Z是 e ( g , g ) a b c e(g, g)^{abc} e(g,g)abc 或 e ( g , g ) z e(g, g)^z e(g,g)z。
-
模拟系统参数
-
公共参数:
P K = ( G 0 , g , g y = g a , { T i = g t i } i = 1 n ) PK = \left( G_0, g, g^y = g^a, \{ T_i = g^{t_i} \}_{i=1}^n \right) PK=(G0,g,gy=ga,{Ti=gti}i=1n)
模拟器将 g y g^y gy设置为 g a g^a ga。
-
对于每个属性 A i A_i Ai,选择随机 t i ∈ Z p t_i \in \mathbb{Z}_p ti∈Zp。
-
-
选择明文攻击
攻击者 A \mathcal{A} A提出要挑战的明文 M M M 和访问策略 T ∗ T^* T∗。
-
加密模拟
模拟器需要生成密文 C T CT CT,但不知道 y y y,因为 y = a y = a y=a对模拟器是未知的。
-
选择随机数 s ∈ Z p s \in \mathbb{Z}_p s∈Zp。
-
计算: C = M ⋅ Z = M ⋅ e ( g , g ) a b c 或 M ⋅ e ( g , g ) z C = M \cdot Z = M \cdot e(g, g)^{abc} \quad \text{或} \quad M \cdot e(g, g)^z C=M⋅Z=M⋅e(g,g)abc或M⋅e(g,g)z
-
如果 Z = e ( g , g ) a b c Z = e(g, g)^{abc} Z=e(g,g)abc,则: C = M ⋅ e ( g , g ) y ⋅ s C = M \cdot e(g, g)^{y \cdot s} C=M⋅e(g,g)y⋅s
-
如果 Z Z Z是随机元素,则 C C C 与正确的密文分布相同。
-
-
其余密文组件根据访问策略 T ∗ T^* T∗和 g b , g c g^b, g^c gb,gc构造。
-
-
私钥生成模拟
当攻击者请求属性集 S S S的私钥时,模拟器需要生成对应的私钥。
-
对于每个属性 A i ∈ S A_i \in S Ai∈S: D i = g ( y + t i ) r i = ( g a ⋅ g t i ) r i D_i = g^{\frac{(y + t_i)}{r_i}} = (g^a \cdot g^{t_i})^{r_i} Di=gri(y+ti)=(ga⋅gti)ri
模拟器可以计算 D i D_i Di而不需要知道 y y y。
-
-
解密能力
如果 A \mathcal{A} A 能够成功解密密文 C C C,则说明 Z = e ( g , g ) a b c Z = e(g, g)^{abc} Z=e(g,g)abc;否则, Z Z Z 是随机元素。
-
结论
模拟器 B \mathcal{B} B 利用攻击者 A \mathcal{A} A 的能力,成功区分了 Z Z Z 的类型,从而解决了DBDH问题,与DBDH假设矛盾。
因此,BSW方案在随机预言机模型下是选择明文安全的。
2.5.2 抗合谋攻击(Collusion Resistance)
攻击者无法通过组合不同的属性集合来解密不满足访问策略的密文。
1. 问题描述
抗合谋攻击指的是,即使多个用户合谋,组合他们的属性集合,也无法解密他们各自都无权访问的密文。
2. 原理分析
在BSW方案中,私钥与用户的属性集强绑定,且解密过程依赖于属性集合满足访问策略时的结构化计算。因此,简单地组合不同用户的私钥并不能构造出有效的私钥来解密不满足他们各自属性的密文。主要通过私钥的随机数 r , r i r , r_i r,ri来保证多个用户私钥不能合谋。
3. 具体论证
-
私钥结构
用户 U i U_i Ui 的私钥组件: D i = g ( y + t A i r i ) D_{i} = g^{\left( \frac{y + t_{A_i}}{r_i} \right)} Di=g(riy+tAi)
其中 t A i t_{A_i} tAi 是属性 A i A_i Ai 对应的系统参数, r i r_i ri 是随机数。
-
解密过程中的双线性对运算
在解密过程中,需要计算: e ( D i , C i ) = e ( g ( y + t A i r i ) , g r i ) = e ( g , g ) y + t A i e(D_i, C_i) = e\left( g^{\left( \frac{y + t_{A_i}}{r_i} \right)}, g^{r_i} \right) = e(g, g)^{y + t_{A_i}} e(Di,Ci)=e(g(riy+tAi),gri)=e(g,g)y+tAi
-
组合攻击的困难
假设两个用户 U 1 U_1 U1 和 U 2 U_2 U2 分别持有属性 A 1 A_1 A1 和 A 2 A_2 A2,但他们的属性集合 { A 1 , A 2 } \{A_1, A_2\} {A1,A2} 仍然不足以满足访问策略 T T T。
-
他们尝试组合私钥 D 1 D_1 D1 和 D 2 D_2 D2,但由于私钥中的随机性 r i r_i ri 是彼此独立的,无法消除这些随机性。
-
双线性对运算的性质确保了无法通过简单的数学操作组合不同的 D i D_i Di 来得到新的有效私钥组件。
-
-
数学证明
假设攻击者试图构造新的私钥组件 D ′ D' D′: D ′ = D 1 α ⋅ D 2 β D' = D_1^{\alpha} \cdot D_2^{\beta} D′=D1α⋅D2β
其中 α , β \alpha, \beta α,β 是某些系数。计算: D ′ = g ( α ( y + t A 1 ) / r 1 + β ( y + t A 2 ) / r 2 ) D' = g^{\left( \alpha(y + t_{A_1})/r_1 + \beta(y + t_{A_2})/r_2 \right)} D′=g(α(y+tA1)/r1+β(y+tA2)/r2)
由于 r 1 r_1 r1 和 r 2 r_2 r2 是独立的随机数,攻击者无法消除分母中的 r 1 , r 2 r_1, r_2 r1,r2,从而无法得到形式正确的私钥组件。
4. 结论
由于私钥中的随机性和双线性对的性质,合谋的攻击者无法组合他们的私钥来解密他们各自无权访问的密文。因此,BSW方案具有抗属性集合攻击的能力。
总结
通过以上详细的公式和原理论证,我们可以看到:
-
选择明文安全性:BSW方案基于DBDH假设,在随机预言机模型下是选择明文安全的。攻击者无法在不破坏DBDH假设的情况下,区分密文中的加密元素,从而无法恢复明文。
-
抗属性集合攻击:由于私钥中的随机性和双线性对的数学性质,攻击者无法通过组合不同属性集合的私钥来解密不满足访问策略的密文,保证了系统的抗合谋性。
2.6 优缺点
- 优点:
- 支持复杂的访问策略。
- 不需要额外的安全信道传递密钥。
- 缺点:
- 计算和存储开销较大,特别是当访问策略复杂时。
- 选择明文安全,不能抵抗选择密文攻击。
2.7 总结
BSW方案是CP-ABE领域的奠基性工作,通过将访问策略直接嵌入到密文中,实现了基于属性的灵活访问控制。它采用访问树结构和双线性映射的数学工具,提供了强大的表达能力和安全性。然而,为了在实际应用中提高效率和安全性,后续的研究对BSW方案进行了改进和优化。
三. 基于C++、PBC库的源码实现
下面介绍基于C++以及PBC库的源码实现全流程,PBC库的安装以及使用可以参考之前的博客,这里不再赘述。源码主要包括bswabe.h,bswabe.cpp,main.cpp
三个程序。
3.1 bswabe.h
定义相关结构体以及功能函数。
// bswabe.h
#ifndef BSWABE_H
#define BSWABE_H
#include <pbc/pbc.h>
#include <string>
#include <vector>
/***************************************part1: SetUp阶段生成系统公钥BswabePub和主私钥BswabeMsk***********************************************/
// 公钥结构体,包含系统配对和公钥元素
struct BswabePub {
pairing_t pairing; // 双线性配对参数
element_t g; // 群生成元 g ∈ G_0
element_t h; // h = g^β ∈ G_0
element_t f; // f = g^(1/β) ∈ G_0
element_t e_gg_alpha;// e(g, g)^α ∈ G_T
};
// 主私钥结构体
struct BswabeMsk {
element_t beta; // β ∈ Z_p
element_t g_alpha; // g^α ∈ G_0
};
/***************************************part2: KeyGen阶段生成用户私钥:SK={D,D_j,D_j'}***********************************************/
// 用户私钥中的属性组件结构体
struct BswabePrvComp {
std::string attr; // 属性名
element_t d; // D_j = g^r * H(j)^r_j ∈ G_0
element_t dp; // D_j' = g^r_j ∈ G_0
};
// 用户私钥结构体
struct BswabePrv {
element_t d; // D = g^((α + r) / β)
std::vector<BswabePrvComp*> comps; // 属性组件列表
};
/***************************************part3: Encrypt阶段生成密文:CT={BswabePolicy(树结构),cs,c,C_y,C_y'}***********************************************/
// 多项式结构体,用于加密策略树的每个节点
struct BswabePolynomial {
int deg; // 多项式的度
std::vector<element_s*> coef; // 多项式系数
};
// 访问策略节点结构体
struct BswabePolicy {
int k; // 阈值 k
std::string attr; // 属性名(叶子节点)
BswabePolynomial* q; // 多项式 q_x
std::vector<BswabePolicy*> children; // 子节点
element_t c; // 密文CT中C_y = g^{q_y(0)} ∈ G_0
element_t cp; // 密文CT中C_y' = H(att(y))^{q_y(0)} ∈ G_0
bool satisfiable; // 用于解密时是否满足条件
std::vector<int> satl;// 满足条件的子节点索引列表: 从1开始...
};
// 密文结构体
struct BswabeCph {
element_t cs; // 加密后的明文 \tilde{C}
element_t c; // h^s
BswabePolicy* p; // 访问策略的根节点
};
/***************************************part4: Decrypt阶段生成明文:M={e,b}***********************************************/
// 解密结果结构体
struct BswabeElementBoolean {
element_t e; // 解密结果
bool b; // 解密成功标志
};
/***************************************函数声明***********************************************/
// 1.SetUp:系统初始化函数,生成公钥和主私钥
void bswabe_setup(BswabePub* pub, BswabeMsk* msk);
// 2.KeyGen:用户私钥生成函数,基于主私钥和用户属性生成用户私钥
BswabePrv* bswabe_keygen(BswabePub* pub, BswabeMsk* msk, const std::vector<std::string>& attrs);
// 3.Encrypt:加密函数,根据访问策略加密明文
BswabeCph* bswabe_enc(BswabePub* pub, const std::string& policy_str, element_t m);
// 4.Decrypt:解密函数,使用用户私钥解密密文
BswabeElementBoolean* bswabe_dec(BswabePub* pub, BswabePrv* prv, BswabeCph* cph);
// 释放用户私钥
void free_bswabe_prv(BswabePrv* prv);
// 释放密文
void free_bswabe_cph(BswabeCph* cph);
// 打印策略树(用于调试)
void print_policy_tree(BswabePolicy* p, int level = 0);
#endif // BSWABE_H
3.2 bswabe.cpp
具体功能实现:
#include "bswabe.h"
#include <openssl/sha.h>
#include <iostream>
#include <sstream>
#include <stack>
#include <map>
#include <cstring>
#include <algorithm> // 为 std::find 提供支持
#include <sstream> // 引入字符串流库
// 哈希函数,将字符串映射为群元素
void element_from_string(element_t h, const std::string& s) {
unsigned char digest[SHA_DIGEST_LENGTH];
SHA1(reinterpret_cast<const unsigned char*>(s.c_str()), s.length(), digest);
element_from_hash(h, digest, SHA_DIGEST_LENGTH); // 将哈希值转换为群元素
}
/**
* 1.SetUp:系统初始化函数
*
* 功能描述:
* 此函数用于初始化基于双线性对的加密系统,生成公共参数(公钥)和主私钥。它读取配对参数文件,初始化配对环境,并生成系统所需的公共元素和私有元素。
*
* 输入参数:
* - pub:BswabePub*,公钥结构体指针,将在函数中被初始化和设置。
* - msk:BswabeMsk*,主私钥结构体指针,将在函数中被初始化和设置。
*
* 返回值:
* - 无。函数通过修改传入的 pub 和 msk 指针来设置公钥和主私钥。
*
* 注意事项:
* - 在调用此函数之前,pub 和 msk 需要已分配内存,但其中的元素无需初始化。
* - 函数内部会读取配对参数文件,路径需要根据实际环境修改,确保参数文件存在并可读取。
* - 函数结束后,pub 和 msk 中的元素已初始化,使用完毕后需要调用适当的清理函数释放内存。
* - 函数内部使用的临时元素在函数结束前已被正确清理。
* - 如果无法打开参数文件或读取失败,函数将打印错误信息并退出程序。
*/
void bswabe_setup(BswabePub* pub, BswabeMsk* msk) {
element_t alpha, beta_inv;
pbc_param_t param;
// 初始化pairing,通过从参数文件读取
char param_buf[2048];
FILE* param_file = fopen("/home/hututu/instllpkg/pbc-0.5.14/param/a.param", "r"); // 替换为实际的参数文件路径
if (!param_file) {
fprintf(stderr, "Error opening param file\n");
exit(1);
}
// 读取文件内容
size_t count = fread(param_buf, 1, 2048, param_file);
fclose(param_file);
if (!count) pbc_die("Error reading param file");
// 根据读取的参数初始化 pairing
pairing_init_set_buf(pub->pairing, param_buf, count);
// 初始化公钥和私钥的元素
element_init_G1(pub->g, pub->pairing);
element_init_G1(pub->h, pub->pairing);
element_init_G1(pub->f, pub->pairing);
element_init_GT(pub->e_gg_alpha, pub->pairing);
element_init_Zr(alpha, pub->pairing);
element_init_Zr(msk->beta, pub->pairing);
element_init_G1(msk->g_alpha, pub->pairing);
// 随机选择 α 和 β
element_random(alpha); // 随机选择 α ∈ Z_p
element_random(msk->beta); // 随机选择 β ∈ Z_p
element_random(pub->g); // 随机生成元 g ∈ G1
// 计算 g^α
element_pow_zn(msk->g_alpha, pub->g, alpha); // g^α
// 计算 β 的逆元并使用它进行相关计算
element_init_Zr(beta_inv, pub->pairing); // β 的逆元
element_invert(beta_inv, msk->beta); // beta_inv = 1/β
element_pow_zn(pub->f, pub->g, beta_inv); // f = g^{1/β}
element_pow_zn(pub->h, pub->g, msk->beta); // h = g^β
// 计算 e(g, g)^α
element_pairing(pub->e_gg_alpha, pub->g, msk->g_alpha); // e(g, g)^α
// 清除临时元素
element_clear(alpha);
element_clear(beta_inv);
}
/**
* 2.KeyGen:用户私钥生成函数
*
* 功能描述:
* 根据系统公钥、公钥参数和用户的属性集合,生成用户的私钥。私钥包括主私钥部分 D,以及与用户属性相关的部分 D_j 和 D_j'。
*
* 输入参数:
* - pub:BswabePub*,系统公钥结构体,包含公共参数(如 g、h、f、e_gg_alpha 等)。
* - msk:BswabeMsk*,主私钥结构体,包含私有参数(如 β、g^α 等)。
* - attrs:const std::vector<std::string>&,用户属性的字符串列表。
*
* 返回值:
* - BswabePrv*,生成的用户私钥结构体,包含 D 以及属性组件 D_j 和 D_j'。
*
* 注意事项:
* - 确保在调用此函数之前,公钥 pub 和主私钥 msk 已正确初始化。
* - 返回的 BswabePrv* 指针需要在使用完毕后手动释放,防止内存泄漏。
* - 函数内部使用的临时元素在函数结束前已被正确清理。
* - 此函数不验证输入参数的合法性,调用者需确保输入的正确性。
*/
BswabePrv* bswabe_keygen(BswabePub* pub, BswabeMsk* msk, const std::vector<std::string>& attrs) {
BswabePrv* prv = new BswabePrv(); // 用户私钥,D,D_j,D_j'
element_t r,g_r,beta_inv; // 随机数r ∈ Z_p
element_init_Zr(r, pub->pairing);
element_init_G1(g_r, pub->pairing);
element_init_G1(prv->d, pub->pairing);
element_init_Zr(beta_inv, pub->pairing);
// 1. 随机选择 r ∈ Z_p
element_random(r); // 随机选择 r ∈ Z_p
// 2. 计算 beta 的逆元 beta_inv = 1 / beta
element_invert(beta_inv, msk->beta);
// 3. 计算 g^r
element_pow_zn(g_r, pub->g, r); // g_r = g^r
// 4. 计算 g^{alpha + r} = g^alpha * g^r
element_mul(prv->d, msk->g_alpha, g_r); // D = g^alpha * g^r = g^(α + r)
// 5. 计算 D = (g^{alpha + r})^{beta_inv}, 现在 D 即为所需的私钥元素 temp = g^{(α + r)/β}
element_pow_zn(prv->d, prv->d, beta_inv); // D = temp^{beta_inv}
// 6. 生成与用户属性相关的:D_j, D_j'
for (const std::string& attr : attrs) {
BswabePrvComp* comp = new BswabePrvComp();
comp->attr = attr;
element_t rj;
element_init_Zr(rj, pub->pairing);
element_random(rj); // 随机选择 r_j ∈ Z_p
element_init_G1(comp->d, pub->pairing); // D_j
element_init_G1(comp->dp, pub->pairing); // D_j'
element_t h_attr,h_attr_rj;
element_init_G1(h_attr, pub->pairing);
element_init_G1(h_attr_rj, pub->pairing);
element_from_string(h_attr, comp->attr); // 根据用户属性string生成H(j)
element_pow_zn(h_attr_rj, h_attr, rj); // h_attr_rj = H(j)^rj
element_pow_zn(comp->d, pub->g, r); // D_j = g^r
element_mul(comp->d, comp->d, h_attr_rj); // g^r * H(j)^r_j
element_pow_zn(comp->dp, pub->g, rj); // D_j' = g^{r_j}
prv->comps.push_back(comp); // 将与某一用户属性相关的D_j与D_j'存入prv->comps中
element_clear(h_attr);
element_clear(h_attr_rj);
element_clear(rj);
}
// 清除元素
element_clear(r);
element_clear(beta_inv);
element_clear(g_r);
return prv;
}
// 生成访问策略的节点
BswabePolicy* base_node(int k, const std::string& s) {
BswabePolicy* p = new BswabePolicy();
p->k = k;
p->attr = s;
p->q = nullptr;
p->satisfiable = false;
return p;
}
/**
* 解析访问策略的后缀表达式并生成策略树函数
*
* 功能描述:此函数用于解析给定的后缀表达式形式的访问策略字符串,并将其转换为策略树(`BswabePolicy` 结构体)。策略字符串采用逆波兰表示法,其中属性和阈值门以空格分隔。函数通过遍历令牌并使用栈来构建策略树。
*
* 输入参数:
* - `s`:`const std::string&`,后缀表达式形式的策略字符串,例如 `"attr1 attr2 1of2"`。
*
* 返回值:
* - `BswabePolicy*`,解析得到的策略树的根节点指针。如果解析失败,程序将输出错误信息并终止。
*
* 注意事项:
* - 策略字符串应按正确的后缀表达式格式编写,属性和阈值门之间以空格分隔。
* - 阈值门的格式为 `"kofn"`,表示在 `n` 个子节点中至少满足 `k` 个条件。
* - 函数假定输入的策略字符串格式正确,不进行格式验证。
* - 函数使用了 `exit(1)` 在发生错误时终止程序,实际使用中可修改为抛出异常或其他错误处理方式。
*/
BswabePolicy* parse_policy_postfix(const std::string& s) {
std::vector<std::string> tokens; // 用于存储拆分后的策略令牌
std::istringstream iss(s);
std::string token;
// 将策略字符串按空格拆分为令牌,并存入 tokens 向量
while (iss >> token) {
tokens.push_back(token);
}
std::vector<BswabePolicy*> stack; // 栈,用于构建策略树
// 遍历每个令牌,构建策略树
for (const auto& tok : tokens) {
if (tok.find("of") == std::string::npos) {
// 如果令牌不包含 "of",则认为是属性,创建叶子节点并压入栈
stack.push_back(base_node(1, tok)); // 叶子节点,阈值 k=1,属性名为 tok
} else {
// 令牌包含 "of",表示是阈值门,需要解析 k 和 n
size_t pos = tok.find("of");
int k = std::stoi(tok.substr(0, pos)); // 提取阈值 k
int n = std::stoi(tok.substr(pos + 2)); // 提取子节点数量 n
// 检查栈中是否有足够的节点供弹出
if (stack.size() < n) {
std::cerr << "错误:栈中节点不足,无法弹出所需的子节点数" << std::endl;
exit(1);
}
// 创建新的策略节点,阈值为 k,属性名为空(因为是内部节点)
BswabePolicy* node = base_node(k, ""); // 阈值节点,阈值 k=k,属性名为空
node->children.resize(n); // 调整子节点向量的大小
// 从栈中弹出 n 个节点,作为当前节点的子节点
for (int i = n - 1; i >= 0; --i) {
node->children[i] = stack.back(); // 从栈顶获取子节点
stack.pop_back(); // 弹出栈顶元素
}
// 将新创建的节点压入栈
stack.push_back(node);
}
}
// 解析结束后,栈中应只剩下一个节点,即根节点
if (stack.size() != 1) {
std::cerr << "错误:策略字符串格式不正确,无法生成唯一的根节点" << std::endl;
exit(1);
}
return stack.back(); // 返回根节点
}
// 打印策略树(用于调试)
void print_policy_tree(BswabePolicy* p, int level) {
for (int i = 0; i < level; ++i)
std::cout << " ";
std::cout << "Node: k = " << p->k << ", attr = " << p->attr << std::endl;
for (const auto& child : p->children) {
print_policy_tree(child, level + 1);
}
}
/**
* 生成随机多项式函数
*
* 功能描述:生成一个指定度数的随机多项式,并使该多项式在零点处的值为指定的 `zero_val`。多项式用于加密策略树中的各个节点,以实现访问控制策略。
*
* 输入参数:
* - deg:int,多项式的度数。
* - zero_val:element_t,指定的零点处的值,即多项式在 x=0 处的值。
*
* 返回值:
* - BswabePolynomial*,生成的多项式结构体指针,包含多项式的度数和系数。
*
* 注意事项:
* - 返回的多项式结构体需要在使用完毕后手动释放其内存,包括多项式系数中的元素,防止内存泄漏。
* - 函数内部使用了 PBC 库的元素类型,调用者需确保 PBC 环境已正确初始化。
* - 为了防止内存泄漏,函数在每次分配元素后,都需要确保在程序结束时对其进行释放。
*/
BswabePolynomial* rand_poly(int deg, element_t zero_val) {
BswabePolynomial* q = new BswabePolynomial(); // 创建新的多项式结构体
q->deg = deg; // 设置多项式的度数
q->coef.resize(deg + 1); // 调整系数向量的大小,系数数量为 deg + 1
// 初始化多项式的系数元素
for (int i = 0; i <= deg; i++) {
q->coef[i] = (element_s*)malloc(sizeof(element_s)); // 给每个系数coef分配存储空间
element_init_same_as(q->coef[i], zero_val); // 初始化每个系数元素,与 zero_val 类型相同
}
// 设置多项式在 x=0 处的值为 zero_val,即常数项,也即s=qx(0)
element_set(q->coef[0], zero_val);
// 为多项式的其他系数赋值
for (int i = 1; i <= deg; i++) {
element_random(q->coef[i]); // 随机生成系数元素
// element_set_si(q->coef[i], 2); // (可选)将系数设置为固定值,例如 2,用于测试
}
return q; // 返回生成的多项式结构体指针
}
/**
* 评估多项式在指定点的值函数
*
* 功能描述:计算给定多项式 `q` 在点 `x` 处的值,并将结果存储在元素 `r` 中。多项式使用系数表示,其中系数为群元素类型(`element_t`)。该函数适用于基于双线性对的密码系统中的多项式求值操作。
*
* 输入参数:
* - `q`:`BswabePolynomial*`,指向多项式结构体的指针,包含多项式的度数和系数。
* - `x`:`element_t`,多项式求值的点。调用前需要初始化。
*
* 输出参数:
* - `r`:`element_t`,用于存储计算结果的元素。调用前需要初始化。
*
* 返回值:
* - 无。计算结果直接存储在参数 `r` 中。
*
* 注意事项:
* - 在调用此函数之前,参数 `r`、`x` 和多项式 `q` 的系数都必须已正确初始化,且与配对环境匹配。
* - 函数内部使用了临时元素 `sum`、`exp`、`term`,在函数结束前已被正确清理,防止内存泄漏。
* - 调用者需确保多项式 `q` 的度数和系数已正确设置。
* - 该函数假定多项式的系数存储在 `q->coef` 中,索引从 0 到 `q->deg`。
*/
void eval_poly(element_t r, BswabePolynomial* q, element_t x) {
element_t sum, exp, term;
// 初始化临时变量 sum,用于累加多项式求和值,与 r 类型相同
element_init_same_as(sum, r);
// 初始化临时变量 term,用于存储每一项的计算结果,与 r 类型相同
element_init_same_as(term, r);
// 初始化临时变量 exp,用于计算 x 的幂次,与 x 类型相同
element_init_same_as(exp, x);
// sum = 0,初始化求和值为零
element_set0(sum);
// exp = 1,初始化 x 的指数次幂为 1(即 x^0)
element_set1(exp);
// 遍历多项式的所有系数,计算 sum = ∑ (q->coef[i] * x^i)
// 例如:f(x) = ax^2+bx+c,一个三个系数,下面循环执行三次;第一次结束sum = c, exp = x; 第二次结束sum = c + bx, exp = x^2; 第三次结束sum = c + bx + ax^2, exp = x^3;
for (int i = 0; i <= q->deg; ++i) {
// term = q->coef[i] * exp,计算当前项的值
element_mul(term, q->coef[i], exp);
// sum += term,将当前项的值累加到总和中
element_add(sum, sum, term);
// exp *= x,更新 exp 为 x 的下一个幂次(即 x^{i+1})
element_mul(exp, exp, x);
}
// 将计算得到的多项式值 sum 赋值给结果变量 r,返回的结果变量r
element_set(r, sum);
// 清理临时变量,释放内存
element_clear(sum);
element_clear(exp);
element_clear(term);
}
/**
* 填充策略树并计算加密组件函数,主要用在加密过程,对叶子节点(整个树的所有叶子节点)进行加密
*
* 功能描述:该函数用于在加密过程中,递归地填充策略树的每个节点。对于每个节点,生成一个随机多项式 `q`,并根据该多项式计算节点的加密组件。对于叶子节点,计算并存储加密所需的群元素;对于内部节点,递归处理其子节点。
*
* 输入参数:
* - `p`:`BswabePolicy*`,指向策略树节点的指针,需要填充的节点。
* - `pub`:`BswabePub*`,系统公钥结构体,包含公共参数(如 `g`、`h`、`pairing` 等)。
* - `e`:`element_t`,上级节点在当前节点处的多项式值,即 `q(0)` 的值。对于根节点,`e` 通常为随机生成的秘密值 `s`。
*
* 返回值:
* - 无。函数通过修改参数 `p` 来填充策略树节点的内容。
*
* 注意事项:
* - 调用此函数前,策略树应已正确构建(例如通过 `parse_policy_postfix` 函数)。
* - 函数内部使用了递归调用来处理策略树的所有节点。
* - 函数中使用了临时元素,需要确保在函数结束前正确清理,防止内存泄漏。
* - 对于叶子节点,函数会初始化并计算加密所需的元素 `p->c` 和 `p->cp`。
*/
void fill_policy(BswabePolicy* p, BswabePub* pub, element_t e) {
// 生成随机多项式 q,并设置其在 x=0 处的值为 e
p->q = rand_poly(p->k - 1, e); // 多项式的度为 k-1,q(0) = e
if (p->children.empty()) { // 处理叶子节点
// 初始化叶子节点的加密组件 c 和 cp
element_init_G1(p->c, pub->pairing);
element_init_G1(p->cp, pub->pairing);
// 将属性字符串映射为群元素 h_attr = H(attr)
element_t h_attr;
element_init_G1(h_attr, pub->pairing);
element_from_string(h_attr, p->attr);
// 计算加密组件
// c = g^{q(0)}
element_pow_zn(p->c, pub->g, p->q->coef[0]);
// cp = H(attr)^{q(0)}
element_pow_zn(p->cp, h_attr, p->q->coef[0]);
// 清理临时元素 h_attr
element_clear(h_attr);
} else { // 处理阈值节点
// 初始化临时元素 index,用于表示子节点的序号
element_t index;
element_init_Zr(index, pub->pairing);
// 遍历子节点
for (size_t i = 0; i < p->children.size(); ++i) {
// 设置 index = i + 1,因为子节点序号从 1 开始
element_set_si(index, i + 1);
// 初始化临时元素 q_y0,用于存储多项式在子节点序号处的值 q(i+1)
element_t q_y0;
element_init_Zr(q_y0, pub->pairing);
// 计算多项式 q 在 index 处的值,即 f(x) = q_y0(父节点的f(x)值为子节点的秘密值) = q(index)
eval_poly(q_y0, p->q, index);
// 递归调用 fill_policy,填充子节点,传递 q_y0 作为新的 e 值
fill_policy(p->children[i], pub, q_y0);
// 清理临时元素 q_y0
element_clear(q_y0);
}
// 清理临时元素 index
element_clear(index);
}
}
/**
* 3.Encrypt:加密函数
*
* 功能描述:根据给定的访问策略字符串,对明文元素进行加密。函数生成一个新的密文结构体 `BswabeCph`,其中包含加密的明文和访问策略树。加密过程中使用随机生成的秘密值 `s`,并将其与访问策略树结合,生成策略相关的加密组件。
*
* 输入参数:
* - `pub`:`BswabePub*`,系统公钥结构体,包含公共参数(如 `g`、`h`、`e_gg_alpha` 等)。
* - `policy_str`:`const std::string&`,访问策略字符串,采用后缀表达式形式,例如 `"attr1 attr2 1of2"`。
* - `m`:`element_t`,要加密的明文元素,属于 `GT` 群。
*
* 返回值:
* - `BswabeCph*`,生成的密文结构体,包含加密的明文和策略树。
*
* 注意事项:
* - 在调用此函数之前,公钥 `pub` 必须已正确初始化。
* - 返回的密文结构体需要在使用完毕后手动释放,防止内存泄漏。
* - 函数内部生成的随机秘密值 `s` 是加密过程中的关键参数,确保其随机性和安全性。
* - 函数中使用的临时元素在函数结束前已被正确清理。
*/
BswabeCph* bswabe_enc(BswabePub* pub, const std::string& policy_str, element_t m) {
BswabeCph* cph = new BswabeCph(); // 创建新的密文结构体
element_t s; // 随机秘密值 s,用于加密过程
element_init_Zr(s, pub->pairing); // 初始化元素 s,属于 Z_p 群
element_random(s); // 生成随机秘密值 s ∈ Z_p
// 初始化密文结构体中的元素
element_init_GT(cph->cs, pub->pairing); // 初始化加密后的明文元素 cs,属于 GT 群
element_init_G1(cph->c, pub->pairing); // 初始化加密组件 c,属于 G1 群
/***********************************CT第一部分:C_y,C_y'************************************************/
// 解析访问策略字符串,生成策略树
cph->p = parse_policy_postfix(policy_str); // 解析访问策略,生成策略树 cph->p
// 填充策略树,生成策略相关的加密组件
fill_policy(cph->p, pub, s); // 填充策略树,使用秘密值 s
/***********************************CT第二部分:C=h^s, C_head=Me(g,g)^{α*s}************************************************/
// 计算加密的明文部分
element_t egg_alpha_s; // 临时元素,用于存储 e(g, g)^{αs}
element_init_GT(egg_alpha_s, pub->pairing);
// 计算 egg_alpha_s = e(g, g)^{αs}
element_pow_zn(egg_alpha_s, pub->e_gg_alpha, s); // egg_alpha_s = (e(g, g)^α)^s = e(g, g)^{αs}
// 计算密文中的加密明文部分 cph->cs = m * e(g, g)^{αs}
element_mul(cph->cs, m, egg_alpha_s); // C_head = cs = m * e(g, g)^{αs}
// 计算密文中的加密组件 c = h^s
element_pow_zn(cph->c, pub->h, s); // c = h^s
// 清理临时元素
element_clear(s); // 清理秘密值 s
element_clear(egg_alpha_s); // 清理临时元素 egg_alpha_s
return cph; // 返回生成的密文结构体
}
/**
* 检查策略是否满足并标记满足条件的节点函数
*
* 功能描述:此函数递归地检查策略树中的每个节点,确定用户的属性集合是否满足策略要求。
* 对于叶子节点,函数检查其属性是否在用户的属性集合中。
* 对于中间节点,函数统计满足条件的子节点数量,并根据阈值 `k` 决定当前节点是否满足。
* 函数会设置节点的 `satisfiable` 标志,并在中间节点的 `satl` 列表中记录满足条件的子节点索引。
* 该函数在解密过程中用于确定用户是否有足够的属性来满足加密策略,从而能够成功解密密文。
*
* 输入参数:
* - `p`:`BswabePolicy*`,指向策略树节点的指针,需要检查的节点。
* - `attrs`:`const std::vector<std::string>&`,用户持有的属性集合。
*
* 返回值:
* - `bool`,表示当前节点是否满足策略要求。`true` 表示满足,`false` 表示不满足。
*
* 注意事项:
* - 函数通过递归调用,遍历整个策略树。
* - 对于叶子节点,函数会检查其属性是否在用户属性集合中,并设置 `satisfiable` 标志。
* - 对于中间节点,函数会递归检查其子节点,统计满足条件的子节点数量,并根据阈值 `k` 决定当前节点是否满足。
* - 函数会修改策略树节点的状态,包括 `satisfiable` 标志和 `satl` 列表。
* - 在解密过程中,需要在调用此函数后,才能正确地进行后续的拉格朗日插值等操作。
*/
bool check_sat(BswabePolicy* p, const std::vector<std::string>& attrs) {
if (p->children.empty()) { // 叶子节点处理
// 检查叶子节点的属性是否在用户的属性集合中
// 如果存在,则设置 satisfiable 为 true;否则为 false
p->satisfiable = std::find(attrs.begin(), attrs.end(), p->attr) != attrs.end();
} else { // 阈值节点处理
int satisfied = 0; // 统计满足条件的子节点数量
p->satl.clear(); // 清空满足条件的子节点索引列表
// 遍历所有子节点,递归检查每个子节点是否满足条件
for (size_t i = 0; i < p->children.size(); ++i) {
if (check_sat(p->children[i], attrs)) {
satisfied++; // 如果子节点满足条件,增加计数
p->satl.push_back(i + 1); // 记录满足条件的子节点索引,索引从 1 开始计数
}
}
// 根据阈值 k,判断当前节点是否满足条件
// 如果满足条件的子节点数量不少于阈值 k,则当前节点满足
p->satisfiable = (satisfied >= p->k);
}
// 返回当前节点的 satisfiable 状态
return p->satisfiable;
}
/**
* 拉格朗日插值系数计算函数
*
* 功能描述:该函数用于计算在拉格朗日插值中用于秘密共享重构的拉格朗日系数 λ_i。
* 给定满足条件的节点索引列表 `satl`,以及特定的节点索引 `i`,
* 计算拉格朗日系数 λ_i,其公式为:
*
* λ_i = ∏_{j ∈ satl, j ≠ i} (-j) / (i - j)
*
* 该系数用于在秘密共享重构中,根据满足条件的子节点计算父节点的值。
*
* 输入参数:
* - `satl`:`const std::vector<int>&`,满足条件的子节点索引列表。
* - `i`:`int`,当前计算的节点索引。
* - `pub`:`BswabePub*`,系统公钥结构体,包含配对参数。
*
* 输出参数:
* - `coef`:`element_t`,用于存储计算结果的元素。调用前需要初始化。
*
* 返回值:
* - 无。计算结果直接存储在参数 `coef` 中。
*
* 注意事项:
* - 参数 `coef` 在调用前必须已使用 `element_init_Zr` 初始化。
* - 函数内部使用了临时元素 `num`、`denom` 和 `tmp`,在函数结束前已被正确清理。
* - 索引 `i` 和 `j` 应该从 1 开始,与策略树中子节点的编号一致。
* - 该函数假定 `satl` 中的索引都是正整数,且包含 `i`。
*/
void lagrange_coefficient(element_t coef, const std::vector<int>& satl, int i, BswabePub* pub) {
element_t num, denom; // 分别用于存储分子和分母的中间计算结果
element_init_Zr(num, pub->pairing); // 初始化分子 num 为整数域元素
element_init_Zr(denom, pub->pairing); // 初始化分母 denom 为整数域元素
element_set1(num); // num = 1,初始化分子为 1
element_set1(denom); // denom = 1,初始化分母为 1
// 计算拉格朗日系数 λ_i = ∏_{j ≠ i} (-j) / (i - j)
for (int j : satl) {
if (j == i) {
continue; // 跳过当前节点索引 i
}
element_t tmp; // 临时变量用于存储中间结果
element_init_Zr(tmp, pub->pairing);
// 计算分子部分 num *= -j
element_set_si(tmp, -j); // tmp = -j
element_mul(num, num, tmp); // num = num * tmp
// 计算分母部分 denom *= (i - j)
element_set_si(tmp, i - j); // tmp = i - j
element_mul(denom, denom, tmp); // denom = denom * tmp
element_clear(tmp); // 清理临时变量 tmp
}
// 计算 denom 的逆元
element_invert(denom, denom); // denom = 1 / denom
// 计算拉格朗日系数 coef = num * denom
element_mul(coef, num, denom); // coef = num * denom
// 清理分子和分母的中间结果
element_clear(num);
element_clear(denom);
}
/**
* 使用拉格朗日插值法解密节点函数
*
* 功能描述:此函数递归地解密策略树中的每个节点,利用拉格朗日插值法在满足策略的情况下重构秘密。
* 对于叶子节点,函数计算解密元素;对于内部节点,函数递归地处理满足条件的子节点,计算拉格朗日系数,重构父节点的值。
* 函数最终在根节点重构出加密过程中使用的秘密值,用于解密密文。
*
* 输入参数:
* - `p`:`BswabePolicy*`,指向策略树节点的指针,需要解密的节点。
* - `prv`:`BswabePrv*`,用户的私钥结构体,包含用户的属性和对应的私钥组件。
* - `cph`:`BswabeCph*`,密文结构体,包含加密的明文和策略树。
* - `pub`:`BswabePub*`,系统公钥结构体,包含公共参数和配对。
*
* 输出参数:
* - `r`:`element_t`,用于存储计算结果的元素。调用前需要初始化。
*
* 返回值:
* - 无。计算结果直接存储在参数 `r` 中。
*
* 注意事项:
* - 在调用此函数之前,需要确保策略树已使用 `check_sat` 函数标记了满足条件的节点。
* - 函数通过递归调用,遍历满足条件的策略树节点,计算用于解密的元素。
* - 函数内部使用了临时元素,需要在函数结束前正确清理,防止内存泄漏。
* - 索引 `i` 在 `p->satl` 中是从 1 开始的,因此在访问子节点时需要减 1。
*/
void decrypt_node_with_lagrange(element_t r, BswabePolicy* p, BswabePrv* prv, BswabeCph* cph, BswabePub* pub) {
if (p->children.empty()) { // 叶子节点的解密操作
// 在用户私钥中查找与当前叶子节点属性匹配的私钥组件
for (auto& comp : prv->comps) {
if (comp->attr == p->attr) { // 找到用户私钥对应的属性密文
// 初始化临时元素 e1 和 e2,用于存储配对运算结果
element_t e1, e2;
element_init_GT(e1, pub->pairing);
element_init_GT(e2, pub->pairing);
// 计算 e1 = e(C_y, D_j)
element_pairing(e1, p->c, comp->d); // e(C_y, D_j)
// 计算 e2 = e(C_y', D_j')
element_pairing(e2, p->cp, comp->dp); // e(C_y', D_j')
// 计算 e2 的逆元 e2^{-1}
element_invert(e2, e2); // e2^{-1}
// 计算 r = e1 * e2 = e(C_y, D_j) / e(C_y', D_j')
element_mul(r, e1, e2); // r = e1 * e2
// 清理临时元素 e1 和 e2
element_clear(e1);
element_clear(e2);
break; // 找到匹配的属性,退出循环(一次仅处理一个叶子节点)
}
}
} else { // 处理非叶子节点,使用拉格朗日插值还原秘密 s
element_t Fx, t;
// 初始化 Fx,用于累积子节点的解密结果
element_init_GT(Fx, pub->pairing);
element_set1(Fx); // 初始化 Fx 为 1
// 初始化 t,用于存储拉格朗日系数
element_init_Zr(t, pub->pairing);
// 遍历满足条件的子节点索引列表 p->satl
for (int i : p->satl) {
element_t share;
// 初始化 share,用于存储子节点的解密结果
element_init_GT(share, pub->pairing);
// 递归解密子节点,注意子节点数组从 0 开始,而 satl 索引从 1 开始
decrypt_node_with_lagrange(share, p->children[i - 1], prv, cph, pub);
// 计算拉格朗日系数 t = λ_i
lagrange_coefficient(t, p->satl, i, pub);
// 计算 share = share^{λ_i}
element_pow_zn(share, share, t);
// 累积计算 Fx = Fx * share
element_mul(Fx, Fx, share);
// 清理临时元素 share
element_clear(share);
}
// 将累积结果 Fx 赋值给输出参数 r
element_set(r, Fx);
// 清理临时元素 Fx 和 t
element_clear(Fx);
element_clear(t);
}
}
/**
* 4.Decrypt:解密函数
*
* 功能描述:该函数用于对属性基加密的密文进行解密。它首先检查用户的属性集合是否满足密文的访问策略,
* 如果满足,则通过递归方式计算必要的中间值,最终恢复出原始的明文元素。
*
* 输入参数:
* - `pub`:`BswabePub*`,系统公钥结构体,包含公共参数和配对环境。
* - `prv`:`BswabePrv*`,用户的私钥结构体,包含用户的属性和对应的私钥组件。
* - `cph`:`BswabeCph*`,密文结构体,包含加密的明文和访问策略树。
*
* 返回值:
* - `BswabeElementBoolean*`,包含解密得到的明文元素和解密成功与否的标志。
* - `result->e`:`element_t`,解密得到的明文元素,属于目标群 `GT`。
* - `result->b`:`bool`,表示解密是否成功。`true` 表示成功,`false` 表示失败。
*
* 注意事项:
* - 在调用此函数之前,需要确保公钥、私钥和密文都已正确初始化。
* - 函数内部会检查用户的属性集合是否满足密文的访问策略,如果不满足,将返回解密失败的结果。
* - 函数内部使用了临时元素,需要在函数结束前正确清理,防止内存泄漏。
* - 返回的结果结构体需要在使用完毕后手动释放,并清理内部的元素。
*/
BswabeElementBoolean* bswabe_dec(BswabePub* pub, BswabePrv* prv, BswabeCph* cph) {
BswabeElementBoolean* result = new BswabeElementBoolean();
// 初始化结果元素 result->e,属于目标群 GT
element_init_GT(result->e, pub->pairing);
// 提取用户私钥中的属性列表,存入 attrs
std::vector<std::string> attrs; // 从私钥中提取用户属性集
for (size_t i = 0; i < prv->comps.size(); ++i) {
attrs.push_back(prv->comps[i]->attr); // 提取每个组件的属性
}
// 检查用户的属性是否满足密文的访问策略
if (!check_sat(cph->p, attrs)) {
std::cout << "用户属性不满足策略要求" << std::endl;
result->b = false; // 解密失败
return result;
}
// 执行解密过程,首先通过递归解密得到 e(g, g)^{rs}
element_t e_gg_rs;
element_init_GT(e_gg_rs, pub->pairing);
element_set1(e_gg_rs); // 初始化 e_gg_rs 为 1
// 递归解密策略树,计算 e_gg_rs = e(g, g)^{rs}
decrypt_node_with_lagrange(e_gg_rs, cph->p, prv, cph, pub);
// 计算 e(C, D)
element_t e_CD;
element_init_GT(e_CD, pub->pairing);
element_pairing(e_CD, cph->c, prv->d); // e_CD = e(C, D)
// 计算 e(C, D) / e(g, g)^{rs}
element_div(e_CD, e_CD, e_gg_rs); // e_CD = e(C, D) / e(g, g)^{rs}
// 计算最终的明文 M = \tilde{C} / (e(C, D) / e(g, g)^{rs})
element_div(result->e, cph->cs, e_CD); // result->e = \tilde{C} / e_CD
// 清理临时变量
element_clear(e_gg_rs);
element_clear(e_CD);
result->b = true; // 解密成功
return result;
}
// 释放策略树资源
void free_bswabe_policy(BswabePolicy* p) {
if (!p) return;
for (auto& child : p->children) {
free_bswabe_policy(child);
}
delete p;
}
// 释放用户私钥资源
void free_bswabe_prv(BswabePrv* prv) {
if (!prv) return;
for (auto& comp : prv->comps) {
element_clear(comp->d);
element_clear(comp->dp);
delete comp;
}
delete prv;
}
// 释放密文资源
void free_bswabe_cph(BswabeCph* cph) {
if (!cph) return;
free_bswabe_policy(cph->p);
element_clear(cph->cs);
element_clear(cph->c);
delete cph;
}
3.3 main.cpp
#include "bswabe.h"
#include <pbc/pbc.h>
#include <iostream>
#include <vector>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;
const int ELEMENT_SIZE = 2048; // 假设曲线元素的字节大小
// 将字符串转换为element
void string_to_element(element_t& element, const string& str) {
int len = str.size();
// 确保缓冲区包含固定大小并为字符串文字和填充可能更长的元素而设计
vector<unsigned char> buffer(ELEMENT_SIZE, 0);
memcpy(buffer.data(), str.c_str(), len);
element_from_bytes(element, buffer.data());
}
// 将element转换为字符串
void element_to_string(element_t& element, string& str) {
int length = element_length_in_bytes(element);
vector<unsigned char> buffer(length);
element_to_bytes(buffer.data(), element);
// 从缓冲区中提取原始字符串内容
auto pos = std::find(buffer.begin(), buffer.end(), 0);
str.assign(reinterpret_cast<char*>(buffer.data()), pos != buffer.end() ? pos - buffer.begin() : length);
}
int main() {
cout<<endl<<"********************************************step1:系统初始化阶段***************************************************"<<endl;
BswabePub pub; // 定义系统公钥
BswabeMsk msk; // 定义系统主私钥
bswabe_setup(&pub, &msk); // 初始化系统,生成公钥和主私钥
cout<<endl<<"********************************************step2:密钥生成阶段***************************************************"<<endl;
std::vector<std::string> user_attrs = {"attr25", "attr6", "attr7s","attr1","attr7","attr8" }; // 定义用户属性集
BswabePrv* prv = bswabe_keygen(&pub, &msk, user_attrs); // 生成用户私钥
cout<<endl<<"********************************************step3:加密阶段***************************************************"<<endl;
std::string policy = "attr1 attr2 attr3 attr4 1of2 2of2 1of2 attr5 attr6 attr7 attr8 1of2 2of2 1of2 2of2"; // 定义策略树
// 当前曲线最大加密长度为128 bytes,现在输入是140 bytes,但是后面解密只能恢复前面128 bytes
string M_string = "cc11111111112222222222333333333344444444445555555555666666666677777777778aabbbbbbbbbbccccccccccddddddddddeeeeeeeeee33";
element_t M; // element_t明文
element_init_GT(M, pub.pairing); // 初始化M
string_to_element(M, M_string); // 将string转成element
std::cout << "Plaintext M_string: " << M_string << ", size = "<<M_string.size()<<std::endl;
element_printf("Plaintext M: %B\n", M);
BswabeCph* cph = bswabe_enc(&pub, policy, M); // 使用policy对M进行加密
if (cph == nullptr) {
std::cerr << "Encrypt failed!" << std::endl;
return 1;
}
element_printf("cs (Encrypted message): %B\n", cph->cs); // 打印加密后的明文 \tilde{C}
std::cout << "Access policy tree:" << std::endl;
print_policy_tree(cph->p, 0); // 打印访问策略树的内容
cout<<endl<<"********************************************step4:解密阶段***************************************************"<<endl;
BswabeElementBoolean* result = bswabe_dec(&pub, prv, cph); // 使用用户私钥prv对密文cph进行解密
if (result->b) {
string output;
element_to_string(result->e, output); // 将element转成string
cout << "Decrypt success! Decrypted plaintext is: " << output <<",size= "<<output.size()<<endl; // 解密成功
} else {
std::cout << "Decrypt failed!" << std::endl;
}
// 释放资源
free_bswabe_prv(prv);
free_bswabe_cph(cph);
element_clear(result->e);
delete result;
element_clear(msk.beta);
element_clear(msk.g_alpha);
element_clear(pub.g);
element_clear(pub.h);
element_clear(pub.f);
element_clear(pub.e_gg_alpha);
pairing_clear(pub.pairing);
return 0;
}
3.4 编译 & 运行
编译:g++ main.cpp bswabe.cpp -o test -L/usr/local/lib/pbc -lpbc -L/usr/lib -lgmp -lcrypto
运行:./test
注意:根据上面编译命令可以看出,程序依赖三个库pbc、gmp、crypto,也就是说需要安装openssl做密码学支持。
3.5 执行结果
********************************************step1:系统初始化阶段***************************************************
********************************************step2:密钥生成阶段***************************************************
********************************************step3:加密阶段***************************************************
Plaintext M_string: cc11111111112222222222333333333344444444445555555555666666666677777777778aabbbbbbbbbbccccccccccddddddddddeeeeeeeeee33, size = 117
Plaintext M: [5205344140073205636919547778842138865494575854406214706964728954569561463991554903588844784666170336947548779351242192082515106137749581432358453376333623, 2891880141752324864617312240915208141818104769656821415680416289610269083212830577235247828579371943992052019821671556566345604006848332756293850792722432]
cs (Encrypted message): [5916257443095765904788063490198978673229284956809812978643773997849045665217377660331616477039520327887401648265640222836172921996693726580892896772089104, 6580301541418529665227073754685731831520714940276594480419975985410534354665198204672431446692765829662886361273113823877735711245227723200574325896138232]
Access policy tree:
Node: k = 2, attr =
Node: k = 1, attr =
Node: k = 1, attr = attr1
Node: k = 2, attr =
Node: k = 1, attr = attr2
Node: k = 1, attr =
Node: k = 1, attr = attr3
Node: k = 1, attr = attr4
Node: k = 1, attr =
Node: k = 1, attr = attr5
Node: k = 2, attr =
Node: k = 1, attr = attr6
Node: k = 1, attr =
Node: k = 1, attr = attr7
Node: k = 1, attr = attr8
********************************************step4:解密阶段***************************************************
Decrypt success! Decrypted plaintext is: cc11111111112222222222333333333344444444445555555555666666666677777777778aabbbbbbbbbbccccccccccddddddddddeeeeeeeeee33,size= 117
可见,上述的用户属性集满足策略要求,也即解密成功!这里用的策略polices结构如下,更方便查看:
AND (2of2)
/ \
OR (1of2) OR (1of2)
/ \ / \
attr1 AND (2of2) attr5 AND (2of2)
/ \ / \
attr2 OR (1of2) attr6 OR (1of2)
/ \ / \
attr3 attr4 attr7 attr8
3.6 github源码链接
为了方便源码的直接使用,这里给出github源码地址,可直接下载使用:
https://github.com/hututu578/bswabe
四. 参考文献
- Bethencourt, John, et al. “Ciphertext-Policy Attribute-Based Encryption.” 2007 IEEE Symposium on Security and Privacy (SP ’07), 2007, https://doi.org/10.1109/sp.2007.11.(原版论文)
- 属性加密访问结构(一)—访问控制树(超详细)
- 什么是选择明文攻击安全、CPA secure?
- Java版本源码实现
- github源码地址
五. 感谢支持
完结撒花!密码学实属复杂深奥的问题,不仅在原理上难以理解,在代码实现上更是难的一批,写这篇文章花了我很大精力,遇到了无数问题,个个都让人头大。也感谢开源小伙伴提供的代码,分析和思考,给了我很大帮助。希望看到这里的小伙伴能点个关注,我后续会持续更新更多关于密码学原理分析和实现,也欢迎大家广泛交流。
码字实属不易,如果本文对你有10分帮助,就赏个10分把,感谢各位大佬支持!