29、密码学中的一次性密码本与素数探索

密码学中的一次性密码本与素数探索

1. 一次性密码本与维吉尼亚密码

一次性密码本在加密时,有时会产生与维吉尼亚密码相同的密文。当对比维吉尼亚密码(密文如 ZKHQXNAGKCGMOAJMAAXR)和一次性密码本(密文同样为 ZKHQXNAGKCGMOAJMAAXR)的密文时,会发现它们完全一致。这意味着可以使用破解维吉尼亚密码的相同技术来破解二次使用的一次性密码本。

一次性密码本是一种让维吉尼亚密码加密变得难以破解的方法,它要求密钥满足三个条件:与消息长度相同、真正随机且仅使用一次。当这三个条件都满足时,一次性密码本就无法被破解。然而,由于使用起来非常不便,它并不适用于日常加密。通常,一次性密码本会以列表形式存在并通过人工分发,并且要确保这个列表不会落入坏人之手。

这里有几个相关的问题供思考:
1. 为什么没有介绍一次性密码本程序?
2. 二次使用的一次性密码本相当于哪种密码?
3. 使用长度为明文消息两倍的密钥,会让一次性密码本的安全性提升两倍吗?

2. 素数的重要性与背景

过去的经典密码已经存在了数百年,在黑客只能依靠纸笔的时代,这些密码效果良好。但如今,计算机处理数据的速度比人类快数万亿倍,这些经典密码变得更加脆弱。而且,它们在加密和解密时使用相同的密钥,这在发送加密消息时会带来问题,比如如何安全地传输解密所需的密钥。

为了解决这些问题,公钥密码应运而生。它利用非常大的素数来创建两个密钥:用于加密的公钥和用于解密的私钥。为了生成公钥密码所需的素数,需要了解素数的一些特性以及分解大数字的难度。

3. 素数的定义与特性

素数是大于 1 且只有两个因数(1 和它本身)的整数。例如,3 和 7 是 21 的因数,12 的因数有 2 和 6、3 和 4。每个数都有 1 和它本身作为因数,因为 1 乘以任何数都等于该数。如果一个数除了 1 和它本身没有其他因数,那么这个数就是素数,比如 2 只有 1 和 2 作为因数,所以 2 是素数。

以下是一个简短的素数列表(注意 1 不被视为素数):2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281 等等。

素数有无穷多个,不存在最大的素数,它们会像普通数字一样不断增大。公钥密码使用大素数来使密钥变得非常大,从而难以通过暴力破解。

素数的一些有趣特性:
- 因为所有偶数都是 2 的倍数,所以 2 是唯一可能的偶素数。
- 两个素数相乘得到的数,其因数只有 1、它本身以及这两个相乘的素数。例如,3 和 7 相乘得到 21,21 的因数只有 1、21、3 和 7。

不是素数的整数被称为合数,每个合数都有唯一的素因数分解。例如,合数 1386 可以分解为 2 × 3 × 3 × 7 × 11。

4. 素数模块的创建

为了快速判断一个数是否为素数并生成素数,我们可以创建一个名为 primeNum.py 的模块。这个模块会用到 Python 的 math random 模块。以下是 primeNum.py 的代码:

# Prime Number Sieve
# https://www.nostarch.com/crackingcodes/ (BSD Licensed)

import math, random

def isPrimeTrialDiv(num):
    # Returns True if num is a prime number, otherwise False.
    # Uses the trial division algorithm for testing primality.
    # All numbers less than 2 are not prime:
    if num < 2:
        return False
    # See if num is divisible by any number up to the square root of num:
    for i in range(2, int(math.sqrt(num)) + 1):
        if num % i == 0:
            return False
    return True

def primeSieve(sieveSize):
    # Returns a list of prime numbers calculated using
    # the Sieve of Eratosthenes algorithm.
    sieve = [True] * sieveSize
    sieve[0] = False # Zero and one are not prime numbers.
    sieve[1] = False
    # Create the sieve:
    for i in range(2, int(math.sqrt(sieveSize)) + 1):
        pointer = i * 2
        while pointer < sieveSize:
            sieve[pointer] = False
            pointer += i
    # Compile the list of primes:
    primes = []
    for i in range(sieveSize):
        if sieve[i] == True:
            primes.append(i)
    return primes

def rabinMiller(num):
    # Returns True if num is a prime number.
    if num % 2 == 0 or num < 2:
        return False # Rabin-Miller doesn't work on even integers.
    if num == 3:
        return True
    s = num - 1
    t = 0
    while s % 2 == 0:
        # Keep halving s until it is odd (and use t
        # to count how many times we halve s):
        s = s // 2
        t += 1
    for trials in range(5): # Try to falsify num's primality 5 times.
        a = random.randrange(2, num - 1)
        v = pow(a, s, num)
        if v != 1: # This test does not apply if v is 1.
            i = 0
            while v != (num - 1):
                if i == t - 1:
                    return False
                else:
                    i = i + 1
                    v = (v ** 2) % num
    return True

# Most of the time we can quickly determine if num is not prime
# by dividing by the first few dozen prime numbers. This is quicker
# than rabinMiller() but does not detect all composites.
LOW_PRIMES = primeSieve(100)

def isPrime(num):
    # Return True if num is a prime number. This function does a quicker
    # prime number check before calling rabinMiller().
    if (num < 2):
        return False # 0, 1, and negative numbers are not prime.
    # See if any of the low prime numbers can divide num:
    for prime in LOW_PRIMES:
        if (num % prime == 0):
            return False
    # If all else fails, call rabinMiller() to determine if num is prime:
    return rabinMiller(num)

def generateLargePrime(keysize=1024):
    # Return a random prime number that is keysize bits in size:
    while True:
        num = random.randrange(2**(keysize-1), 2**(keysize))
        if isPrime(num):
            return num
5. 素数模块的使用示例

导入 primeNum.py 模块后,我们可以使用 generateLargePrime() 函数生成非常大的素数,也可以使用 isPrime() 函数判断任何数(无论大小)是否为素数。以下是一些示例:

>>> import primeNum
>>> primeNum.generateLargePrime()
122881168342211041030523683515443239007484290600701555369488271748378054744009
463751312511471291011945732413378446666809140502037003673211052153493607681619
990563076859566835016382556518967124921538212397036345815983641146000671635019
637218348455544435908428400192565849620509600312468757953899553441648428119
>>> primeNum.isPrime(45943208739848451)
False
>>> primeNum.isPrime(13)
True
6. 试除法算法的工作原理

试除法算法用于判断一个给定的数是否为素数。该算法会不断用整数(从 2 开始)去除这个数,看是否能整除(余数为 0)。例如,要判断 49 是否为素数,可以从 2 开始尝试:
- 49 ÷ 2 = 24 余 1
- 49 ÷ 3 = 16 余 1
- 49 ÷ 4 = 12 余 1
- …
- 49 ÷ 7 = 7 余 0

因为 7 能整除 49,所以 49 不是素数。为了加快这个过程,可以只除以素数,而不是合数。因为合数是素数的组合,如果 2 不能整除 49,那么像 6(因数包含 2)这样的合数也不能整除 49。

判断一个数是否为素数时,只需要测试到该数的平方根即可。例如,13 的平方根约为 3.6,所以只需要除以 2 和 3 就能判断 13 是素数。

在 Python 中,可以使用 math.sqrt() 函数来计算一个数的平方根,示例如下:

>>> import math
>>> 5 * 5
25
>>> math.sqrt(25)
5.0
>>> math.sqrt(10)
3.1622776601683795
7. 试除法算法的实现

primeNum.py 中的 isPrimeTrialDiv() 函数使用试除法算法来判断一个数是否为素数。代码如下:

def isPrimeTrialDiv(num):
    # Returns True if num is a prime number, otherwise False.
    # Uses the trial division algorithm for testing primality.
    # All numbers less than 2 are not prime:
    if num < 2:
        return False
    # See if num is divisible by any number up to the square root of num:
    for i in range(2, int(math.sqrt(num)) + 1):
        if num % i == 0:
            return False
    return True

该函数首先检查 num 是否小于 2,如果是则返回 False ,因为小于 2 的数不是素数。然后,使用 for 循环从 2 到 num 的平方根进行遍历,如果 num 能被其中任何一个数整除,则返回 False ,否则返回 True

8. 埃拉托斯特尼筛法

埃拉托斯特尼筛法是一种用于找出一定范围内所有素数的算法。以 1 到 50 的数为例,其工作流程如下:
1. 先将所有数标记为素数。
2. 标记 1 为“非素数”。
3. 标记所有 2 的倍数(除 2 本身)为“非素数”,即 4、6、8 等。
4. 对 3 的倍数(除 3 本身)重复上述操作,标记为“非素数”,如 6、9、12 等。
5. 依次对 4、5 等数的倍数进行同样操作,直到达到平方根小于等于该范围上限的数(对于 50,平方根约为 7.071,所以到 8 为止)。

最终,剩下的未被标记的数就是素数。使用埃拉托斯特尼筛法,我们可以找出小于 50 的素数为 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47。

这个筛法算法适用于快速找出一定范围内的所有素数,比逐个使用试除法判断每个数是否为素数要快得多。

9. 埃拉托斯特尼筛法的实现

primeNum.py 中的 primeSieve() 函数使用埃拉托斯特尼筛法来返回 1 到 sieveSize 之间的所有素数列表:

def primeSieve(sieveSize):
    # Returns a list of prime numbers calculated using
    # the Sieve of Eratosthenes algorithm.
    sieve = [True] * sieveSize
    sieve[0] = False # Zero and one are not prime numbers.
    sieve[1] = False
    # Create the sieve:
    for i in range(2, int(math.sqrt(sieveSize)) + 1):
        pointer = i * 2
        while pointer < sieveSize:
            sieve[pointer] = False
            pointer += i
    # Compile the list of primes:
    primes = []
    for i in range(sieveSize):
        if sieve[i] == True:
            primes.append(i)
    return primes

该函数首先创建一个长度为 sieveSize 的布尔列表 sieve ,并将 0 和 1 标记为 False (因为它们不是素数)。然后,通过 for 循环和 while 循环将合数标记为 False 。最后,遍历 sieve 列表,将标记为 True 的数添加到 primes 列表中并返回。

10. 拉宾 - 米勒素性测试算法

拉宾 - 米勒算法是一种相对简单的素性测试方法,在普通计算机上运行只需要几秒钟。虽然它的 Python 代码只有几行,但解释其背后的数学原理会比较复杂。该算法不是一种绝对准确的素性测试方法,它能找出很可能是素数的数,但不能保证一定是素数。不过,误判为素数的概率很小,对于我们的目的来说已经足够。

primeNum.py 中的 rabinMiller() 函数实现了这个算法:

def rabinMiller(num):
    # Returns True if num is a prime number.
    if num % 2 == 0 or num < 2:
        return False # Rabin-Miller doesn't work on even integers.
    if num == 3:
        return True
    s = num - 1
    t = 0
    while s % 2 == 0:
        # Keep halving s until it is odd (and use t
        # to count how many times we halve s):
        s = s // 2
        t += 1
    for trials in range(5): # Try to falsify num's primality 5 times.
        a = random.randrange(2, num - 1)
        v = pow(a, s, num)
        if v != 1: # This test does not apply if v is 1.
            i = 0
            while v != (num - 1):
                if i == t - 1:
                    return False
                else:
                    i = i + 1
                    v = (v ** 2) % num
    return True

该函数首先排除偶数和小于 2 的数,对于 3 直接返回 True 。然后,通过循环将 s 不断除以 2 直到为奇数,并记录除的次数 t 。接着,进行 5 次测试,随机选择一个数 a 进行计算,如果不满足特定条件则返回 False ,否则返回 True

综上所述,我们介绍了一次性密码本的相关知识,以及素数在密码学中的重要性,还详细讲解了判断素数的几种算法和相应的 Python 实现。这些知识和代码对于理解和实现密码学中的公钥密码非常有帮助。

密码学中的一次性密码本与素数探索

11. 素数判断的优化策略

在判断一个数是否为素数时,我们可以采用一些优化策略,以提高效率。 primeNum.py 模块中的 isPrime() 函数就运用了这样的优化思路。

# Most of the time we can quickly determine if num is not prime
# by dividing by the first few dozen prime numbers. This is quicker
# than rabinMiller() but does not detect all composites.
LOW_PRIMES = primeSieve(100)

def isPrime(num):
    # Return True if num is a prime number. This function does a quicker
    # prime number check before calling rabinMiller().
    if (num < 2):
        return False # 0, 1, and negative numbers are not prime.
    # See if any of the low prime numbers can divide num:
    for prime in LOW_PRIMES:
        if (num % prime == 0):
            return False
    # If all else fails, call rabinMiller() to determine if num is prime:
    return rabinMiller(num)

该函数的工作流程如下:
1. 首先,判断 num 是否小于 2,如果是,则直接返回 False ,因为 0、1 和负数都不是素数。
2. 接着,使用预先计算好的前 100 个素数(存储在 LOW_PRIMES 列表中)来尝试整除 num 。如果 num 能被其中任何一个素数整除,那么它就不是素数,函数返回 False 。这种方法通常比直接调用 rabinMiller() 函数更快,因为只需要进行少量的除法运算。
3. 如果以上步骤都没有判断出 num 不是素数,那么再调用 rabinMiller() 函数进行更精确的素性测试。

通过这种先快速筛选,再精确测试的方式,可以在大多数情况下提高素数判断的效率。

12. 大素数的生成

在密码学中,公钥密码需要使用非常大的素数作为密钥。 primeNum.py 模块中的 generateLargePrime() 函数可以帮助我们生成指定位数的大素数。

def generateLargePrime(keysize=1024):
    # Return a random prime number that is keysize bits in size:
    while True:
        num = random.randrange(2**(keysize-1), 2**(keysize))
        if isPrime(num):
            return num

该函数的工作流程如下:
1. 接受一个可选参数 keysize ,表示要生成的素数的位数,默认值为 1024 位。
2. 使用 random.randrange() 函数在 2**(keysize - 1) 2**keysize 之间随机选择一个数 num
3. 调用 isPrime() 函数判断 num 是否为素数。如果是,则返回该素数;如果不是,则继续随机选择另一个数,直到找到一个素数为止。

以下是一个使用示例:

>>> import primeNum
>>> primeNum.generateLargePrime()
122881168342211041030523683515443239007484290600701555369488271748378054744009
463751312511471291011945732413378446666809140502037003673211052153493607681619
990563076859566835016382556518967124921538212397036345815983641146000671635019
637218348455544435908428400192565849620509600312468757953899553441648428119
13. 不同素数判断方法的对比

为了更直观地了解不同素数判断方法的特点,我们可以对试除法、埃拉托斯特尼筛法和拉宾 - 米勒算法进行对比,如下表所示:

算法名称 适用范围 时间复杂度 准确性 特点
试除法 较小的数 $O(\sqrt{n})$ 准确 简单直接,但对于大数字效率低
埃拉托斯特尼筛法 一定范围内的数 $O(n log log n)$ 准确 可以快速找出一定范围内的所有素数
拉宾 - 米勒算法 大数字 近似常数时间 大概率准确 速度快,但有极小概率误判

从表中可以看出,不同的算法适用于不同的场景。试除法适合判断较小的数是否为素数;埃拉托斯特尼筛法适合找出一定范围内的所有素数;而拉宾 - 米勒算法则在处理大数字时表现出色,虽然有一定的误判概率,但在密码学中是可以接受的。

14. 素数判断算法的流程图

下面是一个使用 mermaid 语法绘制的素数判断算法的流程图,展示了 isPrime() 函数的工作流程:

graph TD;
    A[开始] --> B{num < 2};
    B -- 是 --> C[返回 False];
    B -- 否 --> D{num 能被 LOW_PRIMES 中的素数整除};
    D -- 是 --> C;
    D -- 否 --> E{rabinMiller(num) 为真};
    E -- 是 --> F[返回 True];
    E -- 否 --> C;

这个流程图清晰地展示了 isPrime() 函数先进行快速筛选,再进行精确测试的过程。

15. 总结

本文围绕密码学中的一次性密码本和素数展开了详细的介绍。一次性密码本在满足特定条件时具有很高的安全性,但由于使用不便,不适合日常加密。而素数在公钥密码中扮演着重要角色,为了生成和判断素数,我们介绍了多种算法:
1. 试除法 :通过不断用整数去除待判断的数,适用于判断较小的数是否为素数。
2. 埃拉托斯特尼筛法 :可以快速找出一定范围内的所有素数,效率较高。
3. 拉宾 - 米勒算法 :适合处理大数字,虽然有一定的误判概率,但在密码学中是可行的。

同时,我们还实现了 primeNum.py 模块,包含了这些算法的具体实现,并提供了使用示例。通过这些算法和模块,我们可以更方便地进行素数的生成和判断,为密码学的应用提供了有力的支持。

希望本文能帮助你更好地理解一次性密码本和素数在密码学中的应用,以及相关算法的原理和实现。如果你对这些内容有更深入的需求,可以进一步探索相关的知识和代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值