[OU98] Okamoto T, Uchiyama S. A new public-key cryptosystem as secure as factoring[C]//Advances in Cryptology—EUROCRYPT’98: International Conference on the Theory and Application of Cryptographic Techniques Espoo, Finland, May 31–June 4, 1998 Proceedings 17. Springer Berlin Heidelberg, 1998: 308-318.
之所以选择写这个加密方案,主要原因是它有一种很好用的同态性质,即摘要的第6点,次要原因是它用了一些挺“漂亮”的构造,次次要原因是我自己有可能会用到,所以做个笔记。
设E ( m , r ) \rm{E}(m, r) E ( m , r ) 是在该方案中使用随机数r r r 对明文m m m 进行加密,密文落在Z n ∗ \mathbb{Z}_n^* Z n ∗ 中,如果有两个明文m 0 m_0 m 0 和m 1 m_1 m 1 达到条件m 0 + m 1 < p m_0 + m_1 < p m 0 + m 1 < p (p p p 是一个模数,可以参考下面的理论部分),上图摘要第6点的同态性质说的是(虽然论文里没说,但其实随机数部分也有同态):
E ( m 0 , r 0 ) E ( m 1 , r 1 ) ≡ E ( m 0 + m 1 , r 0 + r 1 ) ( m o d n ) \rm{E}(m_0, r_0) \rm{E}(m_1, r_1) \equiv \rm{E}(m_0 + m_1, r_0 + r_1) \pmod n
E ( m 0 , r 0 ) E ( m 1 , r 1 ) ≡ E ( m 0 + m 1 , r 0 + r 1 ) ( mod n )
从这个关系可以轻松推出摘要第7点的密文转换:
E ( m , r ) E ( 0 , r ′ ) ≡ E ( m + 0 , r + r ′ ) ≡ E ( m , r ′ ′ ) ( m o d n ) \rm{E}(m, r) \rm{E}(0, r') \equiv \rm{E}(m + 0, r + r') \equiv \rm{E}(m, r'') \pmod n
E ( m , r ) E ( 0 , r ′ ) ≡ E ( m + 0 , r + r ′ ) ≡ E ( m , r ′′ ) ( mod n )
另外文章没说的是,这个方案还有一个“乘法同态”的属性(可以姑且称上面的同态是“加法同态”),设存在一个x ∈ Z p x \in \mathbb{Z}_p x ∈ Z p 使得x m < p xm < p x m < p (如果不用正确解密甚至不需要这个条件),则:
E ( m , r ) x ≡ E ( x m , x r ) ( m o d n ) \rm{E}(m, r)^x \equiv \rm{E}(xm, xr) \pmod n
E ( m , r ) x ≡ E ( xm , xr ) ( mod n )
知识清单·
如果看到这里还没把你劝退的话,接下来的阅读可能需要:
群论知识,最好也知道环
看过RSA/ElGamal等加密方案,虽然只是方便作类比而已
英语论文阅读能力
Sagemath/Python代码阅读、编写能力
理论部分·
前置知识·
这里需要先说明方案加解密时用到一些东西。首先是解密需要用到的一个p-西罗子群 ([OU98]定义1),令p p p 是奇素数,下面的Γ \Gamma Γ 是Z p 2 ∗ \mathbb{Z}_{p^2}^* Z p 2 ∗ 的p-西罗子群:
Γ = { x ∈ Z p 2 ∗ ∣ x ≡ 1 ( m o d p ) } \Gamma = \{x \in \mathbb{Z}_{p^2}^* \ |\ x \equiv 1 \pmod p \}
Γ = { x ∈ Z p 2 ∗ ∣ x ≡ 1 ( mod p )}
实际上也可以看成下面的写法(虽然我没严格证明过但两个东西应该等同,而且文章中实际计算时也会用下面比较具象的表述):
Γ = { x p − 1 ( m o d p 2 ) ∣ x ∈ Z p 2 ∗ } \Gamma = \{x^{p-1} \pmod {p^2} \ |\ x \in \mathbb{Z}_{p^2}^* \}
Γ = { x p − 1 ( mod p 2 ) ∣ x ∈ Z p 2 ∗ }
文章中直接由Z p 2 ∗ \mathbb{Z}_{p^2}^* Z p 2 ∗ 的阶是p ( p − 1 ) p(p-1) p ( p − 1 ) (和x ≡ 1 ( m o d p ) x \equiv 1 \pmod p x ≡ 1 ( mod p ) ,根据拉格朗日定理 ?)就推出Γ \Gamma Γ 的阶是p p p 。
用上面的“具象表述”也可以给出比较严格的推理:设g g g 是Z p 2 ∗ \mathbb{Z}_{p^2}^* Z p 2 ∗ 的一个生成元(根据广义原根定理,Z p 2 ∗ \mathbb{Z}_{p^2}^* Z p 2 ∗ 是循环群,所以一定有生成元)
Γ \Gamma Γ 表述中的x x x 表示的其实是Z p 2 ∗ \mathbb{Z}_{p^2}^* Z p 2 ∗ 中的所有元素,那么借助生成元使用g i g^i g i 也可以达到同样的效果,所以Γ \Gamma Γ 可以被写作(其中φ ( ⋅ ) \varphi(·) φ ( ⋅ ) 是欧拉函数 ,φ ( p 2 ) = p ( p − 1 ) \varphi(p^2) = p(p-1) φ ( p 2 ) = p ( p − 1 ) ):
Γ = { ( g i ) p − 1 ( m o d p 2 ) ∣ i ∈ Z φ ( p 2 ) } \Gamma = \{(g^i)^{p-1} \pmod {p^2} \ |\ i \in \mathbb{Z}_{\varphi(p^2)} \}
Γ = {( g i ) p − 1 ( mod p 2 ) ∣ i ∈ Z φ ( p 2 ) }
根据欧拉定理 ,有
g ( i + p ) ( p − 1 ) ≡ g i ( p − 1 ) + p ( p − 1 ) ≡ g i ( p − 1 ) g φ ( p 2 ) ≡ g i ( p − 1 ) ( m o d p 2 ) g^{(i + p) (p-1)} \equiv g^{i(p-1) + p(p-1)} \equiv g^{i(p-1)} g^{\varphi(p^2)} \equiv g^{i(p-1)} \pmod {p^2}
g ( i + p ) ( p − 1 ) ≡ g i ( p − 1 ) + p ( p − 1 ) ≡ g i ( p − 1 ) g φ ( p 2 ) ≡ g i ( p − 1 ) ( mod p 2 )
说明i i i 和i + p i+p i + p 生成的值是一样的,即只要i ≥ p i \ge p i ≥ p ,生成的值就是多余的,只有落在Z p \mathbb{Z}_p Z p 中的i i i 有用,所以Γ \Gamma Γ 可以被写作:
Γ = { ( g i ) p − 1 ( m o d p 2 ) ∣ i ∈ Z p } \Gamma = \{(g^i)^{p-1} \pmod {p^2} \ |\ i \in \mathbb{Z}_{p} \}
Γ = {( g i ) p − 1 ( mod p 2 ) ∣ i ∈ Z p }
所以∣ Γ ∣ = ∣ Z p ∣ = p |\Gamma| = |\mathbb{Z}_p| = p ∣Γ∣ = ∣ Z p ∣ = p ,即Γ \Gamma Γ 阶为p p p 。
观察Γ \Gamma Γ 的结构,只要x ∈ Γ x \in \Gamma x ∈ Γ ,就有x − 1 ≡ 0 ( m o d p ) x - 1 \equiv 0 \pmod p x − 1 ≡ 0 ( mod p ) ,即x − 1 = k p x-1 = kp x − 1 = k p (k ∈ Z p − 1 k \in \mathbb{Z}_{p-1} k ∈ Z p − 1 ),也就是x − 1 x-1 x − 1 可以被p p p 整除,于是就可以定义一个文章中称作F p -valued \mathbb{F}_p \text{-valued} F p -valued 的函数L L L :
L ( x ) : = x − 1 p L(x) := \frac{x-1}{p}
L ( x ) := p x − 1
之所以定义这个L L L 是因为解密需要用到其同态属性 (其中L ( ⋅ ) L(·) L ( ⋅ ) 括号里是Z p 2 \mathbb{Z}_{p^2} Z p 2 上的运算,括号外是Z p \mathbb{Z}_p Z p 上的运算):
L ( a b ) ≡ L ( a ) + L ( b ) ( m o d p ) L(ab) \equiv L(a) + L(b) \pmod p
L ( ab ) ≡ L ( a ) + L ( b ) ( mod p )
简要证明:首先
L ( a b ) = a b − 1 p = ( a − 1 ) ( b − 1 ) + ( a − 1 ) + ( b − 1 ) p = a − 1 p ( b − 1 ) + a − 1 p + b − 1 p = L ( a ) ( b − 1 ) + L ( a ) + L ( b ) \begin{aligned}
L(ab) = &\frac{ab-1}{p} \\
= &\frac{(a-1)(b-1)+(a-1)+(b-1)}{p} \\
= &\frac{a-1}{p}(b-1) + \frac{a-1}{p} + \frac{b-1}{p} \\
= &L(a)(b-1) + L(a) + L(b)
\end{aligned}
L ( ab ) = = = = p ab − 1 p ( a − 1 ) ( b − 1 ) + ( a − 1 ) + ( b − 1 ) p a − 1 ( b − 1 ) + p a − 1 + p b − 1 L ( a ) ( b − 1 ) + L ( a ) + L ( b )
最后,因为b ∈ Γ b \in \Gamma b ∈ Γ ,所以b − 1 ≡ 0 ( m o d p ) b-1 \equiv 0 \pmod p b − 1 ≡ 0 ( mod p ) ,故
L ( a b ) ≡ L ( a ) ( b − 1 ) + L ( a ) + L ( b ) ≡ L ( a ) + L ( b ) ( m o d p ) \begin{aligned}
L(ab) \equiv & L(a)(b-1) + L(a) + L(b) \\
\equiv & L(a) + L(b) \pmod p
\end{aligned}
L ( ab ) ≡ ≡ L ( a ) ( b − 1 ) + L ( a ) + L ( b ) L ( a ) + L ( b ) ( mod p )
得证。
最后说明一下怎么用L L L 的同态属性解密:设x ∈ Γ x \in \Gamma x ∈ Γ ,L ( x ) ≢ 0 ( m o d p ) L(x) \not\equiv 0 \pmod p L ( x ) ≡ 0 ( mod p ) ,设y ≡ x m ( m o d p 2 ) y \equiv x^m \pmod {p^2} y ≡ x m ( mod p 2 ) 模拟一个密文,m ∈ Z p m \in \mathbb{Z}_p m ∈ Z p ,那么可以通过计算
m ≡ ∑ i = 1 m L ( x ) L ( x ) ≡ L ( y ) L ( x ) ≡ y − 1 x − 1 ( m o d p ) m \equiv \frac{\sum_{i=1}^{m}L(x)}{L(x)} \equiv \frac{L(y)}{L(x)} \equiv \frac{y-1}{x-1} \pmod p
m ≡ L ( x ) ∑ i = 1 m L ( x ) ≡ L ( x ) L ( y ) ≡ x − 1 y − 1 ( mod p )
实现解密。
另外,文章中介绍了取样“x ∈ Γ x \in \Gamma x ∈ Γ ”的方法 ,只要g g g 是Z p 2 ∗ \mathbb{Z}_{p^2}^* Z p 2 ∗ 的一个生成元(或者原文叫原根),就会有g p − 1 ∈ Γ g^{p-1} \in \Gamma g p − 1 ∈ Γ 。
讲证明前先讲一个换模公式:若y ≡ x ( m o d a b ) y \equiv x \pmod {ab} y ≡ x ( mod ab ) ,则y ≡ x ( m o d a ) y \equiv x \pmod a y ≡ x ( mod a ) 且y ≡ x ( m o d b ) y \equiv x \pmod b y ≡ x ( mod b ) 。
简要证明:由y ≡ x ( m o d a b ) y \equiv x \pmod {ab} y ≡ x ( mod ab ) 可得,存在k ∈ Z k \in \mathbb{Z} k ∈ Z 使得y = x + k a b y = x + kab y = x + kab ,所以
y ≡ x + k a b ≡ x ( m o d a ) y \equiv x + kab \equiv x \pmod a
y ≡ x + kab ≡ x ( mod a )
同理y ≡ x + k a b ≡ x ( m o d b ) y \equiv x + kab \equiv x \pmod b y ≡ x + kab ≡ x ( mod b ) 。
而在上述取样中,由于g p − 1 ∈ Z p 2 g^{p-1} \in \mathbb{Z}_{p^2} g p − 1 ∈ Z p 2 (注意因为Γ \Gamma Γ 是Z p 2 \mathbb{Z}_{p^2} Z p 2 的子群),所以需要证的是
( g p − 1 ( m o d p 2 ) ) ≡ 1 ( m o d p ) (g^{p-1} \pmod {p^2}) \equiv 1 \pmod p
( g p − 1 ( mod p 2 )) ≡ 1 ( mod p )
使用上面的换模公式后,即证
g p − 1 ≡ 1 ( m o d p ) g^{p-1} \equiv 1 \pmod p
g p − 1 ≡ 1 ( mod p )
根据欧拉定理,显然成立。
加密方案·
密钥生成 :
取俩大素数p p p 和q q q ,其中p p p 和q q q 都是κ \kappa κ 位的素数,即∣ p ∣ = ∣ q ∣ = κ |p| = |q| = \kappa ∣ p ∣ = ∣ q ∣ = κ ,另外还需要满足g c d ( p , q − 1 ) = g c d ( q , p − 1 ) = 1 \rm{gcd}(p, q-1) = \rm{gcd}(q, p-1) = 1 gcd ( p , q − 1 ) = gcd ( q , p − 1 ) = 1 ;
设n = p 2 q n = p^2q n = p 2 q ;
采样Z p 2 ∗ \mathbb{Z}_{p^2}^* Z p 2 ∗ 的一个生成元g g g ,文章的做法是,先任意取g ∈ Z n ∗ g \in \mathbb{Z}_n^* g ∈ Z n ∗ ,若g p ≡ g p − 1 ( m o d p 2 ) g_p \equiv g^{p-1} \pmod {p^2} g p ≡ g p − 1 ( mod p 2 ) 的阶为p p p 则保留,否则重选g g g (大概意思是让g g g 的阶是φ ( p 2 ) \varphi(p^2) φ ( p 2 ) );
设h ≡ g n ( m o d n ) h \equiv g^n \pmod n h ≡ g n ( mod n ) ;
公钥设置为:( n , g , h , κ ) (n, g, h, \kappa) ( n , g , h , κ ) ,其实h h h 不公开也行,文章说公开是为了效率;
私钥设置为: ( p , q ) (p, q) ( p , q ) 。
加密 :
明文空间为0 < m < 2 κ − 1 0 < m < 2^{\kappa - 1} 0 < m < 2 κ − 1 ,实际上m m m 的取值是Z p \mathbb{Z}_p Z p ,但是由于p p p 不公开,而{ 0 , 1 } κ − 1 ⊆ Z p \{0, 1\}^{\kappa-1} \sube \mathbb{Z}_p { 0 , 1 } κ − 1 ⊆ Z p ,所以取{ 0 , 1 } κ − 1 \{0, 1\}^{\kappa-1} { 0 , 1 } κ − 1 作明文空间,另外个人觉得m m m 可以等于0 0 0 ;
取随机数r ∈ Z n r \in \mathbb{Z}_n r ∈ Z n ,实际上r r r 的取值是Z φ ( n ) \mathbb{Z}_{\varphi(n)} Z φ ( n ) ,同样因为p p p 、q q q 不公开,而h r + φ ( n ) ≡ h r ( m o d n ) h^{r+\varphi(n)} \equiv h^r \pmod n h r + φ ( n ) ≡ h r ( mod n ) ,所以问题不大;
计算密文:C ≡ g m h r ( m o d n ) C \equiv g^m h^r \pmod n C ≡ g m h r ( mod n ) 。
解密 :
收到密文C C C 后,计算C p ≡ C p − 1 ( m o d p 2 ) C_p \equiv C^{p-1} \pmod {p^2} C p ≡ C p − 1 ( mod p 2 ) ;
计算明文:
C p ≡ g m ( p − 1 ) h r ( p − 1 ) ≡ g m ( p − 1 ) + r n ( p − 1 ) ≡ g m ( p − 1 ) + r p q φ ( p 2 ) ≡ g m ( p − 1 ) ≡ g p m C_p
\equiv g^{m(p-1)}h^{r(p-1)}
\equiv g^{m(p-1) + rn(p-1)}
\equiv g^{m(p-1) + rpq \varphi(p^2)}
\equiv g^{m(p-1)}
\equiv g_p^m
C p ≡ g m ( p − 1 ) h r ( p − 1 ) ≡ g m ( p − 1 ) + r n ( p − 1 ) ≡ g m ( p − 1 ) + r pqφ ( p 2 ) ≡ g m ( p − 1 ) ≡ g p m
所以:
L ( C p ) L ( g p ) ≡ C p − 1 g p − 1 ≡ g p m − 1 g p − 1 ≡ L ( g p m ) L ( g p ) ≡ m ( m o d p ) \frac{L(C_p)}{L(g_p)}
\equiv \frac{C_p - 1}{g_p - 1}
\equiv \frac{g_p^m - 1}{g_p - 1}
\equiv \frac{L(g_p^m)}{L(g_p)}
\equiv m \pmod p
L ( g p ) L ( C p ) ≡ g p − 1 C p − 1 ≡ g p − 1 g p m − 1 ≡ L ( g p ) L ( g p m ) ≡ m ( mod p )
最后回来看一下同态性质 ,设两个密文C 0 ≡ g m 0 h r 0 ( m o d n ) C_0 \equiv g^{m_0} h^{r_0} \pmod n C 0 ≡ g m 0 h r 0 ( mod n ) 和C 1 ≡ g m 1 h r 1 ( m o d n ) C_1 \equiv g^{m_1} h^{r_1} \pmod n C 1 ≡ g m 1 h r 1 ( mod n ) ,则有
C 0 C 1 ≡ g m 0 h r 0 g m 1 h r 1 ≡ g m 0 + m 1 h r 0 + r 1 ( m o d n ) C_0 C_1 \equiv g^{m_0} h^{r_0} g^{m_1} h^{r_1} \equiv g^{m_0 + m_1} h^{r_0 + r_1} \pmod n
C 0 C 1 ≡ g m 0 h r 0 g m 1 h r 1 ≡ g m 0 + m 1 h r 0 + r 1 ( mod n )
即C 0 C 1 C_0 C_1 C 0 C 1 就是使用随机数r 0 + r 1 r_0 + r_1 r 0 + r 1 对m 0 + m 1 m_0 + m_1 m 0 + m 1 加密的密文。
设C ≡ g m h r ( m o d n ) C \equiv g^m h^r \pmod n C ≡ g m h r ( mod n ) ,0 ≤ x m < p 0 \le xm < p 0 ≤ x m < p ,则有
C x ≡ ( g m h r ) x ≡ g x m h x r ( m o d n ) C^x \equiv (g^m h^r)^x \equiv g^{xm} h^{xr} \pmod n
C x ≡ ( g m h r ) x ≡ g x m h x r ( mod n )
即C x C^x C x 就是使用随机数x r xr x r 对x m xm x m 加密的密文。
安全性证明·
应该没人会看的,会看的还不如看论文(逃
实现代码·
代码基本上是根据上面理论部分写的,只是为了一些微不足道的实用性加了一堆if
和assert
,如果懂Python/Sagemath应该不难看懂。
keyGen(self, kappa=1024)
基本上按上面理论部分写,只是我只判断g p ≠ 1 g_p \ne 1 g p = 1 ,因为g p g_p g p 的阶只能是p p p 或1 1 1 ,而阶为1 1 1 时只可能是g p = 1 g_p = 1 g p = 1 ;
L(self, x)
只是简单地返回(x-1) // self.p
,为了统一类型,所有函数的返回值我都设置成整数(Integer
)类型,所以就没判断x x x 是否属于Z p 2 \mathbb{Z}_{p^2} Z p 2 ,最后返回也要包一层Integer(·)
;
encrypt(self, m)
也是按理论部分写,中间加了个m m m 的范围判断,实际上这步感觉应该由用户判定,因为限死m m m 落在Z p \mathbb{Z}_p Z p 会少了很多“骚操作”(
decrypt(self, c)
那里,在理论部分(和文章中)只是写了m ≡ L ( C p ) L ( g p ) ( m o d p ) m \equiv \frac{L(C_p)}{L(g_p)} \pmod p m ≡ L ( g p ) L ( C p ) ( mod p ) ,这个除法就很迷惑,实际上应该是一个Z p \mathbb{Z}_p Z p 中的求逆(因为L ( C p ) L(C_p) L ( C p ) 和L ( g p ) L(g_p) L ( g p ) 都会小于p p p );
最后测试了一下,在密钥生成中最耗时的是密钥生成部分,加密解密几乎瞬间完成,估计是素数采样需要时间,应该可以换作使用伪素数 解决
参考代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 class OU98 : def __init__ (self, kappa=1024 , g=None , n=None , p=None , q=None ): self.debug = False self.kappa = kappa if g==None and n==None and p==None and q==None : self.p, self.q, self.n, self.g, self.h, self.gp = self.keyGen(self.kappa) elif g!=None and n!=None : self.p, self.q, self.n, self.g = p, q, n, g self.h = pow (g, n, n) else : raise RuntimeError('give me at least g and n' ) def test (self ): assert self.debug == True print ('p = %s' % self.p) print ('q = %s' % self.q) print ('g = %s' % self.g) def keyGen (self, kappa=1024 ): while True : while True : p = random_prime(2 ^kappa) if len (p.bits()) == kappa: break while True : q = random_prime(2 ^kappa) if len (q.bits()) == kappa: break if gcd(p, q-1 )==1 and gcd(q, p-1 )==1 : break n = p*p*q while True : g = randrange(2 , n) gp = pow (g, p-1 , p^2 ) if gp != 1 : break h = pow (g, n, n) return Integer(p), Integer(q), Integer(n), Integer(g), Integer(h), Integer(gp) def L (self, x ): if self.p==None : raise RuntimeError('give me private keys!' ) assert x % self.p == 1 assert x > 0 and x < self.p^2 return Integer((x-1 ) // self.p) def encrypt (self, m ): if self.g==None or self.n==None : raise RuntimeError('give me public keys!' ) if type (m) != type (123 ): raise TypeError assert m >= 0 assert m < 2 ^(self.kappa-1 ) r = randrange(1 , self.n) c = pow (self.g, m, self.n) * pow (self.h, r, self.n) % self.n return Integer(c) def decrypt (self, c ): if self.p==None : raise RuntimeError('give me private keys!' ) try : self.gp except : self.gp = Integer(pow (self.g, self.p-1 , self.p^2 )) cp = Integer(pow (c, self.p-1 , self.p^2 ) % self.p^2 ) m = self.L(cp) * self.L(self.gp).inverse_mod(self.p) % self.p return m if __name__ == '__main__' : import time kappa = 512 for i in range (5 ): t = time.time() ou = OU98(kappa) print ('gen used: %s' % (time.time() - t)) m = Integer(randrange(2 , 2 ^(kappa-1 ))) t = time.time() c = ou.encrypt(m) print ('enc used: %s' % (time.time() - t)) t = time.time() m2 = ou.decrypt(c) print ('dec used: %s' % (time.time() - t)) if m != m2: break print ('--------------\n' ) else : print ('passed!' ) kappa = 256 ou1 = OU98(kappa, 231647749704830516191856775735058951582973748052376493876005550179680339768002386735844553860651472418830670763203401111501099820999265532225823653146633600539271633141439807033626938099143244499458112180922003084575670170749439549 , 449965027732786705204448970702653854316346349301045875495834129686323103419549315275112897318530198300450662598129396177920876892924334677401170876443274521113887362004858500246223292731098144098170050605521176321065811898811575921 ) m = Integer(randrange(2 , 2 ^(kappa-1 ))) c = ou1.encrypt(m) ou2 = OU98(kappa, 231647749704830516191856775735058951582973748052376493876005550179680339768002386735844553860651472418830670763203401111501099820999265532225823653146633600539271633141439807033626938099143244499458112180922003084575670170749439549 , 449965027732786705204448970702653854316346349301045875495834129686323103419549315275112897318530198300450662598129396177920876892924334677401170876443274521113887362004858500246223292731098144098170050605521176321065811898811575921 , 64870160335526547317489515685800845826224255169675627357037302268782157579143 , 106927353523516648126355464793570089637028921500478312009962497218537911288129 ) print (m == ou2.decrypt(c))
同态性质应用·
(2023.04.04更新)
理论上同态只要跟着文章开始时的几条式子操作就好了,即明文的加法是密文的乘法、明文的数乘是密文取对应常数的幂,不过在应用上需要注意的是,进行若干次同态操作后需要保证明文依然落在Z p \mathbb{Z}_p Z p 中(或{ 0 , 1 } κ \{0, 1\}^\kappa { 0 , 1 } κ 中) ,否则不能解密,下面给一个应用上的例子。
假设在服务器端有一个度(Degree)为d d d 的多项式
f ( x ) = a 0 x 0 + a 1 x 1 + . . . + a d x d f(x) = a_0 x^0 + a_1 x^1 + ... + a_d x^d
f ( x ) = a 0 x 0 + a 1 x 1 + ... + a d x d
服务端想要在保证自己的多项式系数(a 0 , a 1 , . . . , a d a_0, a_1, ..., a_d a 0 , a 1 , ... , a d )不被暴露给用户端计算f ( x ) f(x) f ( x ) ;而用户也不想服务端知道自己的输入x x x 是什么。那么可以设计以下协议:
U s e r ( x ) S e r v e r ( a 0 , a 1 , . . . , a d ) pk = ( g , n ) , sk = ( p , q ) ← KeyGen ( 1 κ ) c x = ( Enc pk ( x 0 ) , Enc pk ( x 1 ) , . . . , Enc pk ( x d ) ) ⟶ pk , c x c f ( x ) ≡ ∏ i = 0 d c x , i a i ( m o d n ) ⟵ c f ( x ) f ( x ) = Dec sk ( c f ( x ) ) \begin{array}{|l|}
\hline
\begin{array}{lcl} \\
\mathrm{User}(x)
& \ \ \ \ \ \ \ \ \ \ \ \ \ &
\mathrm{Server}(a_0, a_1, ..., a_d) \\
\begin{array}{l} \\
\text{pk} = (g, n), \text{sk} = (p, q) \leftarrow \text{KeyGen}(1^\kappa) \\
c_x = (\text{Enc}_\text{pk}(x^0), \text{Enc}_\text{pk}(x^1), ..., \text{Enc}_\text{pk}(x^d)) \\
\end{array} & & \\
& \overset{_\text{pk}, c_x}{\longrightarrow} & \\
& &\begin{array}{l}
c_{f(x)} \equiv \prod_{i=0}^{d} c_{x, i} ^ {a_i} \pmod n \\
\end{array} \\
& \overset{c_{f(x)}}{\longleftarrow} & \\
\begin{array}{l}
f(x) = \text{Dec}_\text{sk}(c_{f(x)}) \\
\end{array} & & \\
\end{array} \\
\hline
\end{array}
User ( x ) pk = ( g , n ) , sk = ( p , q ) ← KeyGen ( 1 κ ) c x = ( Enc pk ( x 0 ) , Enc pk ( x 1 ) , ... , Enc pk ( x d )) f ( x ) = Dec sk ( c f ( x ) ) ⟶ pk , c x ⟵ c f ( x ) Server ( a 0 , a 1 , ... , a d ) c f ( x ) ≡ ∏ i = 0 d c x , i a i ( mod n )
即用户先对自己的每一项x i x^i x i 进行加密,然后发送给服务端,服务端通过同态操作实现线性运算,得出f ( x ) f(x) f ( x ) 的密文c f ( x ) c_{f(x)} c f ( x ) (服务端没有私钥不能解密),然后把c f ( x ) c_{f(x)} c f ( x ) 发送回用户,用户解密后便可得到f ( x ) f(x) f ( x ) (直观上用户不能通过c f ( x ) c_{f(x)} c f ( x ) 获取客户端的系数)。
上面说过,需要保证明文依然落在Z p \mathbb{Z}_p Z p 中,所以需要稍微计算一下参数的取值,为了简化,假设参数和未知数都是λ \lambda λ 比特的数(x , a 0 , a 1 , . . . , a d ∈ { 0 , 1 } λ x, a_0, a_1, ..., a_d \in \{0, 1\}^\lambda x , a 0 , a 1 , ... , a d ∈ { 0 , 1 } λ ),那么只要设置
λ ( d + 1 ) + 1 ≤ κ \lambda (d + 1) + 1 \le \kappa
λ ( d + 1 ) + 1 ≤ κ
即可,推导过程有点像二进制编码,a d x d a_d x^d a d x d 项最大λ ( d + 1 ) \lambda (d + 1) λ ( d + 1 ) ,前面的项加起来不会大于a d x d a_d x^d a d x d ,所以f ( x ) f(x) f ( x ) 不会大于a d x d a_d x^d a d x d 的两倍。(大概意思,逃
当然以上只是一个小玩具,实际应用时还要考虑很多东西,比如这个多项式只能进行有限次(没记错是最多d d d 次)的运算,否则用户可以通过解线性方程的方法恢复系数;又比如在参数设计方面,要考虑攻击者能否进行类似对背包密码的格攻击,等。
另外,OU98的加密算法只能进行加法和数乘(Scalar)同态,而不能进行乘法同态,即对E ( a ) \text{E}(a) E ( a ) 和E ( b ) \text{E}(b) E ( b ) 操作后获得E ( a b ) \text{E}(ab) E ( ab ) ,如果选用能够乘法同态的算法的话,上面协议估计能有更多骚操作,比如用户只用对x x x 进行加密,又或者可以对多元多项式进行运算,等。
下面给一下上面玩具协议的参考代码,在运行前还要进行一些操作:
假设上面代码文件叫OU98.sage
,理论上运行一次后会生成一个OU98.sage.py
的文件,为了能在别的代码中通过import
引用上面代码,需要稍微先改一下文件的名字:
1 cp ./OU98.sage.py ./OU98.py
还有上面代码中,需要把decrypt
函数中对应行修改为(后来才找到的Bug,我也不知道这里Sagemath抽了什么风x):
1 cp = Integer(pow (c, self.p-1 , self.p^2 ) % self.p^2 )
最后参考代码(有空再考虑把同态写个函数,逃):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 from OU98 import OU98bits = 15 degree = 16 kappa, g, n, p, q = (256 , 231647749704830516191856775735058951582973748052376493876005550179680339768002386735844553860651472418830670763203401111501099820999265532225823653146633600539271633141439807033626938099143244499458112180922003084575670170749439549 , 449965027732786705204448970702653854316346349301045875495834129686323103419549315275112897318530198300450662598129396177920876892924334677401170876443274521113887362004858500246223292731098144098170050605521176321065811898811575921 , 64870160335526547317489515685800845826224255169675627357037302268782157579143 , 106927353523516648126355464793570089637028921500478312009962497218537911288129 ) assert bits * (degree + 1 ) + 1 <= kappa def User (): global kappa, g, n, p, q ou = OU98(kappa, g, n, p, q) x = Integer(randint(0 , 2 ^bits-1 )) cx = [ou.encrypt(x^i) for i in range (degree + 1 )] return (kappa, g, n), cx, x def Server (pk, cx ): kappa, g, n = pk ou = OU98(kappa, g, n) coeff = [Integer(randint(0 , 2 ^bits-1 )) for i in range (degree + 1 )] cfx = product([pow (cx[i], coeff[i], n) for i in range (degree + 1 )]) % n return cfx, coeff def Checker (cfx, coeff, x ): global kappa, g, n, p, q ou = OU98(kappa, g, n, p, q) fx = ou.decrypt(cfx) print ('[Debug] fx1 = %d' % fx) realFx = sum ([coeff[i] * x^i for i in range (degree + 1 )]) print ('[Debug] fx2 = %d' % realFx) return fx == realFx def Transcriber (): pk, cx, x = User() cfx, coeff = Server(pk, cx) result = Checker(cfx, coeff, x) return result if __name__ == '__main__' : for i in range (5 ): print ('--------------\n' ) print (Transcriber())