加密算法-综述

加密算法分为对称加密算法和非对称加密算法。

对称加密算法:加密和解密使用同一个密钥。

  → 常见的对称加密算法有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)雅可比符号的计算逻辑--二次互反律

  1. 化简分子​:首先,利用雅可比符号的周期性,将(an)中的 分子
    a对分母n取模,即a(modn),使得分子小于分母。
  2. 提取因子"2​":如果分子是偶数,将分子中的所有因子
    2提出来。因为我们知道(2n)的值由n(mod8)决定,所以这一步很容易。
  3. 互反翻转​:现在分子是一个奇数。此时,二次互反律允许我们将符号“翻转”为(na),代价仅仅是乘上一个由a和n模4的余数决定的符号(1或−1)。
  4. 重复以上步骤​:翻转之后,我们得到了一个新的雅可比符号,其分母变得更小了。我们可以对这个新符号重复上述过程。
 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 }
随机生成一个小于 n 的素数

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 }
随机生成RSA公钥指数e

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 }
RSA加密算法--工程代码

 

引用来源:

1. (6 封私信 / 80 条消息) RSA —— 经典的非对称加密算法 - 知乎

2. RSA算法原理(一) - 阮一峰的网络日志

3. C语言实现RSA算法 - MoreKing - 博客园

4. 雅可比符号及其互反律 | Bohrium

posted @ 2026-09-21 15:07  微光低语  阅读(19)  评论(0)    收藏  举报