RSA算法之实现篇(Python版)

序言

本文基于Python的gmpy2库对RSA算法案进行实现。实现涉及多种数论算法,本文旨在介绍RSA算法的实现流程,不会对于每一个涉及的算法进行深入介绍,而是直接调用gmpy2库的API,有兴趣深入了解的可以参考本博客的其它文章。

gmpy2安装

pip install gmpy2==2.1.0a1

为什么偏偏是2.1.0a1这个版本呢?因为截止到本文最近一次编辑,gmpy2的其它版本依然没有Python 3.5及以上版本的wheel包,pip安装需要下载源代码到本地编译,会造成不必要的麻烦(比如在Windows下会要求你先安装Visual Studio 2015)。

RSA算法实现

RSA算法大体可以分为三个部分:

  • 生成密钥对
  • 加密
  • 解密

生成密钥对

其中生成密钥对包括以下步骤:

  • 随机生成两个足够大的素数 p , q p,q p,q
  • 计算公共模数 n = p q n=pq n=pq
  • 计算欧拉函数 φ ( n ) = ( p − 1 ) ( q − 1 ) φ(n)=(p−1)(q−1) φ(n)=(p1)(q1)
  • 选取一较小的与φ(n)互质的正整数e作为公共指数。则数对 ( n , e ) (n, e) (n,e)为密钥对中的公钥
  • 计算 d = e − 1 m o d    φ ( n ) d=e^{-1}\mod φ(n) d=e1modφ(n),则数对 ( n , d ) (n, d) (n,d)为密钥对中的私钥

第一步,随机素数生成

根据著名的素数定理,我们可以知道,随机选取一个正整数n,它是素数的概率为 1 l n ( n ) 1\over ln(n) ln(n)1,这个概率并不算小,所以我们用最暴力的方法选取素数:随机选取一个很大的正整数,检测它是否为素数,如果它不是素数,那我们就可以逐个测试它邻近的奇数,直到找到一个素数为止。

比如,我们需要生成一个长度为1024位的素数,那我们先随机选取一个长度为1024位的正整数,它是素数的概率约为 1 l n ( 2 1024 ) ≈ 1 710 {1 \over ln(2 ^{1024})} ≈ {1 \over 710} ln(21024)17101,排除掉偶数,选中素数的概率约为三百五十分之一。

这样问题就转移到如何测试一个正整数是否为素数上了。目前最常用的概率性素性检测方法是米勒-拉宾素性检测法,该算法的原理可以参考本博客的另一篇文章,这里我们使用gmpy2中的素数检测函数。

代码实现:

import gmpy2
from gmpy2 import mpz

rs = gmpy2.random_state()

def gen_prime():
    p = gmpy2.mpz_urandomb(rs, 1024)
    while not gmpy2.is_prime(p):
        p = gmpy2.add(p, 1)
    return p


p = gen_prime()
q = gen_prime()

第二步为简单的乘法运算

n = p * q

第三步计算欧拉函数值也只是减法和乘法运算

phi = (p - 1) * (q - 1)

第四步,选取公共指数e,e必须与φ(n)互质,为了方便,我们直接令e为素数,可以保证φ(n)为任意值时都与e互质。为了计算效率,e常取3, 17和65537三个值,因为这三个素数的二进制位中的1数量最少(仅包含两个1),计算效率最高。这里直接取65537。

第五步求e在模φ(n)下的乘法逆元(也被称为数论倒数)d,需要用到扩展欧几里得算法,详细的可以参考本博客的另一篇文章,这里直接用pygmp2中的API即可。

d = gmpy2.invert(e, phi)

加密与解密

加密就是计算
C = M e m o d    n C=M^e \mod n C=Memodn
其中M为明文,(n, e)为公钥,C为密文

而解密则是计算
M = C d m o d    n M=C^d\mod n M=Cdmodn
其中C为密文,(n, d)为私钥,M为明文

可以看到,加密跟解密都是对以下函数进行求值:

f b ( a ) = a b m o d    n f_b(a)=a^b\mod n fb(a)=abmodn

这种形如 a b m o d    n a^b\mod n abmodn的运算,我们称之为模幂运算。模幂运算在密码学中具有十分重要的意义,除了RSA加密外,离散对数加密等常用的加密方法里都有模幂运算。
对于模幂运算,直觉上是先计算a的b次幂,然后再对其取模。但如果模幂运算中的a或者b特别大,那求幂运算将非常困难。不过,根据模运算的特点,我们并不需要直接计算出a的b次方的值。这种算法叫快速模幂。

快速模幂

假设我们正在加密消息M = 3,取公共指数e = 11,公共模数n = 5。
11的二进制形式为1011,那么我们可以把它变成以下形式:
3 11 = 3 8 ∗ 3 2 ∗ 3 1 3^{11}=3^8∗3^2∗3^1 311=383231

由模运算的性质可知,我们只需要利用反复平方法计算

3 8 m o d    5 3^8 \mod 5 38mod5 3 2 m o d    5 3^2 \mod 5 32mod5 3 m o d    5 3 \mod 5 3mod5的值,即可计算出 3 11 m o d    5 3^{11} \mod 5 311mod5的值。

计算3 ^11 mod 5的值,我们首先计算
3 1 m o d    5 = 3 3 2 m o d    5 = ( 3 1 m o d    5 ) 2 m o d    5 = 4 3 4 m o d    5 = ( 3 2 m o d    5 ) 2 m o d    5 = 1 3 8 m o d    5 = ( 3 4 m o d    5 ) 2 m o d    5 = 1 \begin{aligned} 3^1\mod 5&=3 \\ 3^2\mod 5&=(3^1\mod5)^2\mod5=4\\ 3^4\mod5&=(3^2\mod5)^2\mod5=1\\ 3^8\mod5&=(3^4\mod5)^2\mod5=1 \end{aligned} 31mod532mod534mod538mod5=3=(31mod5)2mod5=4=(32mod5)2mod5=1=(34mod5)2mod5=1

由于 3 11 = 3 8 ∗ 3 2 ∗ 3 1 3^{11}=3^8∗3^2∗3^1 311=383231

3 11 m o d    5 = ( 3 8 ∗ 3 2 ∗ 3 1 ) m o d    5 = [ ( 3 8 m o d    5 ) ( 3 2 m o d    5 ) ( 3 1 m o d    5 ) ] m o d    5 = ( 1 ∗ 4 ∗ 3 ) m o d    5 = 2 \begin{aligned} 3^{11}\mod5 &=(3^8∗3^2∗3^1)\mod5 \\ &=[(3^8\mod5)(3^2\mod5)(3^1\mod5)]\mod5 \\ &=(1∗4∗3)\mod5 \\ &=2 \end{aligned} 311mod5=(383231)mod5=[(38mod5)(32mod5)(31mod5)]mod5=(143)mod5=2

这也是为什么之前选取公共指数e的时候,要选择3、17或65537的原因,它们的二进制形式分别为11, 1001, 10000000000000001。指数(二进制形式)中1的个数越少,计算效率越高。

那么加密过程的代码实现为:

M = 123456 # 假设M是待加密的消息
C = gmpy2.powmod(M, e, n)

解密:

M = gmpy2.powmod(C, d, n)

总结

下面是一个完整的使用RSA算法进行加密解密的程序代码,该代码在Python3.6下正常运行。需要注意的是,输入消息不能过长,否则编码后16进制数字长度大于公共模数n时,会无法解密。

import gmpy2
from gmpy2 import mpz
import binascii


def gen_prime(rs):
    """生成二进制位数为1024的随机素数"""
    p = gmpy2.mpz_urandomb(rs, 1024)
    while not gmpy2.is_prime(p):
        p = p + 1
    return p


def gen_key():
    """生成密钥"""
    rs = gmpy2.random_state()
    p = gen_prime(rs)
    q = gen_prime(rs)
    return p, q


def encrypt(e, n, message):
    """将输入消息转换成16进制数字并加密,支持utf-8字符串"""
    M = mpz(binascii.hexlify(message.encode('utf-8')), 16)
    C = gmpy2.powmod(M, e, n)
    return C


def decrypt(d, n, C):
    """对输入的密文进行解密并解码"""
    M = gmpy2.powmod(C, d, n)
    return binascii.unhexlify(format(M, 'x')).decode('utf-8')


def main():
    # 密钥生成
    p, q = gen_key()
    n = p * q
    phi = (p - 1) * (q - 1)
    e = 65537
    d = gmpy2.invert(e, phi)

    # 输入消息
    message = input('输入待加密的消息:\n')

    # 加密
    C = encrypt(e, n, message)
    print('16进制密文:', hex(C))

    # 解密
    print('解密后的消息:', decrypt(d, n, C))


if __name__ == '__main__':
    main()

从上面代码可以看到,RSA算法本身是非常简洁的,核心逻辑仅有十多行代码。当然,这只是一个教科书实现,仅用于帮助理解RSA算法,从安全性和效率上都跟OpenSSL等普遍使用的实现有较大差距。

注:本文此前使用C++实现,但是代码略显繁琐,现使用Python重新实现。如果需要C++版代码,请移步这里。

你可能感兴趣的:(算法,c++,密码学)