几个简单密码学算法的实现
文章给出了密码学初学时可能会遇到的算法实现,包括求最大公约数(最小公倍数lcm(a,b)=ab/gcd(a,b))、乘法逆元,快速模幂运算、Miller-Rabin素数判定等等。声明一下,只是实现了功能,保证了实践复杂度是我已知的最好的,但是并未做任何优化,仅供学习参考,计算速度跟专业的库相比应该有很大差距。
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* @author miracle
* @date 2022/8/29 18:01
* @description: <br/>
* 我的工具类
*/
public class Main {
/**
* 将一个十进制的数字转化为指定进制,指定位数的数字符串(位数不够,前面补零)
* 如:
* - 将 33 转化为长度为 8 的二进制字符串
* - 则结果为: "00100001"
*
* @param number 带转换的十进制数字
* @param radix 将要转化为的进制数
* @param length 转化后字符串的长度
* @return 满足要求的字符串
*/
public static String toString(int number, int radix, int length) {
StringBuilder str = new StringBuilder();
for (int i = 0; i < length; i++) {
int bit = number % radix;
str.insert(0, bit);
number = number / radix;
}
return str.toString();
}
// 反转指定位置的字符串
public static String reverseStr(String str, int start, int end) {
char[] chars = str.toCharArray();
while (start < end) {
char temp = chars[start];
chars[start] = chars[end];
chars[end] = temp;
start++;
end--;
}
return new String(chars);
}
// 反转指定位置的对象数组
public static <T> T[] reverseArray(T[] arr, int start, int end) {
while (start < end) {
T temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
start++;
end--;
}
return arr;
}
// 反转指定位置的字符串
public static int[] reverseInt(int[] arr, int start, int end) {
while (start < end) {
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
start++;
end--;
}
return arr;
}
/**
* 大整数的大整数幂对一个大整数求幂的结果(Big Integer Power Modulus)
*
* @param base 底数
* @param exponent 指数
* @param n 模数
* @return base的exponent次幂模n
*/
public static BigInteger BigIntegerPM(BigInteger base, BigInteger exponent, BigInteger n) {
// 保证底数大于模数
if (base.compareTo(n) == -1) {
base = base.mod(n);
}
BigInteger res = solvePM(base, exponent, n);
return res;
}
/**
* 1、数论中有如下结论
* (a + b) % p = (a % p + b % p) % p
* (a - b) % p = (a % p - b % p) % p
* (a * b) % p = (a % p * b % p) % p
* (a ^ b) % p = ((a % p) ^ b) % p
* (a ^ b) % p = ((a % p) ^ b) % p
* <p>
* 2、分治法求解
* (1) 若 exponent 为奇数: 令 exponent = 2k+1, k 为非负整数 , 则
* a^(2k+1) = (a^k)^2 *a ; a^(2k+1) % n = ((a^k % n)^2 % n * a % n) % n = [ (a^k % n)^2 * a ] % n
* (2) 若power 为偶数: 令 power = 2k, k为非负整数, 则
* a^(2k) = (a^k)^2 ; a^(2k) % n = (a^k % n)^2 % n
* (3) 若 power == 0: 返回 1。
* <p>
* 3、算法分析
* 时间复杂度 T(n) = O( log(n) ) (这里的n指的是 exponent)
* 空间复杂度 T(n) = O( lon(n) ) (递归函数的空间开销)
*
* @param base 底数
* @param exponent 指数
* @param n 模数
* @return base的exponent次幂模n
*/
public static BigInteger solvePM(BigInteger base, BigInteger exponent, BigInteger n) {
if (exponent == BigInteger.ZERO) {
return new BigInteger("1");
}
if (exponent.mod(new BigInteger("2")) == BigInteger.ZERO) {
BigInteger two = new BigInteger("2");
base = solvePM(base, exponent.divide(two), n).pow(2);
return base.mod(n);
} else {
BigInteger two = new BigInteger("2");
base = solvePM(base, exponent.divide(two), n).pow(2)
.multiply(base);
return base.mod(n);
}
}
/**
* 欧几里得算法(辗转相除法)求两个数的最大公约数。(递归法)
* 注意,第一参数a 必须大于等于第二个参数b
*
* @param a:较大的参数
* @param b:较小的参数
*/
public static int gcdByRecursive(int a, int b) {
return b == 0 ? a : gcdByRecursive(b, a % b);
}
/**
* 欧几里得算法(辗转相除法)求两个数的最大公约数。(非递归法)
*
* @param a:参数
* @param b:参数
*/
public static int gcd(int a, int b) {
int r;
if (a < b) {
r = a;
a = b;
b = r;
}
while (b != 0) {
r = a % b;
a = b;
b = r;
}
return a;
}
/**
* 扩展的欧几里得算法求最大公约数
* 设 s0 = 1 ,s1 = 0 ; t0 = 0 , t1 = 1 ;
* 则:
* s(i+1) = s(i-1) - si * qi ; (i = 1,2,...,n)
* t(i+1) = t(i-1) - ti * qi ; (i = 1,2,...,n)
* 当 n>1 时,按照上述地推公式迭代求得 sn 和 tn
* sn 和 tn 满足 a * sn + b * tn = gcd(a,b)
* (这里 这里的 n 是用辗转相除法使得 a|b 是的辗转相除的次数。)
* 例如 a = 10,b = 0时,辗转相除0次,n = 0;
* * a = 20,b = 10时,辗转相除1次,n = 1;
* * a = 15,b = 10时,辗转相除2次,n = 2;
* 当 n = 0 时, s0 = 1,t0 = 0;
* 当 n = 1 时, s1 = 0,t1 = 1;
* 当 n > 1 时, sn,tn 由上述递推公式求得。
*
* @param a 第一个参数
* @param b 第二个参数
* @return 返回一个含有三个元素的Map,分别为 gcd(a,b)、s和t 其中s和t满足 as + bt = gcd(a,b)
*/
public static Map<String, Integer> gcdExtend(int a, int b) {
Map<String, Integer> res = new HashMap<>();
int r, r0 = a, r1 = b;
// 迭代求解 s和t 使得 as + bt = gcd(a,b) 时用到的变量
int s0 = 1, t0 = 0;
int s1 = 0, t1 = 1;
int s = 1, t = 0;
// 这么赋值是为了保证 s,t满足 as + bt = gcd(a,b)
if (a < b) {
r0 = b;
r1 = a;
s0 = 0;
t0 = 1;
s1 = 1;
t1 = 0;
}
// 以下两种特殊情况处理
if (r1 == 0) {
res.put("gcd", r0);
res.put("s", 1);
res.put("t", 0);
return res;
}
if (r0 % r1 == 0) {
res.put("gcd", r1);
res.put("s", 0);
res.put("t", 1);
return res;
}
// 迭代求解 s和t 使得 as + bt = gcd(a,b)
int qi = 0;
while (r0 % r1 != 0) {
qi = r0 / r1;
s = s0 - s1 * qi;
t = t0 - t1 * qi;
s0 = s1;
t0 = t1;
s1 = s;
t1 = t;
r = r0 % r1;
r0 = r1;
r1 = r;
}
// 找到相应的 s和t 后 , 返回结果
res.put("gcd", a * s + b * t);
res.put("s", s);
res.put("t", t);
return res;
}
/**
* 针对大整数的扩展的欧几里得算法
* 时间复杂度T(n) = O(log n)
*
* @param a 任意长度的整数
* @param b 任意长度的整数
* @return a 和 b 的最大公约数以及 s 和 t组成的map,其中 key = "gcd" 的 value 是 gcd(a,b)
*/
public static Map<String, BigInteger> gcdExtend(BigInteger a, BigInteger b) {
Map<String, BigInteger> res = new HashMap<>();
BigInteger r, r0 = a, r1 = b;
BigInteger s0 = BigInteger.ONE;
BigInteger t0 = BigInteger.ZERO;
BigInteger s1 = BigInteger.ZERO;
BigInteger t1 = BigInteger.ONE;
BigInteger s = BigInteger.ONE;
BigInteger t = BigInteger.ZERO;
// 如果 a < b; 交换 a,b ; 保证 a >= b
if (a.compareTo(b) == -1) {
r0 = b;
r1 = a;
s0 = BigInteger.ZERO;
t0 = BigInteger.ONE;
s1 = BigInteger.ONE;
t1 = BigInteger.ZERO;
}
// 以下两种特殊情况处理
if (r1 == BigInteger.ZERO) {
res.put("gcd", r0);
res.put("s", BigInteger.ONE);
res.put("t", BigInteger.ZERO);
return res;
}
if (a.mod(b) == BigInteger.ZERO) {
res.put("gcd", r1);
res.put("s", BigInteger.ZERO);
res.put("t", BigInteger.ONE);
return res;
}
// 迭代求解 s和t 使得 as + bt = gcd(a,b)
BigInteger qi = BigInteger.ZERO;
while (r0.mod(r1) != BigInteger.ZERO) {
qi = r0.divide(r1);
s = s0.subtract(s1.multiply(qi));
t = t0.subtract(t1.multiply(qi));
s0 = s1;
t0 = t1;
s1 = s;
t1 = t;
r = r0.mod(r1);
r0 = r1;
r1 = r;
}
// 找到相应的 s和t 后 , 返回结果
BigInteger as = a.multiply(s);
BigInteger bt = b.multiply(t);
res.put("gcd", as.add(bt));
res.put("s", s);
res.put("t", t);
return res;
}
/**
* 求 a 在 模n 下的乘法逆元(扩展的欧几里得算法)
*
* @param a 求a的乘法逆元
* @param n 模数
* @return 存在乘法逆元时,返回 a 在 模n 下的乘法逆元。否则返回 -1
*/
public static int getMultiplicativeInverse(int a, int n) {
Map<String, Integer> map = gcdExtend(a, n);
if (map.get("gcd") == 1) {
Integer s = map.get("s");
while (s < 0) {
s = s + n;
}
while (s >= n) {
s = s - n;
}
return s;
} else {
System.out.println("getMultiplicativeInverse()提示您: " + a + "在模" + n + "下没有乘法逆元!");
return -1;
}
}
/**
* 求 a 在 模n 下的乘法逆元(扩展的欧几里得算法)
* 这里 a 和 n 都是 BigInteger 类型的
*
* @param a 求a的乘法逆元
* @param n 模数
* @return 存在乘法逆元时,返回 a 在 模n 下的乘法逆元。否则返回 -1
*/
public static BigInteger getMultiplicativeInverse(BigInteger a, BigInteger n) {
Map<String, BigInteger> map = gcdExtend(a, n);
if (map.get("gcd")
.compareTo(BigInteger.ONE) == 0) {
BigInteger s = map.get("s");
while (s.compareTo(BigInteger.ZERO) == -1) {
s = s.add(n);
}
while (s.compareTo(n) == 1) {
s = s.subtract(n);
}
return s;
} else {
System.out.println("getMultiplicativeInverse()提示您: " + a + "在模" + n + "下没有乘法逆元!");
return new BigInteger("-1");
}
}
/**
* 返回int型数据的位数
*
* @param number 数据
* @return 数据的位数
*/
public static int bitOfInt(int number) {
if (number < 0) {
number = -number;
}
int[] sizeTable = {9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, Integer.MAX_VALUE};
int i = 0;
while (number > sizeTable[i++])
;
return i;
}
/**
* 牛顿下降法判断大整数是不是完全平方数(具体方法可参考 https://www.jianshu.com/p/91a69f585352)
*
* @param F7 带判断大整数
* @return 如果是完全平方数,返回平方根,否则返回0
*/
public static boolean isSquare(BigInteger F7) {
// 牛顿法求解平方根, 求解a的平方根
// x为a的平方根,x的初始值为1, 按x = (x+a/x)/2迭代, 误差为error,事实上,当error < 1时即可求出x平方根的整数部分
BigDecimal x = BigDecimal.ONE;
BigDecimal a = new BigDecimal(F7.toString());
BigDecimal eps = new BigDecimal("1");
final BigDecimal error = new BigDecimal("1E-10");
int scale = 100;
// 进入循环
while (eps.compareTo(error) > 0) { // eps > error是执行循环
x = x.add(a.divide(x, scale, BigDecimal.ROUND_HALF_UP))
.divide(new BigDecimal("2.0"), scale, BigDecimal.ROUND_HALF_UP);
eps = x.multiply(x)
.subtract(a)
.abs();
}
BigInteger sqrt = x.toBigInteger(); // 求平方根的整数部分
if (sqrt.pow(2)
.compareTo(F7) == 0) {
return true;
} else {
return false;
}
}
/**
* 牛顿下降法对大整数开根号
* 由于开根号结果不一定是整数,这里的结果使用的直接抹掉小数部分,效果即为 floor(根号F7)
*
* @param F7 待开放的整数
* @return 对F7开放后,结果的整数部分
*/
public static BigInteger sqrt(BigInteger F7) {
// 牛顿法求解平方根, 求解a的平方根
// x为a的平方根,x的初始值为1, 按x = (x+a/x)/2迭代, 误差为error,事实上,当error < 1时即可求出x平方根的整数部分
BigDecimal x = BigDecimal.ONE;
BigDecimal a = new BigDecimal(F7.toString());
BigDecimal eps = new BigDecimal("1");
final BigDecimal error = new BigDecimal("1E-10");
int scale = 100;
// 进入循环
while (eps.compareTo(error) > 0) { // eps > error是执行循环
x = x.add(a.divide(x, scale, BigDecimal.ROUND_HALF_UP))
.divide(new BigDecimal("2.0"), scale, BigDecimal.ROUND_HALF_UP);
eps = x.multiply(x)
.subtract(a)
.abs();
}
BigInteger sqrt = x.toBigInteger(); // 求平方根的整数部分
return sqrt;
}
/**
* 产生一个范围在 [start,end]之间的随机大整数
*
* @param start 随机数的最小值
* @param end 随机数的最大值
* @return 返回一个范围在[start, end]之间的随机大整数, 如果start<end , 返回null
*/
public static BigInteger random(BigInteger start, BigInteger end) {
if (start.compareTo(end) > 0) {
return null;
}
if (start.compareTo(end) == 0) {
return end;
}
BigInteger a;
do {
a = new BigInteger((end.subtract(start)).bitLength(), new Random());
}
while (a.compareTo(end.subtract(start)) > 0);
a = a.add(start);
return a;
}
/**
* 产生一个范围在 [start,end]之间的随机数
*
* @param start 随机数的最小值
* @param end 随机数的最大值
* @return 返回一个范围在[start, end]之间的随机大整数, 如果start<end , 返回-1
*/
public static double random(double start, double end) {
if (start > end) {
return -1;
}
if (start == end) {
return end;
}
double res;
Random random = new Random();
return start + random.nextDouble() * (end - start);
}
/**
* 产生一个范围在 [start,end]之间的随机数
*
* @param start 随机数的最小值
* @param end 随机数的最大值
* @return 返回一个范围在[start, end]之间的随机整数, 如果start<end , 返回-1
*/
public static int random(int start, int end) {
if (start > end) {
int temp = start;
start = end;
end = temp;
}
Random random = new Random();
return start + random.nextInt(end - start + 1);
}
/**
* Miller-Rabin素性判定
* 算法思路
* 设n是一个奇数,n-1 = 2^t * m,其中m是一个奇数。要测试n是否为素数,
* ① 先随机选择一个介于 [2,n-2]区间内的整数a,如果a^m ≠ 1 且
* ② 对所有的满足 r ∈ [0,k-1] 的整数有,a^(2^r * m) ≠ -1 , 则有n一定是合数。否则,n有很大可能是素数。
* (目前可以在数学上证明,Miller-Rabin将合数判定为素数的概率小于1/4,而事实上经验告诉我们,概率比1/4小得多)
* <p>
* 时间复杂度: T(n) = O(log n)
*
* @param n 待判定数字
* @param k 迭代的轮数,迭代k次后,将合数判定为素数的概率为 1/4^k
* @return 是素数返回true, 是合数返回false。注意返回false一定是合数,返回true,有1/4^k 的概率是合数
*/
public static boolean isPrime_Miller_Rabin(BigInteger n, int k) {
if (n.compareTo(BigInteger.ONE) <= 0) {
return false;
}
// 特殊情况,n为2或3时,MyTools.random返回为null
if (n.compareTo(new BigInteger("2")) == 0 || n.compareTo(new BigInteger("3")) == 0) {
return true;
}
// Miller-Rabin素性判定
BigInteger two = new BigInteger("2");
BigInteger m = n.subtract(BigInteger.ONE);
int t = 0;
while (m.mod(two) == BigInteger.ZERO) {
m = m.divide(two);
t++;
}
for (int i = 0; i < k; i++) {
BigInteger a = random(two, n.subtract(two));
if (BigIntegerPM(a, m, n)
.compareTo(BigInteger.ONE) != 0) {
int r;
for (r = 0; r <= t - 1; r++) {
if (BigIntegerPM(a, two.pow(r)
.multiply(m), n)
.subtract(n)
.compareTo(new BigInteger("-1")) == 0) {
break;
}
}
if (r == t) {
return false;
}
}
}
return true;
}
// 获得Miller-Rabin素数检测的检测次数
private static int getTryTime(BigInteger n) {
//copy自jdk源码, n的bit数越多, 需要的Miller-Rabin检测次数就越少
int sizeInBits = n.bitLength();
int tryTime = 0;
if (sizeInBits < 100) {
tryTime = 50;
} else if (sizeInBits < 256) {
tryTime = 27;
} else if (sizeInBits < 512) {
tryTime = 15;
} else if (sizeInBits < 768) {
tryTime = 8;
} else if (sizeInBits < 1024) {
tryTime = 4;
} else {
tryTime = 2;
}
return tryTime;
}
/**
* 判断一个数是否是素数
* 当 num 较小时,使用查询素数表和查看num是不是小素数的倍数的方法对num进行素性判定
* 当 使用上述方法无法判断 num 的素性时,采用 Miller-Rabin进行k轮素性判定,其中k的值根据num的大小不同而动态求出。
*
* @param num 待判断的数
* @return 素数返回true,合数返回false
*/
public static boolean isPrime(BigInteger num) {
// # 创建小素数的列表,可以大幅加快速度
int[] small_primes = {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, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997};
if (num.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) < 0) {
long n = num.longValue();
// # 如果是小素数,那么直接返回true
for (int i = 0; i < small_primes.length; i++) {
if (n == small_primes[i]) {
return true;
}
}
// #如果大数是这些小素数的倍数,那么就是合数,返回false
for (int i = 0; i < small_primes.length; i++) {
if (n % small_primes[i] == 0) {
return false;
}
}
}
// #如果这样没有分辨出来,就一定是大整数,那么就调用rabin算法
return isPrime_Miller_Rabin(num, getTryTime(num));
}
public static void main(String[] args) {
// System.out.println(toString(33,33,8));
// Integer[] arr = new Integer[5];
// for(int i = 0; i < 5; i++){
// arr[i] = i;
// }
// Integer[] res = reverseArray(arr,0,arr.length - 1);
// for(Integer re : res){
// System.out.println(re);
// }
// Integer a = 5;
// System.out.println(a);
// System.out.println(bitOfInt(1000));
// System.out.println(MyTools.getMultiplicativeInverse(7467,11200));
// System.out.println(MyTools.BigIntegerPM(new BigInteger("5859"),new BigInteger("3"),new BigInteger("11413")));
// for(int i = 0; i < 100; i++){
// System.out.print(random(BigInteger.TEN,BigInteger.TEN.pow(2)) + ",");
// }
// for(int i = 2; i < 300; i++){
// System.out.println(isPrime_Miller_Rabin(BigInteger.valueOf(i),10));
// }
System.out.println(99 % 13);
Random random = new Random();
int bitLength = 180;
BigInteger randomInteger = new BigInteger(bitLength, random);
BigInteger bigInteger = new BigInteger(randomInteger.toString());
boolean isPrime = bigInteger.isProbablePrime(100);
while (!isPrime) {
bigInteger = bigInteger.add(BigInteger.ONE);
isPrime = bigInteger.isProbablePrime(100);
}
System.out.println("生成的大素数:" + bigInteger);
}
}