密码学专题 - 数学知识
2. 数论
这里仅列出一些对密码学有用的思想,关于数论更详细的知识请参考专业文献。
2.1 模运算
本质上,如果 对某些整数 成立,那么 。如果 为正, 为 0 ~ n,那么你可将 看做 被 整除后的余数。有时 叫做 模 的余数 (residue)。有时 叫做与 模 同余 (congruent) (三元等号 表示同余)。
从 的整数组成的集合构成了模 的完全剩余集 (complete set of residue)。这意味着,对于每一个整数 ,它的模 的余项是从 的某个数。
模 的运算给出了 的余数,余数是从 的某个整数,这种运算称为模变换 (modular reduction)。例如,。
模运算就像普通运算一样,它是可交换的、可结合的和可分配的。而且,简化每一个中间结果的模 运算,其作用与先进行全部运算再简化模 运算是一样的。
密码学用了许多模 运算,因为像计算离散对数和平方根这样的问题很困难,而模运算可将所有中间结果和最后结果限制在一个范围内,所以用它进行计算比较容易。对一个 位的模数 ,任何加、减、乘的中间结果将不会超过 位长。因此可以用模运算进行指数运算而又不会产生巨大的中间结果。虽然计算某数的乘方并对其取模的运算
将导致一系列的乘法和除法运算,但有加速运算的方法:一种方法指在最小化模乘法运算的数量;另一种旨在优化单个模乘法运算。因为操作步骤划分后,当完成一串乘法,并且每次都进行模运算后,指数运算就更快,这样就与一般取幂没有多大差别,但当用 200 位的数字进行运行时,情况就不同了。
例如,如果要计算 ,不要直接进行七次乘法和一个大数的模化简:
相反,应进行三次较小的乘法和三次较小的模化简:
以此类推,
当 不是 2 的幂次方时,计算 稍微要难些。可将 表示成 2 的幂次方之和:在二进制中,25 是 11001,因此 。故:
注意,上面的公式利用了,。
适当利用存储的中间结果,只需要 6 次乘法:
这种算法称为加法链 (addition chaining),或二进制平方和乘法方法。它用二进制表示了一个简单明了的加法链。算法的 C 语言描述如下:
unsigned long qe2(unsigned long x, unsigned long y, unsigned long n) {
unsigned long s, t, u;
int i;
s = 1; t = x; u = y;
while (u)
{
if (u&1) s = (s * t) % n;
u >>= 1;
t = (t * t) % n;
}
return s;
}
另一种递归算法为:
unsigned long fast_exp(unsigned long x, unsigned long y, unsigned long N) {
unsigned long tmp;
if (y == 1) return (x % N);
if ((y & 1) == 0) {
tmp = fast_exp(x, y/2, N);
return ((tmp * tmp) % N);
}
else {
tmp = fast_exp(x, (y-1) / 2, N);
tmp = (tmp * tmp) % N;
tmp = (tmp * x) % N;
return (tmp);
}
}
对应的 python 实现如下。
def qe2(x, y, n):
s = 1
t = x
u = y
while (u):
if (u&1):
s = (s * t) % n
u >>= 1
t = (t * t) % n
return s
另一种递归算法为:
def fast_exp(x, y, N):
if (y == 1):
return (x % N)
if ((y & 1) == 0):
tmp = fast_exp(x, y/2, N)
return ((tmp * tmp) % N)
else:
tmp = fast_exp(x, (y-1) / 2, N)
tmp = (tmp * tmp) % N
tmp = (tmp * x) % N
return (tmp)
如果用 表示数 中位数的长度,这项技术平均可减少 次操作。
2.2 整除性与素数
素数是这样一种数:比 1 大,其因子只有 1 和它本身,没有其他数可以整除它。2 是一个素数,其他的素数如 73、2521、2365347734399 和 等。素数是无限的。密码学,特别是公开密钥密码学常用大的素数 (512 位,甚至更大)。
如果 除以 余数为 0,则称 是 的一个因子 (记叙 ,读作 “a整除b”)。比如,7 是 35 的一个因子,记作 。如果一个数只有 1 和它自身两个正因子,我们就称这个数是素数。比如,13 是素数,两个因子为 1 和 13。最初几个素数很容易找到:2、3、5、7、11、13...... 如果一个整数大于 1 且不为素数,我们就称为合数。1 既不是素数也不是合数。
下面是关于整除性的一个简单的引理。
引理 1:如果 且 ,那么 。
证明:如果 ,那么存在整数 使得 (由 能被 整除可知 是 的倍数);如果 ,同样存在整数 使得 。综上可知,,所以 为 的一个因子。
引理 2:如果 为大于 1 的正整数且 为 除 1 之外最小的因子,那么 是素数。
证明:首先,我们必须保证 是被明确定义的。(如果对于某个 ,除 1 之外不存在一个最小的因子,那么 的定义就不恰当,引理 2 就毫无意义。) 由于 也是 的一个因子,而 ,所以 至少有一个大于 1 的因子,也必然有一个大于 1 的最小因子。
为证明 是素数,我们使用一种标准的数学技巧,称为反证法。为证明结论 X,反证法的一般思路是假设 X 不成立,接着从这个假设推出矛盾;如果假设 X 不成立能够推出矛盾,那么 X 必须是正确的。
在这个例子中,我们假设 不是素数,那么 肯定存在满足 的因子 。但是从引理 1 可知,如果 且 ,那么 ,即 也是 的一个因子且 。这样就产生了矛盾,因为 被定义为 除 1 之外最小的因子,因此我们的假设是错误的,从而 是素数。
定理 3 (殴几里得):素数有无穷多个。
证明:我们仍然使用反证法来证明。假设素数的个数是有限的,那么一个包含所有素数的列表也是有限的,记为 ,这里 表示素数的个数。定义 ,即 为所有素数的乘积加上 1。
考虑 除 1 之外的最小因子,我们仍用 来表示这个因子。由引理 2 可知, 为素数且 ;但是在那个有限的素数列表中,没有一个素数是 的因子,因为它们都是 的因子, 除以列表中任何一个素数 都会有余数 1,所以 为素数且不在列表中。而列表在定义时就包含了所有素数的,这样就出现了矛盾,所以素数的个数是有限的这个假设是错误的,从而可知素数有无穷多个。
2.3 最大公因子
两个数互素 (relatively prime) 是指:当它们除了 1 外没有共同的因子。换句话说,如果 和 的最大公因子 (greatest common divisor) 等于 1,那么可写作:
数 15 和 28 是互素的,15 和 27 不是,而 13 和 500 是。一个素数与它的倍数以外的任何其他数都是互素的。
计算两个数的最大公因子最容易的方法是用殴几里得算法 (Euclid's algorithm)。殴几里德在公元前 300 年所写的《Elements》中描述了这个算法。这个算法并非由他发明,历史学家相信这个算法在当时已有 200 年历史。它是幸存到现在最古老的非凡的算法,至今它仍是完好的。
算法的 C 语言描述如下:
/* returns gcd of x and y */
int gcd(int x, int y) {
int g;
if (x < 0)
x = -x;
if (y < 0)
y = -y;
if (x + y == 0)
exit(1);
g = y;
while (x > 0)
{
g = x;
x = y % x;
y = g;
}
return g;
}
这个算法可以推广为返回由 m 个数组成的 gcd 数组。
/* return the gcd of x1, x2, ..., xm */
int multiple_gcd(int m, int *x) {
size_t i;
int g;
if (m < 1)
return 0;
g = x[0];
for (i = 1; i < m; ++i) {
g = gcd(g, x[i]);
/* optimization, since for random x(i), g==1 60% of the time: */
if (g == 1)
return 1;
}
return g;
}
对应的 python 实现如下。
# returns gcd of x and y
def gcd(x, y):
if (x < 0):
x = -x
if (y < 0):
y = -y
if (x + y == 0):
exit(1)
g = y
while (x > 0):
g = x
x = y % x
y = g
return g
这个算法可以推广为返回由 m 个数组成的 gcd 数组。
# return the gcd of x1, x2, ..., xm
def multiple_gcd(m, x):
if (m < 1):
return 0
g = x[0]
for i in range(m):
g = gcd(g, x[i])
# optimization, since for random x(i), g==1 60% of the time:
if (g == 1):
return 1
return g
2.4 殴几里得算法
求最大公因子 (GCD) 的算法。
2.5 求模逆元
记得逆元 (inverse) 吗?4 的乘法逆元是 1/4,因为 。在模运算的领域,这个问题更复杂:
这个方程等价于寻找一组 和 ,以使:
这里 和 均为整数。
更为一般的问题是寻找一个 ,使得:
也可写作:
解决模的逆元问题很困难。有时候有一个方案,有时候没有。例如,5 模 14 的逆元是 3:。2 模 14 却没有逆元。
一般而论,如果 和 是互素的,那么 有唯一解;如果 和 不是互素的,那么 没有解。如果 是素数,那么从 的每一个数与 都是互素的,且在这个范围内恰好有一个逆元。
一切顺利。现在,怎样找出 模 的逆元呢?有一系列的方法。殴几里得算法也能计算 模 的逆元,有时候这叫做扩展殴几里得算法 (extended Euclidean algorithm)。
下面是用 C++ 写的算法:
#include <stdlib.h>
#include <iostream>
using namespace std;
#define isEven(x) ((x & 0x01) == 0)
#define isOdd(x) (x & 0x01)
#define swap(x,y) (x ^= y, y ^= x, x ^= y)
void ExtBinEuclid(int *u, int *v, int *u1, int *u2, int *u3) {
// warning: u and v will be swapped if u < v
int k, t1, t2, t3;
if (*u < *v) swap(*u, *v);
for (k = 0; isEven(*u) && isEven(*v); ++k) {
*u >>= 1; *v >>= 1;
}
*u1 = 1; *u2 = 0; *u3 = *u; t1 = *v; t2 = *u - 1; t3 = *v;
do {
do {
if (isEven(*u3)) {
if (isOdd(*u1) || isOdd(*u2)) {
*u1 += *v; *u2 += *u;
}
*u1 >>= 1; *u2 >>= 1; *u3 >>= 1;
}
if (isEven(t3) || *u3 < t3) {
swap(*u1, t1); swap(*u2, t2); swap(*u3, t3);
}
} while (isEven(*u3));
while (*u1 < t1 || *u2 < t2) {
*u1 += *v; *u2 += *u;
}
*u1 -= t1; *u2 -= t2; *u3 -= t3;
} while (t3 > 0);
while (*u1 >= *v && *u2 >= *u) {
*u1 -= *v; *u2 -= *u;
}
*u1 <<= k; *u2 <<= k; *u3 <<= k;
}
int main(int argc, char **argv) {
int a, b, gcd;
if (argc < 3) {
std::cerr << "Usage: xeuclid u v" << std::endl;
return -1;
}
int u = atoi(argv[1]);
int v = atoi(argv[2]);
if (u <= 0 || v <= 0) {
std::cerr << "Arguments must be positive!" << std::endl;
return -2;
}
// warning: u and v will be swapped if u < v
ExtBinEuclid(&u, &v, &a, &b, &gcd);
std::cout << a << " * " << u << " + (-"
<< b << ") * " << v << " = " << gcd << std::endl;
if (gcd == 1)
std::cout << "the inverse of " << v << " mod " << u << " is: " << u - b << std::endl;
return 0;
}
此算法通过迭代运算来实现,对于大的整数,其运行可能较慢。Knuth 指出这个算法完成的除法的平均数目是
2.6 求系数
殴几里得算法可用于解决下面的一类问题:给出一个包含 个变量 的数组,求一个包含 个系数 的数组,使得
2.7 费马小定理
如果 是一个素数,且 不是 的倍数,那么,根据费马小定理 (Fermat's little theorem) 有:
2.8 欧拉函数
还有另一种方法计算模 的逆元,但不是在任何情况下都能使用。模 的余数化简集 (reduced set of residues) 是余数完全集合的子集,与 互素。例如,模 12 的余数化简集是 。如果 是素数,那么模 的余数化简集是从 的所有整数集合。对 不等于 1 的数,数 0 不是余数化简集的元素。
欧拉函数 (Euler totient fuction),也称为欧拉 函数,写作 ,它表示模 的余数化简集中元素的数目。换句话说, 表示与 互素的小于 的正整数的数目 ()。
如果 是素数,那么 ;如果 ,且 和 互素,那么 。这些数字在随后谈到的公开密钥系统中将再次出现,它们都来自于此。
根据费马小定理的欧拉推广,如果 ,那么
现在计算 模 很容易:
现在计算 模 很容易:
证明:
例如,求 5 模 7 的逆元是多少?既然 7 是素数,。因此,5 模 7 的逆元是
计算逆元的两种方法都推广到在一般性的问题中求解 (如果 ):
用欧拉推广公式,解:
用殴几里得算法,解:
通常,殴几里得算法在计算逆元方面比欧拉推广更快,特别是对于 500 位范围内的数。如果 ,并非一切都没用了。这种一般情况而言,,可能有多个解或无解。
2.9 中国剩余定理
如果已知 的素因子,那么就能利用中国剩余定理 (Chinese remainder theorem) 求解整个方程组,这个定理的最初形式是由 1 世纪的中国数学家孙子发现的。
一般而言,如果 的素因子可分解为 ,那么方程组
有唯一解,这里 (注意,有些素数可能不止一次地出现。例如,p_1 可能等于 p_2)。换句话说,一个数 (小于一些素数之积) 被它的余数模这些素数唯一确定。
例如,取素数 3 和 5,取一个数 14,那么 。则小于 且具有上述余数的数只有 14,即由这两个余数唯一地确定了数 14。
如果对任意 和 ( 和 都是素数),那么,当 时,存在一个唯一的 ,使得
为求出这个 ,首先用殴几里得算法找到 ,使得
然后计算:
用 C 语言所写的中国剩余定理如下:
/* r is the number of elements in arrays m and u;
m is the array of (pairwise relatively prime) moduli
u is the array of coefficients
return value is n such than n == u[k]%m[k] (k=0..r-1) and
n < m[0]*m[1]*...*m[r-1]
*/
/* totient() is left as an exercise to the reader. */
int chinese_remainder(size_t r, int *m, int *u) {
size_t i;
int modulus;
int n;
modulus = 1;
for (i = 0; i < r; ++i)
modulus *= m[i];
n = 0;
for (i = 0; i < r; ++i) {
n += u[i] * modexp(modulus / m[i], totient(m[i]), m[i]);
n %= modulus;
}
return n;
}
中国剩余定理的一个推论可用于求出一个类似问题的解:如果 和 都是素数,且 ,那么存在一个唯一的 ,使得
如果 ,那么
如果 ,那么
2.10 二次剩余
如果 是素数,且 ,如果
那么称 是对模 的二次剩余 (quadratic residue)。
不是所有的 的值都满足这个特性。如果 是对模 的一个二次剩余,那么它必定是对模 的所有素因子的二次剩余。例如,如果 ,那么二次剩余是 1、2 和 4:
注意,每一个二次剩余在上面都出现了两次。
没有 值可满足下列这些方程的任意一个:
对模 7 的二次非剩余 (quadratic nonresidue) 是 3、5 和 6。
很容易证明,当 为奇数时,对模 的二次剩余数目恰好是 ,且与其二次非剩余的数目相同。而且,如果 等于二次剩余模 ,那么 恰好有两个平方根:其中一个在 之间;另一个在 之间。这两个平方根中的一个也是模 的二次剩余,称为主平方根 (pricipal square root)。
如果 是两个素数 和 之积,那么模 恰好有 个二次剩余。模 的一个二次剩余是模 的一个完全平方。这是因为要成为模 的平方,其余数必须有模 的平方和模 的平方。例如,模 35 有 11 个二次剩余:1、4、9、11、14、15、16、21、25、29、30。每一个二次剩余恰好有 4 个平方根。
3. 有限域上的离散对数
模指数运算是频繁地用于密码学中的另一种单向函数。计算下面的表达式很容易:
模指数运算的逆问题是找出一个数的离散对数,这是一个难题:
例如:
不是所有的离散对数都有解 (记住,只有整数才是合法的解)。发现下面的方程没有解 很容易:
对 1024 位的数求离散对数更加困难。
3.1 计算有限群中的离散对数
密码设计者对下面三个主要群的离散对数很感兴趣:
- 素数域的乘法群:。
- 特征为 2 的有限域上的乘法群:。
- 有限域 上的椭圆曲线群:。
许多公开密钥算法的安全性是基于寻找离散对数的,因此对这个问题进行了广泛的研究。
项目源代码
项目源代码会逐步上传到 Github,地址为 https://github.com/windstamp。
Contributor
- Windstamp, https://github.com/windstamp