加密算法-综述
加密算法分为对称加密算法和非对称加密算法。
对称加密算法:加密和解密使用同一个密钥。
→ 常见的对称加密算法有AES、DES、Blowfish等。
非对称加密算法:存在密钥对(公钥和私钥)。当使用公钥加密时,只能使用私钥解密;当使用私钥加密时,只能使用公钥解密。
→ 常见的非对称加密算法有RSA。
一、 非对称加密算法
1. RSA加密算法
1977年,三位数学家Rivest、Shamir和Adleman设计了一种算法,可以实现非对称加密,因此命名为RSA算法,是当前使用最广的一种非对称加密算法。
RSA算法基于一个简单的数论事实:将两个大质数相乘十分容易,但是想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密的密钥。
1.1 预备知识
1.1.1 欧拉定理
对于正整数n,欧拉函数φ(n)表示小于等于n,且与n互质的正整数的个数。
1.2 RSA算法
1.2.1 公钥和私钥的生成
1)取两个素数p和q,其二进制位能够达到1024个二进制位或者更多(位数越多越难破解);
2)计算素数p和q的乘积:n=pq;
3)计算n的欧拉函数φ(n):φ(n)=(p-1)(q-1);
4)找到一个整数e(1<e<φ(n)),满足gcd(m, e) = 1(即e和φ(n)互素);
5)计算e在模φ(n)域上的逆元d(即ed = 1 (mod φ(n)));
6)至此,公钥和私钥生成完毕:(n, e)为公钥,(n, d)为私钥
1.2.2 RSA加密
对于明文x,用公钥(n,e)对x加密的过程,就是将x转换为数字(字符串的话取其ASCII码或者unicode值),然后通过幂取模计算出y,其中y就是密文:
- y = xe mod n
1.2.3 RSA解密
对于密文y,用私钥(n,d)对y进行解密的过程和加密类似,同样是计算幂取模,得到明文x:
- x = yd mod n
1.3 C语言实现
1.3.1 快速幂取模算法
目标:计算(a^b) mod c
预备知识:
1)(a1 × a2) mod c = [(a1 mod c)×(a2 mod c)] mod c
2)(a1 × a2 × a3) mod c = [(a1 mod c)×(a2 mod c)×(a3 mod c)] mod c
3)幂次b可以用二进制位表示,从而将a^b表示为a^bn × a^bn-1 ...... b^64 × b^32 × b^16 × b^8 × b^4 × b^2 × b^1
4)幂次取模的迭代计算:
d0 = (a^1) mod c;
d1 = (a^2) mod c = (d0^2) mod c;
d2 = (a^4) mod c = (d1^2) mod c;
d3 = (a^8) mod c = (d2^2) mod c;
d4 = (a^16) mod c = (d3^2) mod c;
......
dn = ...... = (dn-1^2) mod c.
★★举例:(5^109) mod 9★★
109用二进制数表示为:110 1101
5^109 = 5^64 × 5^32 × 5^8 × 5^4 × 5^1
res0 = 1; d0 = 5;
res1 = (5^1) mod 9 = d0 mod 9 = 5; d1 = (d0^2) mod 9 = 7;
res2 = [res1 × [0*(5^2)] mod 9] mod 9 = res1 = 5; d2 = (d1^2) mod 9 = 4;
res3 = [res2 × ((5^4) mod 9)] mod 9 = (res2 × d2) mod 9 = 2; d3 = (d2^2) mod 9 = 7;
res4 = [res3 × ((5^8) mod 9)] mod 9 = (res3 × d3) mod 9 = 5; d4 = (d3^2) mod 9 = 4;
res5 = [res4 × [0*(5^16)] mod 9] mod 9 = res4 = 5; d5 = (d4^2) mod 9 = 7;
res6 = [res5 × ((5^32) mod 9)] mod 9 = (res5 × d5) mod 9 = 8; d6 = (d5^2) mod 9 = 4;
res7 = [res6 × ((5^64) mod 9)] mod 9 = (res6 × d6) mod 9 = 5; d7 = (d6^2) mod 9 = 7;
输出:(5^109) mod 9 = res7 = 5
1 /** 2 * @brief 快速幂取模算法(Exponentiation by Squaring) 3 * @detail 计算 (a^b) mod c 的结果。 4 * 核心思想:将指数 b 拆解为二进制形式,仅对二进制位为1的项进行累乘, 5 * 将 O(b) 的时间复杂度降低为 O(log2 b)。 6 * @param a 底数(当前版本实际为32位,存在溢出风险) 7 * @param b 指数(使用 long long 确保大指数处理) 8 * @param c 模数(int 类型,结果范围限制在 0 ~ c-1) 9 * @return int 计算结果 (a^b mod c) 的余数 10 */ 11 int modpow(long long a, long long b, int c) 12 { 13 int res = 1; //存储中间结果 14 15 while(b>0) 16 { 17 /*第1步:判断当前最低位是否为1。若为1,则说明当前轮次的底数幂(a^(2^i))需要乘入最终结果*/ 18 //必须使用长乘法,否则将会导致值的溢出 19 if(b & 1) 20 { 21 res = (res * a) % c; //乘入当前底数贡献,并立即取模压缩至<c 22 } 23 /*第2步:右移1位,准备处理下一个二进制位*/ 24 b = b >> 1; 25 /*第3步:底数平方取模(为下一轮循环做准备)*/ 26 a = (a * a) % c; 27 } 28 return res; 29 }
1.3.2 雅可比算法
雅可比算法用于计算雅可比符号
1) 二次剩余
在模7得世界中,只有数字0, 1, 2, 3, 4, 5, 6
把每个数平方,得到的平方数取模7(即 x2 mod 7),从而得到的数只有1、2、4,这三个数就叫“模7的二次剩余”,而3、5、6就叫非二次剩余。
因此,判断一个数是否为模n的二次剩余,就看它能不能开平方
2) 欧拉判别法
(a/n) ≡ a(n-1)/2 mod n
等式左边是勒让德符号,它的值表示a是否为模n的二次剩余,只有三种值:1(是二次剩余)、-1(是二次非剩余)、0(a和n不互质)。需要通过开平方求解。
等式右边将左边判断“能否开平方”这件事,从“把1到(p-1)挨个平方试一遍”,变成了算一次模幂。a^((n-1)/2) mod n = [a mod n]^((n-1)/2)
3)二次互反律
如果m和n是两个互素的奇正整数,那么有:(m/n)(n/m) = (-1)((m-1)/2)·((n-1)/2)
通过该二次互反律,我们可以将雅可比符号中的分子和分母进行翻转,而代价仅仅是引入一个由m和n模4的余数决定的简单符号。具体来说,仅当m和n形如4k+3(也即m mod 4和n mod 4 的余数均为3)时,这个符号因子才为-1;在其他情况(即m mod 4和n mod 4 的余数中至少有一个不为3)时,这个符号因子都为1.
4)补充定律
-
- 第一补充定律----关于(-1/n)
(-1/n) = (-1)(n-1)/2
这个公式表明,(-1/n)的值完全取决于n模4的余数。如果n ≡ 1 (mod 4),它就是1;如果n ≡ 3 (mod 4),它就是-1.
-
- 第二补充定律----关于(2/n):
(2/n) = (-1)(n^2-1)/8
这个公式表明,(2/n) 的值完全取决于n模8的余数。如果n ≡ 1, 7 (mod 8),它就是1;如果n ≡ 3, 5 (mod 8),它的值为-1.
5)雅可比符号的计算逻辑--二次互反律
- 化简分子:首先,利用雅可比符号的周期性,将(an)中的 分子
a对分母n取模,即a(modn),使得分子小于分母。 - 提取因子"2":如果分子是偶数,将分子中的所有因子
2提出来。因为我们知道(2n)的值由n(mod8)决定,所以这一步很容易。 - 互反翻转:现在分子是一个奇数。此时,二次互反律允许我们将符号“翻转”为(na),代价仅仅是乘上一个由a和n模4的余数决定的符号(1或−1)。
- 重复以上步骤:翻转之后,我们得到了一个新的雅可比符号,其分母变得更小了。我们可以对这个新符号重复上述过程。
1 /** 2 * @brief 计算雅可比符号 (a/n) 3 * 4 * 雅可比符号定义:对于正奇数 n,返回值为 -1、0 或 1。 5 * 0 :a 与 n 不互质(即 gcd(a, n) > 1) 6 * 1 :a 可能是模 n 的二次剩余(当 n 为素数时,一定是) 7 * -1 :a 一定不是模 n 的二次剩余 8 * 9 * 本函数使用二次互反律和欧几里得算法进行快速计算,时间复杂度 O(log n)。 10 * 11 * @param a 被判断的整数(分子) 12 * @param n 正奇数(分母) 13 * @return int 雅可比符号 (a/n),返回 -1、0 或 1 14 * 15 * @note 当前实现未处理 a 为负数的情况,若 a < 0,a % n 可能为负, 16 * 会导致后续逻辑错误。实际使用时应先对 a 取模并规范化为非负数。 17 * 另外,函数假定 n 为正奇数,若 n 为偶数或非正数,结果无定义。 18 */ 19 int jacobi(int a, int n) 20 { 21 int twos, temp; // twos 记录提取出的因子 2 的个数;temp 用于交换 22 int mult = 1; // 累积符号因子,初始为 1,最终作为返回值之一 23 24 // 主循环:当 a 大于 1 且 a 不等于 n 时继续处理 25 while (a > 1 && a != n) { 26 // 利用雅可比符号的周期性:将 a 化简为 a mod n 27 // 因此 (a/n) = (a mod n / n) 28 a = a % n; 29 30 // step1. 化简分子。如果化简后 a <= 1 或 a == n,则跳出循环,进入最终判断 31 if (a <= 1 || a == n) break; 32 33 // step2. 提取因子"2"。提取 a 中的所有因子 2,并统计个数 34 twos = 0; 35 // 当 a 是偶数时,将其除以 2,并递增 twos 36 // 注意:++twos 作为循环条件的一部分,只要 a 是偶数就继续循环 37 while (a % 2 == 0 && ++twos) a /= 2; 38 39 // 根据提取出的因子 2 的个数调整符号 40 // 公式:当 n ≡ ±1 (mod 8),即 n%8 == 1 或 7,(2/n) = 1 41 // 当 n ≡ ±3 (mod 8),即 n%8 == 3 或 5,(2/n) = -1 42 // 若 twos 为奇数,则需要乘上 (2/n) 的值 43 // 表达式 (n % 8 == 1 || n % 8 == 7) * 2 - 1 巧妙地将布尔值转为 1 或 -1 44 if (twos > 0 && twos % 2 == 1) 45 mult *= (n % 8 == 1 || n % 8 == 7) * 2 - 1; 46 47 // 再次检查边界(提取因子 2 后,a 可能变为 1) 48 if (a <= 1 || a == n) break; 49 50 // step3. 互反律翻转。二次互反律:当 a 和 n 都是奇数时,翻转 (a/n) 为 (n/a) 51 // 代价是乘以 (-1)^((a-1)/2 * (n-1)/2) 52 // 该指数为奇数当且仅当 a ≡ 3 (mod 4) 且 n ≡ 3 (mod 4) 53 // 因此,若 a%4 != 1 且 n%4 != 1(即两者都余 3),则符号取反 54 if (n % 4 != 1 && a % 4 != 1) mult *= -1; 55 56 // 交换 a 和 n,准备下一轮迭代(相当于将 (a/n) 变为 (n/a)) 57 temp = a; 58 a = n; 59 n = temp; 60 } 61 62 // 循环结束后的最终判断 63 // 情况 1:a == 0,说明 gcd(a, n) != 1,雅可比符号为 0 64 if (a == 0) return 0; 65 // 情况 2:a == 1,说明互质,返回累积的符号因子 mult(±1) 66 else if (a == 1) return mult; 67 // 其他情况(例如 a == n),意味着 gcd(a, n) != 1,返回 0 68 else return 0; 69 }
1.3.3 素数判定
1)素数的单轮判定
1 /** 2 * @brief Solovay-Strassen 素性测试的单轮判定函数 3 * 4 * 利用欧拉判别法判断 n 是否可能为素数。 5 * 如果返回 0,则 n 一定是合数; 6 * 如果返回 1,则 n 很可能是素数(但存在被卡迈克尔数欺骗的可能)。 7 * 8 * 算法核心: 9 * 对于素数 n,任意与 n 互质的 a 都满足欧拉判别法: 10 * a^((n-1)/2) ≡ (a/n) (mod n) 11 * 其中 (a/n) 是雅可比符号。 12 * 如果等式不成立,则 n 必为合数。 13 * 14 * @param a 随机选择的底数,通常满足 1 < a < n 15 * @param n 待测试的奇数(大于 2) 16 * @return int 返回 1 表示 n 通过本轮测试(可能为素数); 17 * 返回 0 表示 n 一定是合数 18 * 19 * @note 单轮测试的误判概率至多为 1/2,实际使用时应对多个随机 a 重复调用。 20 * 函数假定 n 为正奇数,且 a 与 n 互质(若不互质,x=0 会直接返回 0)。 21 */ 22 int solovayPrime(int a, int n) 23 { 24 // 1. 计算雅可比符号 (a/n) 25 // 返回值可能是 -1、0 或 1 26 int x = jacobi(a, n); 27 28 // 2. 将 -1 转换为模 n 下的等价表示 n-1 29 // 因为 modpow 的结果是 0 ~ n-1 之间的非负整数, 30 // 而雅可比符号的 -1 在模 n 下就是 n-1,这样便于直接比较。 31 if (x == -1) { 32 x = n - 1; 33 } 34 35 // 3. 核心判定: 36 // - 若 x == 0,说明 gcd(a, n) != 1,n 必为合数,直接返回 0(假)。 37 // - 否则计算 a^((n-1)/2) mod n,并与雅可比符号 x 比较。 38 // 如果相等,返回 1(真);否则返回 0(假)。 39 // 注意:&& 具有短路特性,若 x == 0,右侧的 modpow 不会执行。 40 return x != 0 && modpow(a, (n - 1) / 2, n) == x; 41 }
2)判断整数n是否为“可能素数”
1 /** 2 * @brief 判断 n 是否为 probable prime(可能素数) 3 * 4 * 使用 Solovay-Strassen 测试进行 k 轮随机检验。 5 * 如果所有轮次都通过,则返回 1(很可能是素数); 6 * 如果任何一轮失败,则返回 0(一定是合数)。 7 * 8 * @param n 待测试的整数,要求 n >= 1 9 * @param k 测试轮数,越大越可靠,建议 20~50 10 * @return int 1 表示可能为素数;0 表示一定是合数 11 */ 12 int probablePrime(int n, int k) 13 { 14 // step1. 处理特殊情况:2 是素数 15 if (n == 2) return 1; 16 // 其他偶数、1 都不是素数 17 else if (n % 2 == 0 || n == 1) return 0; 18 19 // step2. 执行 k 轮随机测试 20 while (k-- > 0) { 21 // 随机选择底数 a,范围 [2, n-1] 22 int a = rand() % (n - 2) + 2; 23 // 如果单轮测试失败,立即断定 n 是合数 24 if (!solovayPrime(a, n)) return 0; 25 } 26 27 // 所有轮次都通过,返回 1(可能素数) 28 return 1; 29 }
3)生成随机素数
1 /** 2 * @brief 随机生成一个小于 n 的素数(probable prime) 3 * 4 * 算法思路: 5 * 1. 随机选取一个 [0, n-1] 的起点; 6 * 2. 将 n 调整为偶数,确保后续 (prime + 2) % n 的结果仍为奇数; 7 * 3. 将起点调整为奇数,然后每次加 2 循环扫描所有奇数; 8 * 4. 对每个奇数调用 probablePrime 进行素性测试,成功则返回。 9 * 10 * @param n 上限(不包含)。要求 n > 2,否则可能陷入死循环。 11 * @return int 找到的素数(小于 n) 12 * 13 * @note 依赖外部宏 ACCURACY 定义素性测试轮数。 14 * 使用标准库 rand(),随机性质量有限,不适合密码学安全场景。 15 * 函数内部会修改局部变量 n,但不影响调用者。 16 */ 17 int randPrime(int n) 18 { 19 int prime = rand() % n; // 1. 随机取 [0, n-1] 之间的整数 20 n += n % 2; // 2. 将 n 调整为偶数 21 prime += 1 - prime % 2; // 3. 将 prime 调整为奇数 22 while(1) { // 4. 无限循环,直到找到素数 23 if(probablePrime(prime, ACCURACY)) return prime; // 5. 素性测试 24 prime = (prime + 2) % n; // 6. 跳到下一个奇数(循环扫描) 25 } 26 }
1.3.4 欧几里得算法--计算两个非负整数的最大公约数
1 /** 2 * @brief 计算两个非负整数的最大公约数(欧几里得算法) 3 * 4 * 欧几里得算法原理:gcd(a, b) = gcd(b, a % b),反复迭代直到 b == 0。 5 * 6 * @param a 第一个整数(非负) 7 * @param b 第二个整数(非负) 8 * @return int a 和 b 的最大公约数 9 * 10 * @note 若输入可能为负数,应在调用前取绝对值。 11 */ 12 int gcd(int a, int b) 13 { 14 int temp; // 临时变量,用于交换 a 和 b 15 16 // 当 b 不为 0 时,继续辗转相除 17 while (b != 0) { 18 temp = b; // 暂存当前的 b 19 b = a % b; // 计算 a 除以 b 的余数,作为新的 b 20 a = temp; // 将原来的 b 赋给 a 21 } 22 23 // 当 b 为 0 时,a 即为最大公约数 24 return a; 25 }
1.3.5 生成RSA公钥指数e
1 /** 2 * @brief 随机生成一个与 phi 互质的整数 e,用作 RSA 公钥指数 3 * 4 * 算法:随机选取 [0, n-1] 的起点,然后线性扫描, 5 * 找到第一个与 phi 互质的数返回。 6 * 7 * @param phi 欧拉函数 φ(n) 8 * @param n RSA 模数 n = p * q 9 * @return int 与 phi 互质的 e,满足 gcd(e, phi) == 1 10 * 11 * @note 跳过 0、1、2,强制从 3 开始。 12 * 使用 rand(),不适合密码学安全场景。 13 * 未检查 e < phi,实际使用时应确保 e < phi。 14 */ 15 int randExponent(int phi, int n) 16 { 17 int e = rand() % n; // 1. 随机生成 [0, n-1] 的起始值 18 while(1) { // 2. 无限循环,直到找到合适的 e 19 if(gcd(e, phi) == 1) // 3. 检查 e 与 phi 是否互质 20 return e; // 若互质,直接返回 e 21 e = (e + 1) % n; // 4. 否则 e 加 1 并模 n,尝试下一个数 22 if(e <= 2) e = 3; // 5. 跳过 0、1、2,强制从 3 开始 23 } 24 }
1.3.6 计算模逆元(扩展欧几里得算法)
1 /** 2 * @brief 计算 n 在模 modulus 下的乘法逆元(扩展欧几里得算法) 3 * 4 * 算法:扩展欧几里得算法 5 * 在计算 gcd(n, modulus) 的同时,维护系数 x0, y0,使得: 6 * a = x0 * n + y0 * modulus 7 * 当循环结束时 a = gcd(n, modulus)。若 gcd = 1,则: 8 * 1 = x0 * n + y0 * modulus 9 * 两边对 modulus 取模得: 10 * 1 ≡ x0 * n (mod modulus) 11 * 因此 x0 就是 n 的模逆元。 12 * 13 * @param n 要求逆元的整数(通常为正数,且与 modulus 互质) 14 * @param modulus 模数,必须大于 1 15 * @return int n 在模 modulus 下的逆元,范围 [0, modulus-1] 16 * 17 * @note 前提条件:gcd(n, modulus) == 1,否则逆元不存在,返回值无意义。 18 * 若 n 或 modulus 为负数,需先规范化为非负数。 19 * 对于大整数,应使用 long long 或大数类型防止溢出。 20 */ 21 int inverse(int n, int modulus) 22 { 23 int a = n, b = modulus; // a, b 为辗转相除的余数序列 24 int x = 0, y = 1, x0 = 1, y0 = 0, q, temp; // 系数序列 25 while(b != 0) { 26 q = a / b; // 商 27 temp = a % b; // 余数 28 a = b; // 更新 a 为旧 b 29 b = temp; // 更新 b 为余数 30 31 temp = x; // 更新 x 系数 32 x = x0 - q * x; 33 x0 = temp; 34 35 temp = y; // 更新 y 系数 36 y = y0 - q * y; 37 y0 = temp; 38 } 39 if(x0 < 0) x0 += modulus; // 将负的逆元转为正数表示 40 return x0; 41 }
1.3.7 读取文件数据
1 /** 2 * @brief 从文件流读取全部数据,并填充到 bytes 的整数倍 3 * 4 * 读取文件所有内容到动态缓冲区,末尾填充零字节, 5 * 确保总长度是 bytes 的整数倍。如果原始长度正好是 bytes 的倍数, 6 * 则额外添加一个全零块,以明确标记数据结束。 7 * 8 * @param fd 已打开的文件流 9 * @param buffer 输出参数,指向动态分配的缓冲区指针 10 * @param bytes 块大小(必须 > 0) 11 * @return int 读取并填充后的总字节数(bytes 的整数倍) 12 * 13 * @note 调用者负责释放 *buffer。函数未处理内存分配失败和读取错误。 14 */ 15 int readFile(FILE* fd, char** buffer, int bytes) 16 { 17 int len = 0; // 已读取的数据长度 18 int cap = BUF_SIZE; // 当前缓冲区容量 19 int r; // 本次 fread 实际读取的字节数 20 char buf[BUF_SIZE]; // 栈上临时缓冲区 21 22 // 初始分配 BUF_SIZE 字节的堆缓冲区 23 *buffer = (char *)malloc(BUF_SIZE * sizeof(char)); 24 25 // 循环读取文件,直到 fread 返回 0(文件结束或错误) 26 while ((r = fread(buf, sizeof(char), BUF_SIZE, fd)) > 0) { 27 // 如果当前容量不足以容纳新数据,则翻倍扩容 28 if (len + r >= cap) { 29 cap *= 2; 30 *buffer = (char *)realloc(*buffer, cap); 31 } 32 // 将新读的数据追加到缓冲区末尾 33 memcpy(&(*buffer)[len], buf, r); 34 len += r; 35 } 36 37 // 计算填充后的总长度:补齐到 bytes 的整数倍 38 // 若 len % bytes == 0,则额外增加一个完整块(len + bytes) 39 int padded_len = len + bytes - len % bytes; 40 if (padded_len > cap) { 41 *buffer = (char *)realloc(*buffer, padded_len); 42 } 43 44 // 填充零字节,直到长度是 bytes 的整数倍 45 // do-while 保证至少填充一个字节(即使原始长度已是倍数) 46 do { 47 (*buffer)[len] = '\0'; 48 len++; 49 } while (len % bytes != 0); 50 51 return len; // 返回填充后的总长度 52 }
1.4 工程代码汇总
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <time.h> 4 #include <string.h> 5 6 #define ACCURACY 5 7 #define SINGLE_MAX 10000 8 #define EXPONENT_MAX 1000 9 #define BUF_SIZE 1024 10 11 /** 12 * Computes a^b mod c,计算a^b mod c 13 */ 14 int modpow(long long a, long long b, int c) { 15 int res = 1; 16 while(b > 0) { 17 /* Need long multiplication else this will overflow... 18 必须使用长乘法,否则这将溢出*/ 19 if(b & 1) { 20 res = (res * a) % c; 21 } 22 b = b >> 1; 23 a = (a * a) % c; /* Same deal here */ 24 } 25 return res; 26 } 27 28 /** 29 * Computes the Jacobi symbol, (a, n),计算Jacobi符号(a,n) 30 */ 31 int jacobi(int a, int n) { 32 int twos, temp; 33 int mult = 1; 34 while(a > 1 && a != n) { 35 a = a % n; 36 if(a <= 1 || a == n) break; 37 twos = 0; 38 while(a % 2 == 0 && ++twos) a /= 2; /* Factor out multiples of 2 ,减去2的倍数*/ 39 if(twos > 0 && twos % 2 == 1) mult *= (n % 8 == 1 || n % 8 == 7) * 2 - 1; 40 if(a <= 1 || a == n) break; 41 if(n % 4 != 1 && a % 4 != 1) mult *= -1; /* Coefficient for flipping,翻转系数 */ 42 temp = a; 43 a = n; 44 n = temp; 45 } 46 if(a == 0) return 0; 47 else if(a == 1) return mult; 48 else return 0; /* a == n => gcd(a, n) != 1 */ 49 } 50 51 /** 52 * Check whether a is a Euler witness for n,检查a是否为n的欧拉见证 */ 53 int solovayPrime(int a, int n) { 54 int x = jacobi(a, n); 55 if(x == -1) x = n - 1; 56 return x != 0 && modpow(a, (n - 1)/2, n) == x; 57 } 58 59 /** 60 * Test if n is probably prime, using accuracy of k (k solovay tests),用k的精度检查n是否可能是素数 */ 61 int probablePrime(int n, int k) { 62 if(n == 2) return 1; 63 else if(n % 2 == 0 || n == 1) return 0; 64 while(k-- > 0) { 65 if(!solovayPrime(rand() % (n - 2) + 2, n)) return 0; 66 } 67 return 1; 68 } 69 70 /** 71 * Find a random (probable) prime between 3 and n - 1在3和(n-1)之间找一个随机素数, this distribution is, 72 * nowhere near uniform, see prime gaps 73 */ 74 int randPrime(int n) { 75 int prime = rand() % n; 76 n += n % 2; /* n needs to be even so modulo wrapping preserves oddness */ 77 prime += 1 - prime % 2; 78 while(1) { 79 if(probablePrime(prime, ACCURACY)) return prime; 80 prime = (prime + 2) % n; 81 } 82 } 83 84 /** 85 * Compute gcd(a, b),计算gcd(a,b) 86 */ 87 int gcd(int a, int b) { 88 int temp; 89 while(b != 0) { 90 temp = b; 91 b = a % b; 92 a = temp; 93 } 94 return a; 95 } 96 97 /** 98 * Find a random exponent x between 3 and n - 1 such that gcd(x, phi) = 1,在3和n-1之间找到随机指数x,使得gcd(x,phi)=1 99 * this distribution is similarly nowhere near uniform,这种分布同样不接近制服 100 */ 101 int randExponent(int phi, int n) { 102 int e = rand() % n; 103 while(1) { 104 if(gcd(e, phi) == 1) return e; 105 e = (e + 1) % n; 106 if(e <= 2) e = 3; 107 } 108 } 109 110 /** 111 * Compute n^-1 mod m by extended euclidian method,用扩展欧几里得法计算n^-1 mod m 112 */ 113 int inverse(int n, int modulus) { 114 int a = n, b = modulus; 115 int x = 0, y = 1, x0 = 1, y0 = 0, q, temp; 116 while(b != 0) { 117 q = a / b; 118 temp = a % b; 119 a = b; 120 b = temp; 121 temp = x; x = x0 - q * x; x0 = temp; 122 temp = y; y = y0 - q * y; y0 = temp; 123 } 124 if(x0 < 0) x0 += modulus; 125 return x0; 126 } 127 128 /** 129 * Read the file fd into an array of bytes ready for encryption.将文件fd读入准备加密的字节数组 130 * The array will be padded with zeros until it divides the number of数组将填充零,知道它划分每个块加密的字节数 131 * bytes encrypted per block. Returns the number of bytes read.返回读取的字节数 132 */ 133 int readFile(FILE* fd, char** buffer, int bytes) { 134 int len = 0, cap = BUF_SIZE, r; 135 char buf[BUF_SIZE]; 136 *buffer = (char *)malloc(BUF_SIZE * sizeof(char)); 137 while((r = fread(buf, sizeof(char), BUF_SIZE, fd)) > 0) { 138 if(len + r >= cap) { 139 cap *= 2; 140 *buffer = (char *)realloc(*buffer, cap); 141 } 142 memcpy(&(*buffer)[len], buf, r); 143 len += r; 144 } 145 /* Pad the last block with zeros to signal end of cryptogram. An additional block is added if there is no room,将最后一个带有零的块插入密码的信号端。 如果没有房间,则增加一个额外的街区 */ 146 if(len + bytes - len % bytes > cap) *buffer = (char *)realloc(*buffer, len + bytes - len % bytes); 147 do { 148 (*buffer)[len] = '\0'; 149 len++; 150 } 151 while(len % bytes != 0); 152 return len; 153 } 154 155 /** 156 * Encode the message m using public exponent and modulus, c = m^e mod n使用公共指数和模量对消息m进行编码,c = m^e Mod n 157 */ 158 int encode(int m, int e, int n) { 159 return modpow(m, e, n); 160 } 161 162 /** 163 * Decode cryptogram c using private exponent and public modulus, m = c^d mod n,用私有指数和公共模量解码密码c,m = c^d Mod n 164 */ 165 int decode(int c, int d, int n) { 166 return modpow(c, d, n); 167 } 168 169 /** 170 * Encode the message of given length, using the public key (exponent, modulus) 171 * The resulting array will be of size len/bytes, each index being the encryption 172 * of "bytes" consecutive characters, given by m = (m1 + m2*128 + m3*128^2 + ..), 173 * encoded = m^exponent mod modulus 174 * 使用公钥(指数、模数)对给定长度的消息进行编码) 175 得到的数组将是大小为len/字节,每个索引是由m=(m1m2*128m3*128^2.)给出的“字节”连续字符的加密,编码=m^指数mod模量 176 */ 177 int* encodeMessage(int len, int bytes, char* message, int exponent, int modulus) { 178 int *encoded = (int *)malloc((len/bytes) * sizeof(int)); 179 int x, i, j; 180 for(i = 0; i < len; i += bytes) { 181 x = 0; 182 for(j = 0; j < bytes; j++) x += message[i + j] * (1 << (7 * j)); 183 encoded[i/bytes] = encode(x, exponent, modulus); 184 #ifndef MEASURE 185 printf("%d ", encoded[i/bytes]); 186 #endif 187 } 188 return encoded; 189 } 190 191 /** 192 * Decode the cryptogram of given length, using the private key (exponent, modulus) 193 * Each encrypted packet should represent "bytes" characters as per encodeMessage. 194 * The returned message will be of size len * bytes. 195 * 使用私钥(指数、模数)解码给定长度的密码) 196 每个加密的数据包应该按照编码消息表示“字节”字符。 197 返回的消息大小为len*字节。 198 */ 199 int* decodeMessage(int len, int bytes, int* cryptogram, int exponent, int modulus) { 200 int *decoded = (int *)malloc(len * bytes * sizeof(int)); 201 int x, i, j; 202 for(i = 0; i < len; i++) { 203 x = decode(cryptogram[i], exponent, modulus); 204 for(j = 0; j < bytes; j++) { 205 decoded[i*bytes + j] = (x >> (7 * j)) % 128; 206 #ifndef MEASURE 207 if(decoded[i*bytes + j] != '\0') printf("%c", decoded[i*bytes + j]); 208 #endif 209 } 210 } 211 return decoded; 212 } 213 214 /** 215 * Main method to demostrate the system. Sets up primes p, q, and proceeds to encode and 216 * decode the message given in "text.txt" 217 * 系统降级的主要方法。 设置素数p,q,并开始编码和 218 *解码“text.txt”中给出的消息; 219 */ 220 int main(void) { 221 int p, q, n, phi, e, d, bytes, len; 222 int *encoded, *decoded; 223 char *buffer; 224 FILE *f; 225 srand(time(NULL)); 226 while(1) { 227 p = randPrime(SINGLE_MAX); 228 printf("生成第一个随机素数, p = %d ... ", p); 229 getchar(); 230 231 q = randPrime(SINGLE_MAX); 232 printf("生成第二个随机素数, q = %d ... ", q); 233 getchar(); 234 235 n = p * q; 236 printf("计算p和q的乘积n, n = pq = %d ... ", n); 237 if(n < 128) { 238 printf("Modulus is less than 128, cannot encode single bytes. Trying again ... "); 239 getchar(); 240 } 241 else break; 242 } 243 if(n >> 21) bytes = 3; 244 else if(n >> 14) bytes = 2; 245 else bytes = 1; 246 getchar(); 247 248 phi = (p - 1) * (q - 1); 249 printf("计算欧拉函数的值phi, phi = %d ... ", phi); 250 getchar(); 251 252 e = randExponent(phi, EXPONENT_MAX); 253 printf("选取一个随机素数e, e = %d...\n获得公钥 (%d, %d) ... ", e, e, n); 254 getchar(); 255 256 d = inverse(e, phi); 257 printf("计算模反元素d, d = %d...\n获得密钥 (%d, %d) ... ", d, d, n); 258 getchar(); 259 260 printf("打开文件 \"text.txt\" 用于读取信息\n"); 261 f = fopen("text.txt", "r"); 262 if(f == NULL) { 263 printf("Failed to open file \"text.txt\". Does it exist?\n"); 264 return EXIT_FAILURE; 265 } 266 len = readFile(f, &buffer, bytes); /* len will be a multiple of bytes, to send whole chunks伦将是多个字节,以发送整个块 */ 267 fclose(f); 268 269 printf("文件 \"text.txt\" 读取成功, 读取到%d字节. 以%d字节的字节流编码 ... ", len, bytes); 270 getchar(); 271 printf("加密得密文为:"); 272 encoded = encodeMessage(len, bytes, buffer, e, n); 273 printf("\n编码成功完成 ... "); 274 getchar(); 275 276 277 printf("正在解码编码的信息 ... "); 278 getchar(); 279 printf("解码得明文为:"); 280 decoded = decodeMessage(len/bytes, bytes, encoded, d, n); 281 282 283 printf("\nRSA算法演示完成!\n"); 284 285 free(encoded); 286 free(decoded); 287 free(buffer); 288 return EXIT_SUCCESS; 289 }
引用来源:
1. (6 封私信 / 80 条消息) RSA —— 经典的非对称加密算法 - 知乎

浙公网安备 33010602011771号