PyCrypto密码学库源码解析(一)随机数和大素数生成

Python Crypto库源码解析(一) 随机数和大素数的生成

* 版权声明 *
引用请注明出处,转载请联系: h0.1c@foxmail.com

本系列文章(Python Crypto库源码解析)主要讲解Python的密码学工具库PyCrypto的源码中各个技术实现的原理和细节。
本系列文章的编写力图用最少的语言解释清楚要解释的事情,不涉及复杂的数学原理,旨在讲清楚在密码技术实现的过程中一些教科书上不会提到的一些细节问题。
使用的代码来自于pycrypto库,这个库包含了当前绝大多数主流的密码学算法(包括加解密、Hash、签名等),在如下网址可以获取更多信息: https://pythonhosted.org/pycrypto/


本文(随机数和大素数的生成 )主要说明该库中的Util.number模块,也就是主要负责生成随机数/符合RSA要求的素数的模块。


0.本文需要用到的背景知识

如果学习过密码学原理相关课程的朋友可以跳过这部分,为了方便没有经过专业密码学训练或者忘得差不多想快速回忆起来的朋友,在正文开始前,需要对一下知识进行简单的梳理。

0.1 素数的概念

素数定义为在大于1的自然数中,除了1和它本身以外不再有其他因数的数。

0.2 素性检测

素性检测是检验一个给定的整数是否为素数的测试。这样的测试通常并不能同时有效和快速检测出该数是否为素数。
例如实际应用中,通常会使用Monte Carlo方法来完成,Monte Carlo方法是基于随机数的计算方法的统称,具有多项式时间复杂度。其中具有代表性的是Solovay-Strassen算法(1974年)和Miller-Rabin算法。而Solovay-Strassen算法把合数误判为素数的概率为1/2,Miller-Rabin算法将合数误判为素数的概率是1/4。
印度科技学院的Agrawal,Kayal和Saxena提出了一个确定性算法AKS算法(2002年),该算法使用素数的充要条件检测,并证明算法可在多项式时间内完成。

0.3 Miller Rabin 算法

算法描述

Miller-Rabin( n n ):
n1写成 n1=2km n − 1 = 2 k ∗ m 的形式,其中m是一个奇数
随机选择整数 a a ,满足1an1
b=ammodn b = a m m o d n
if b1(modn) b ≡ 1 ( m o d n )
     then return (n_is_prime)
for i i =0 to k-1 {
       if b1(modn)
              then return (n_is_prime)
       else b = b2 b 2 mod n
}
return (n_is_composite)

上述是算法描述中的一种,在具体实现中,会有一定的差别,但是使用的数学原理都是一样的(Fermat定理)。

0.4 随机数的分类

随机数分为伪随机数(只满足统计学伪随机性)、密码学安全的伪随机数(满足统计学伪随机性并且不可以通过演算预测之后的随机序列)、真随机数(随机样本不可重现)。
简单来讲,统计学伪随机性,顾名思义,就是统计起来是随机的,也就是拿到一串随机序列之后,发现各种随机数出现的频率是一样的,这类随机数一般由时钟或者线性同余方程得出,这样的方法就不是密码学安全的随机数,因为使用时钟或者线性同余方程得到的随机序列,通过计算,是可以预测接下来的序列的。而真随机数就是利用某些不可复现(当然也就不可预测)的方式,例如计算机当地的本底辐射波动值。

0.5 RSA算法描述

1生成密钥

选取两个大素数p和q,计算n=pq。
随机选取加密密钥e,使e和(p-1)(q-1)互素。
计算解密密钥d,使ed≡1 mod(p-1)(q-1)
e和n是公开密钥,放在一个公共目录供大家访问,d是私人密钥,p和q不再需要,密钥生成后可抹去,但不能泄漏

2数据加密

加密 m m 时,首先将它分成比n小的数据分组,对每个分组 mi<n m i < n 加密,得到密文 ci=miemodn c i = m i e m o d n

3数据解密

解密时,对每个密文分组 ci c i 计算: cdimodn=mi c i d m o d n = m i

0.6 RSA算法对素数的要求

RSA算法的安全性主要依赖于给出N难以分解出PQ的复杂性。
而RSA算法对素数具有诸多要求,例如 |p-q|很大,通常p和q的长度相同、p+1和q+1分别含有大素因子等等,这些要求在后文(2.6)中会详细介绍(可以参见“Fast generation of random, strong RSA primes”1。在生成随机数的时候需要进行多次筛选判断,这也是RSA速度慢的原因之一。


1. Number模块中涉及的函数

1.1 主要函数列表

  • size (N):输入long型数N返回其比特位数
  • getRandomInteger(N, randfunc=None):获取N比特的随机数
  • getRandomRange(a, b, randfunc=None):在[a,b)区间内获得一个随机数
  • getRandomNBitInteger(N, randfunc=None):获得确实是N比特的随机数(也就是保证最高位非0)
  • GCD(x,y):获得X和Y的最大公约数
  • inverse(u, v):获得 u u modv的逆
  • getPrime(N, randfunc=None):反复使用素性检测算法验证以获素随机数
  • _rabinMillerTest(n, rounds, randfunc=None):使用Miller Rabin算法进行素性检测,其中rounds是检测轮数2
  • getStrongPrime(N, e=0, false_positive_prob=1e-6, randfunc=None):获得满足RSA要求的大素数
  • isPrime(N, false_positive_prob=1e-6, randfunc=None):使用小素数表和Miller-Rabin算法判定是否为素数
  • long_to_bytes(n, blocksize=0):将long型数转为字节型数据,并且以blocksize分块
  • bytes_to_long(s):将字节流转换为long型数

1.2 需要注明的内容

  • randfunc是生成随机数的函数,如果不指定则默认使用Crypto库Random模块下的new().read()
  • sieve_base :包含了前10000个素数的列表
  • from Crypto.Util.py3compat import * :为了与python3 进行兼容
  • getRandomNumber(N, randfunc=None):已被弃用,如果调用,返回的是 getRandomNBitInteger(N, randfunc)

2.代码实现中值得注意的细节

本节讲述在实现过程中的一些细节,是我自己在实现的时候所没有想到的或者自己实现的时候实现过于复杂的。
讲述的顺序是按照底层函数–>上层函数。

2.1生成一个随机数和在范围里生成一个随机数

getRandomInteger函数返回一个long型随机数。

def getRandomInteger(N, randfunc=None):
    if randfunc is None:
        _import_Random()
        randfunc = Random.new().read
    S = randfunc(N>>3)
    odd_bits = N % 8
    if odd_bits != 0:
        char = ord(randfunc(1)) >> (8-odd_bits)
        S = bchr(char) + S
    value = bytes_to_long(S)
    return value

上文提到,如果不指定randfunc 那么会自动使用Random.new().read()函数,我们可以看看在Random模块中,这个函数是如何定义的:

def new():
    return RNGFile(_get_singleton())

继续回溯可以发现该随机函数来自于OSRNG,也就是由操作系统提供的随机数生成器(关于OSRNG的描述见附录):

def collect(self):
        # Collect 64 bits of entropy from the operating system and feed it to Fortuna.
        self._osrng_es.feed(self._osrng.read(8))

        # Add the fractional part of time.time()
        t = time.time()
        self._time_es.feed(struct.pack("@I", int(2**30 * (t - floor(t)))))

        # Add the fractional part of time.clock()
        t = time.clock()
        self._clock_es.feed(struct.pack("@I", int(2**30 * (t - floor(t)))))

getRandomRange函数则负责在给定范围中生成随机数:

def getRandomRange(a, b, randfunc=None):
    range_ = b - a - 1
    bits = size(range_)
    value = getRandomInteger(bits, randfunc)
    while value > range_:
        value = getRandomInteger(bits, randfunc)
    return a + value

可以见得,这种生成逻辑很简单,也就是先用getRandomInteger函数生成一个数,判断其是否大于b-a的间隔,如果大于则重新生成直到不大于为止,这个时候,返回a+该值即可。

2.2 生成恰好为N比特的随机数

getRandomNBitInteger生成恰好为N比特的随机数,其生成逻辑也就是将getRandomInteger 函数生成的数最高比特位置为1。

def getRandomNBitInteger(N, randfunc=None):
    value = getRandomInteger (N-1, randfunc)
    value |= 2 ** (N-1) # Ensure high bit is set
    assert size(value) >= N
    return value

2.3 Miller-Rabin素性检测

def _rabinMillerTest(n, rounds, randfunc=None):
    """
    Returns 0 when n is definitly composite.
    Returns 1 when n is probably prime.
    Returns 2 when n is definitly prime.
    """
    # check special cases (n==2, n even, n < 2)
    if n < 3 or (n & 1) == 0:
        return n == 2
    # 将n-1存储下来以节省时间
    n_1 = n - 1
    # 这些步骤就是在将n-1写成n-1=2^k*m的形式
    b = 0
    m = n_1
    while (m & 1) == 0:
        b += 1
        m >>= 1

    tested = []
    #使用tested存储已经被检验的数
    # we need to do at most n-2 rounds.
    #为什么需要做多轮已经在前文中叙述
    for i in range (min (rounds, n-2)):
        # 随机选取a<n并且保证a没有在tested中出现过
        a = getRandomRange (2, n, randfunc)
        while a in tested:
            a = getRandomRange (2, n, randfunc)
        tested.append (a)
        # rabin-miller检测的本体
        z = pow (a, m, n) # (a**m) % n
        if z == 1 or z == n_1:
            continue
        composite = 1
        for r in range (b):
            z = (z * z) % n
            if z == 1:
                return 0
            elif z == n_1:
                composite = 0
                break
        if composite:
            return 0
    return 1

2.4 判断一个数是否为素数

def isPrime(N, false_positive_prob=1e-6, randfunc=None):
    if _fastmath is not None:
        return _fastmath.isPrime(int(N), false_positive_prob, randfunc)

    if N < 3 or N & 1 == 0:
        return N == 2
    #使用小素数sieve_base表进行筛选,节省时间
    for p in sieve_base:
        if N == p:
            return 1
        if N % p == 0:
            return 0
     #轮数由期望的误判概率决定
    rounds = int(math.ceil(-math.log(false_positive_prob)/math.log(4)))
    #最后用Miller-rabin算法返回结果
    return _rabinMillerTest(N, rounds, randfunc)

2.5 获取一个素数

def getPrime(N, randfunc=None):
    if randfunc is None:
        _import_Random()
        randfunc = Random.new().read
    #使用的是获取一个确切为N比特的函数
    #并且将其最低比特位设为1,保证其不为偶数,这样可以节省尝试次数
    number=getRandomNBitInteger(N, randfunc) | 1
    #使用2.4中提到的方法进行盘点,如果不是素数,则将当前数加2再判断
    while (not isPrime(number, randfunc=randfunc)):
        number=number+2
    #直到最后获取一个素数为止
    return number

2.6 获取一个满足RSA要求的强素数

getStrongPrime是这个模块中最复杂的一个函数,它根据RSA的要求生成强素数,按照说明文档所描述的,生成的素数满足条件为:

  • In this context p is a strong prime if p-1 and p+1 have at least one large prime factor.
  • N should be a multiple of 128 and > 512.
  • If e is provided the returned prime p-1 will be coprime to e and thus suitable for RSA where e is the public exponent.

简单翻译过来就是强素数p的p-1和p+1需要有一个大素数因子,强素数的比特数应该是128的整数倍,并且需要大于512,另外就是强素数需要满足p-1与RSA的参数e互素。

这个函数的构成比较复杂,我们分开来解读:

if (N < 512) or ((N % 128) != 0):
        raise ValueError ("bits must be multiple of 128 and > 512")

对于输入的位数参数进行判断,如果不满足位数条件则报错。

rabin_miller_rounds = int(math.ceil(-math.log(false_positive_prob)/math.log(4)))

根据所给的概率参数判断Miller-Rabin需要做的轮数,这个在之前提到过。

    #   lower_bound = sqrt(2) * 2^{511 + 128*x}
    #   upper_bound = 2^{512 + 128*x} - 1
    x = (N - 512) >> 7;
    lower_bound = divmod(14142135623730950489 * (2 ** (511 + 128*x)),
                         10000000000000000000)[0]
    upper_bound = (1 << (512 + 128*x)) - 1
    # Randomly choose X in calculated range
    X = getRandomRange (lower_bound, upper_bound, randfunc)

这一段就是在sqrt(2)2N1 2N1 2 N − 1 中获得一个数X,之所以写的复杂,是因为要节省运算时间和保证不会发生溢出的情况。

  # generate p1 and p2
    p = [0, 0]
    for i in (0, 1):
        # randomly choose 101-bit y
        y = getRandomNBitInteger (101, randfunc)
        # initialize the field for sieving
        field = [0] * 5 * len (sieve_base)
        # sieve the field
        for prime in sieve_base:
            offset = y % prime
            for j in range ((prime - offset) % prime, len (field), prime):
                field[j] = 1

        # look for suitable p[i] starting at y
        result = 0
        for j in range(len(field)):
            composite = field[j]
            # look for next canidate
            if composite:
                continue
            tmp = y + j
            result = _rabinMillerTest (tmp, rabin_miller_rounds)
            if result > 0:
                p[i] = tmp
                break
        if result == 0:
            raise RuntimeError ("Couln't find prime in field. "
                                "Developer: Increase field_size")

使用筛选的方法产生两个质数,两个质数都是至少为101比特,这两个质数是为了后面满足X+1和X-1都有大质数因子而准备的。

    tmp1 = inverse (p[1], p[0]) * p[1]  # (p2^-1 mod p1)*p2
    tmp2 = inverse (p[0], p[1]) * p[0]  # (p1^-1 mod p2)*p1
    R = tmp1 - tmp2 

    # search for final prime number starting by Y0
    #    Y0 = X + (R - X mod p1p2)
    increment = p[0] * p[1]
    X = X + (R - (X % increment))

这一部分就是在用数论方法保证X+1和X-1都有大质数因子,并且这两个大质数是101比特,这里可以简单进行一下数学推导,推导需要用到域的相关性质,如果没有数学基础可以略过。

R = (p2^{-1} mod p1) * p2 - (p1^{-1} mod p2) * p1,其中-1次方表示mod域下的逆,由此可以知道,(R mod p1)*(R mod p2)=-1 , 又有 Y0 = X + (R - X mod p1p2),可以知道Y0 与 R mod p1p2 同余,也就是说, Y0 也满足 mod p1 = 1 和 mod p2 = -1 的期望性质

if e and is_possible_prime:
            if e & 1:
                if GCD (e, X-1) != 1:
                    is_possible_prime = 0
            else:
                if GCD (e, divmod((X-1),2)[0]) != 1:
                    is_possible_prime = 0

在参数中给出了非0的e的时候,需要满足X-1与RSA的参数e互素,这是算法中安全性的要求,具体数学原理可以参见其他书籍。

附录 OSRNG的具体描述

OSRNG根据不同的操作系统决定调用不同操作系统所提供的随机数生成器(RNG= Random Number Generator),在init 文件中有如下部分:

if os.name == 'posix':
    from Crypto.Random.OSRNG.posix import new
elif os.name == 'nt':
    from Crypto.Random.OSRNG.nt import new
elif hasattr(os, 'urandom'):
    from Crypto.Random.OSRNG.fallback import new
else:
    raise ImportError("Not implemented")

也就是posix(Unix类)系统在OSRNG.posix中导入new函数,nt(Windows类)系统在OSRNG.nt中导入new函数,这里以Windows系统为例看看OSRNG.nt模块:

from Crypto.Random.OSRNG import winrandom
from .rng_base import BaseRNG

模块开头便是引入了winrandom和BaseRNG类,winradom是pyd文件,不可以查看源码,但是可以推测出是Windows提供的API 3,而BaseRNG提供了不同操作系统RNG都需要的RNG基类。
WindowsRNG由BaseRNG继承而来:

class WindowsRNG(BaseRNG):

    name = "<CryptGenRandom>"

    def __init__(self):
        self.__winrand = winrandom.new()
        BaseRNG.__init__(self)

    def flush(self):
        if self.closed:
            raise ValueError("I/O operation on closed file")
        data = self.__winrand.get_bytes(128*1024)
        assert (len(data) == 128*1024)
        BaseRNG.flush(self)

    def _close(self):
        self.__winrand = None

    def _read(self, N):
        self.flush()
        data = self.__winrand.get_bytes(N)
        self.flush()
        return data

  1. RobertD.Silverman, RSALaboratories, May17,1997.
  2. Miller-Rabin算法将合数误判为素数的概率是1/4,那么经过N轮检测可以将这个概率减小到1/2^(2n)
  3. 该API的详细内容参看 https://pypi.org/project/winrandom/
  • 7
    点赞
  • 46
    收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论 2

打赏作者

O_1CxH

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值