- 已编辑
原文链接:https://tover.xyz/p/OU98/
参考
前言
之所以选择写这个加密方案,主要原因是它有一种很好用的同态性质,即摘要的第6点,次要原因是它用了一些挺“漂亮”的构造,次次要原因是我自己有可能会用到,所以做个笔记。
设是在该方案中使用随机数对明文进行加密,密文落在中,如果有两个明文和达到条件(是一个模数,可以参考下面的理论部分),上图摘要第6点的同态性质说的是(虽然论文里没说,但其实随机数部分也有同态):
从这个关系可以轻松推出摘要第7点的密文转换:
另外文章没说的是,这个方案还有一个“乘法同态”的属性(可以姑且称上面的同态是“加法同态”),设存在一个使得(如果不用正确解密甚至不需要这个条件),则:
知识清单
如果看到这里还没把你劝退的话,接下来的阅读可能需要:
- 群论知识,最好也知道环
- 看过RSA/ElGamal等加密方案,虽然只是方便作类比而已
- 英语论文阅读能力
- Sagemath/Python代码阅读、编写能力
理论部分
前置知识
这里需要先说明方案加解密时用到一些东西。首先是解密需要用到的一个p-西罗子群([OU98]定义1),令是奇素数,下面的是的p-西罗子群:
实际上也可以看成下面的写法(虽然我没严格证明过但两个东西应该等同,而且文章中实际计算时也会用下面比较具象的表述):
文章中直接由的阶是(和,根据拉格朗日定理?)就推出的阶是。
用上面的“具象表述”也可以给出比较严格的推理:设是的一个生成元(根据广义原根定理,是循环群,所以一定有生成元)
表述中的表示的其实是中的所有元素,那么借助生成元使用也可以达到同样的效果,所以可以被写作(其中是欧拉函数,):
根据欧拉定理,有
说明和生成的值是一样的,即只要,生成的值就是多余的,只有落在中的有用,所以可以被写作:
所以,即阶为。
观察的结构,只要,就有,即(),也就是可以被整除,于是就可以定义一个文章中称作的函数:
之所以定义这个是因为解密需要用到其同态属性(其中括号里是上的运算,括号外是上的运算):
简要证明:首先
最后,因为,所以,故
得证。
最后说明一下怎么用的同态属性解密:设,,设模拟一个密文,,那么可以通过计算
实现解密。
另外,文章中介绍了取样“”的方法,只要是的一个生成元(或者原文叫原根),就会有。
讲证明前先讲一个换模公式:若,则且。
简要证明:由可得,存在使得,所以
同理。
而在上述取样中,由于(注意因为是的子群),所以需要证的是
使用上面的换模公式后,即证
根据欧拉定理,显然成立。
加密方案
密钥生成:
- 取俩大素数和,其中和都是位的素数,即,另外还需要满足;
- 设;
- 采样的一个生成元,文章的做法是,先任意取,若的阶为则保留,否则重选(大概意思是让的阶是);
- 设;
- 公钥设置为:,其实不公开也行,文章说公开是为了效率;
- 私钥设置为: 。
加密:
- 明文空间为,实际上的取值是,但是由于不公开,而,所以取作明文空间,另外个人觉得可以等于;
- 取随机数,实际上的取值是,同样因为、不公开,而,所以问题不大;
- 计算密文:。
解密:
收到密文后,计算;
计算明文:
所以:
最后回来看一下同态性质,设两个密文和,则有
即就是使用随机数对加密的密文。
设,,则有
即就是使用随机数对加密的密文。
安全性证明
应该没人会看的,会看的还不如看论文(逃
实现代码
代码基本上是根据上面理论部分写的,只是为了一些微不足道的实用性加了一堆if
和assert
,如果懂Python/Sagemath应该不难看懂。
keyGen(self, kappa=1024)
基本上按上面理论部分写,只是我只判断,因为的阶只能是或,而阶为时只可能是;L(self, x)
只是简单地返回(x-1) // self.p
,为了统一类型,所有函数的返回值我都设置成整数(Integer
)类型,所以就没判断是否属于,最后返回也要包一层Integer(·)
;encrypt(self, m)
也是按理论部分写,中间加了个的范围判断,实际上这步感觉应该由用户判定,因为限死落在会少了很多“骚操作”(decrypt(self, c)
那里,在理论部分(和文章中)只是写了,这个除法就很迷惑,实际上应该是一个中的求逆(因为和都会小于);- 最后测试了一下,在密钥生成中最耗时的是密钥生成部分,加密解密几乎瞬间完成,估计是素数采样需要时间,应该可以换作使用伪素数解决
参考代码:
# Sage
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:
# gen p with kappa size
while True:
p = random_prime(2^kappa)
if len(p.bits()) == kappa:
break
# gen q with kappa size
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
# gen n, g, h
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))
m = self.L(cp) * self.L(self.gp).inverse_mod(self.p) % self.p
return m
if __name__ == '__main__':
# test correctness
# test time
import time
kappa = 512
for i in range(5):
t = time.time()
ou = OU98(kappa)
print('gen used: %s' % (time.time() - t))
#ou.test()
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!')
# test function
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))