RSA 加密算法实现(Java)

       什么 RSA 算法这里就不介绍了,本文主要介绍这个算法的实现,加、解密过程按算法过程编写。(这里不是用 jdk 自带的RSA加密类实现,具体是什么可以自行百度了解)
      本文的数据类型采用 BigInteger 类。

一、RSA算法实现过程

       ① 选择两个大素数 p 和 q
       ② 计算乘积 n = p * q 和 Fn = (p - 1) * (q - 1)
       ③ 选择大于 1 小于 Fn 的随机整数 e,使得 gcd(e, Fn) = 1
       ④ 计算 d 使得 de = 1 mod Fn
       ⑤ 加密变换 C = M ^ e % n(C 为加密得到的密文,M 为明文)
       ⑥ 解密变换 M = C ^ d % n

1.1 产生两个随机大素数 p,q
      这两个素数需要是两个随机产生的素数,不能太小,否则容易被破译。
      这里产生随时素数的方法如下:
             首先定义一个大小为N(大小根据需要设置)的数组,然后把求出 2-10000 范围内(范围大小可以自己定)的素数(本文采用的是素数打表法),然后把求出的素数放到前面定义的数组中。若需要产生随机素数时,则先用Random类生成一个一定范围的一个随机数,然后这个随机数用作素数数组的下标时,则可以取出一个素数了。

package com.rsa.utils;
import java.math.BigInteger;
import java.util.Random;

/**
 * @ProjectName: InformationSecurity
 * @Package: PACKAGE_NAME
 * @ClassName: PrimeUtil
 * @Author: Administrator
 * @Description: ${description}
 * @Date: 2019/4/20 0:33
 * @Version: 1.0
 */
public class PrimeUtil {
	//定义素数的开始
	public static int begin = 2;
	//定义素数的结束
    public static int N = 10000;
    //定义素数表2的大小,需要根据实际定
    public static int M = 1230;
    //素数打表法,所用到的素数表
    public static int[] primeTable = new int[N];
    //素数表2,存放一定范围内的所有素数
    public static int[] prime = new int[M];

    static {
        int i;
        //值 为 1表示为素数,值为 0 表示为非素数
        //把表中所有的值都设置为1
        for (i = begin; i <= N-1; i++){
            primeTable[i] = 1;
        }
        
        for (i = begin; i <= N-1; i++){
            if (primeTable[i] == 0){
                continue;
            }
            for (int j = i+i; j <= N-1; j += i){
                primeTable[j] = 0;
            }
        }

        int j = 1;
        for (i = begin; i <= N - 1; i++){
            if(primeTable[i] == 1) {
                prime[j++] = i;
            }
        }

    }

    public static BigInteger getRandomPrime(){
        //随机从素数表 prime 中取出两个素数p, q,素数表中的素数范围为2-9973
        Random random = new Random();
        return new BigInteger(PrimeUtil.prime[random.nextInt(M-1)] + "");
    }

}

1.2 定义 RSA 类(字符串加密、中文加密、一次加密过个字符)

说明:
          ① 获取公钥 N、E 
               > public BigInteger getPublicKeyN();
               > public BigInteger getPublicKeyE();

          ② 获取私钥 D 
               > public BigInteger getPrivateKeyD();

          ③ 获取一次加密几个字符
               > public int getDealBytes(BigInteger publicKeyN);
                  根据公钥 N 的大小确定一次加密几个字符

          ④ 根据公钥 N、E 进行加密单个字符
               > public String encode(BigInteger plainText, BigInteger publicKeyE, BigInteger publicKeyN);
          ⑤ 对字符串进行加密
               > public List<BigInteger> encodeMessage(int bytes, String code, BigInteger publicKeyE, BigInteger publicKeyN);
                  a. 调用 Base64 类的 encode 方法对明文进行编码(把字符全编码成英文再进行加密,防止明文中存在中文)
                  b. 调用 ③ 方法确定一次加密 bytes 个字符,然后把 bytes 个字符处理成一个字符
                  c. 调用 ④ 方法对这个处理过的字符进行加密

          ⑥ 根据私钥 D、N 进行解密单个字符
               > public String decode(BigInteger C, BigInteger privateKeyD, BigInteger publicKeyN);
          ⑦ 对加密后的字符串进行解密
               > public String decodeMessage(List<BigInteger> encode, BigInteger privateKeyD, BigInteger publicKeyN);
                  a. 调用 ⑥ 方法对容器中的密文进行解密,一次解密出 bytes 个字符
                  b. 调用 Base64 类的 decode 方法对解密出来的明文进行解码(加密时编过码,所以这里要解码)

package com.rsa;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Random;

import org.springframework.stereotype.Component;

import com.rsa.utils.Base64Utils;
import com.rsa.utils.PrimeUtil;

/**
 * @ProjectName: InformationSecurity
 * @Package: PACKAGE_NAME
 * @ClassName: RSA
 * @Author: Administrator
 * @Description: ${description}
 * @Date: 2019/4/19 21:13
 * @Version: 1.0
 */
@Component
public class RSA {

    //公钥 n
    private BigInteger publicKeyN;
    //私钥 d
    private BigInteger privateKeyD;
    //大素数 p
    private BigInteger p;
    //大素数 q
    private BigInteger q;
    //Fn = (p-1) * (q-1)
    private BigInteger Fn;

    private int messagelength;
    private int bytes;

    private final BigInteger one = new BigInteger("1");
    private final BigInteger two = new BigInteger("2");

    public RSA(){

    }

    /**
     * 因为 0<M<n,所以可以多个字符处理成一个数,然后把该数进行加密
     * 这样能大大提高加密效率
     * @param publicKeyN
     * @return
     */
    public int getDealBytes(BigInteger publicKeyN) {
        int bytes;
        // 将公钥 N 右移 21 位,当右移后的结果大于 0 时,表示可以一次处理 3 个字符
        // 3 个字符 24 位,每个字符有 1 位为符号位,除符号位剩余 21 位
        if(publicKeyN.shiftRight(21).compareTo(new BigInteger("0")) == 1){
            bytes = 3;
        }else{
            // 右移 14 位
            if (publicKeyN.shiftRight(14).compareTo(new BigInteger("0")) == 1){
                bytes = 2;
            }else{
                bytes = 1;
            }
        }
        
        return bytes;
    }
    
    /**
     * 获取公钥 n
     * @return 返回公钥 n
     */
    public BigInteger getPublicKeyN() {
        //从素数表中随机获取两个素数 p、q
        p = PrimeUtil.getRandomPrime();
        q = PrimeUtil.getRandomPrime();
        // n = p * q
        publicKeyN = p.multiply(q);
        // Fn = (p-1) * (q-1)
        Fn = p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE));
        return publicKeyN;
    }

    /**
     * 获取公钥 e
     * @return 返回公钥 e
     */
    public BigInteger getPublicKeyE(){
        BigInteger e = two;
        while (true){
            /**
             * e.gcd(Fn):
             *      说明:获取 e, Fn 的最大公约数(该方法是 BigInteger 内的一个成员方法)
             *      return:返回 e, Fn 的最大公约数(类型为 BigInteger)
             *
             * a.compareTo(b):
             *      说明:比较 a,b 的大小
             *      return:当 a < b 时,返回 -1;当 a = b 时,返回 0;当 a > b 时,返回 1
             *
             * e.gcd(Fn).compareTo(new BigInteger("1")) == 0
             *      说明:判断 e.gcd(Fn) == 1
             * e.compareTo(Fn) == -1
             *      说明:判断 e < Fn
             */
            if (e.gcd(Fn).compareTo(one) == 0 && e.compareTo(Fn) == -1) {
                break;
            }
            // e++
            e = e.add(one);

        }

        return e;
    }

    /**
     * 获取私钥 d
     * @return 返回私钥 d
     */
    public BigInteger getPrivateKeyD() {
        //获取 d 的值,d 的值的范围为 2 - Fn
        // d = 2
        privateKeyD = two;
        while (true){
            BigInteger e = this.getPublicKeyE();
            /**
             * d.multiply(e).mod(Fn).compareTo(one)
             *      说明:判断 d*e % Fn 是否等于 1,当等于 1 时,返回 0
             * privateKeyD.compareTo(Fn)
             *      说明:比较 d 和 Fn 的大小,当 d < Fn 时,返回 -1;
             */
            if (privateKeyD.multiply(e).mod(Fn).compareTo(one) == 0 && privateKeyD.compareTo(Fn) == -1) {
                break;
            }
            // d++
            privateKeyD = privateKeyD.add(one);

        }
        return privateKeyD;
    }


    /**
     * 根据公钥 e、n,对明文进行加密
     * @param plainText 明文
     * @param publicKeyE 公钥 e
     * @param publicKeyN 公钥 n
     * @return 返回密文
     */
    public String encode(BigInteger plainText, BigInteger publicKeyE, BigInteger publicKeyN){
        // C = M的e次方 % n
        BigInteger C = null;
        //temp = 1
        BigInteger temp = one;

        //while(e > 0)
        while (publicKeyE.compareTo(new BigInteger("0")) == 1){
            //temp = plainText * temp % n;
            temp = plainText.multiply(temp).mod(publicKeyN);
            //e--
            publicKeyE = publicKeyE.subtract(one);
        }
        C = temp;
        return C.toString();
    }
    
    /**
     * 对字符串进行加密
     * @param bytes :一次加密字符的个数
     * @param code :需要加密的明文
     * @param publicKeyE :公钥 e
     * @param publicKeyN :公钥 n
     * @return :返回加密后的字符串,即密文
     * @throws UnsupportedEncodingException 
     */
    public List<BigInteger> encodeMessage(int bytes, String code, BigInteger publicKeyE, BigInteger publicKeyN) throws UnsupportedEncodingException{
        this.bytes = bytes;
        //先对字符串进行编码,防止有中文
        byte[] codeBase64 = Base64.getEncoder().encode(code.getBytes());
        code = new String(codeBase64);

        this.messagelength = code.length();
        //将字符串转换为字符数组
        char[] message = code.toCharArray();
        //用于存放密文
        List<BigInteger> result = new ArrayList<>();

        int x, i, j;

        /**
         * i += bytes : 一次对 bytes 个字符进行加密
         * 当字符的个数不足 bytes 时,则一次处理剩余的字符
         * 如:bytes = 3;message.length = 13 时
         *    前4次处理一次性加密3个字符,最后一次加密剩余的
         *    一个字符
         */
        for(i = 0; i < message.length; i+=bytes) {
            x = 0;
            /**
             * 作用:将 bytes 个字符转换为数字
             * j < bytes :字符剩余大于等于 bytes时,一次处理 bytes 个字符
             * (i+j) < message.length : 表示剩余字符小于 bytes,一次处理 message.length - (i+j)个字符
             */
            for (j = 0; j < bytes && (i+j) < message.length; j++){
                /**
                 * 
                 * 对每个字符进行左移位运算
                 * message[i + j] *(1 << (7 * j)):
                 *         说明:一个字符共 8 位,有 1 位为符号位不用移动;所以每个字符左移 7 位
                 *              移位后转换为数字,然后对数字进行加密操作
                 *              1 << (7 * j) --> 表示 2^(7*j)
                 *
                 *         如:同时对 AB 进行加密(AB的 ASCII码分别为 65、66)
                 *         当对 A 处理时,j = 0,x += 65
                 *         当对 B 处理时,j = 1,x += 66 * (1 << (7 * 1)) = 66 * 2^7 = 8513
                 *         此时 x 的二进制为 0010 0001 0100 0001
                 *         后八位 0100 0001 = 65 --> A
                 *               0010 0001 0000 0000 = 8448 --> 66 * 2^7 --> B * (1 << (7 * 1))
                 *
                 */
                x += message[i + j] * (1 << (7 * j));
                
            }

            BigInteger M = new BigInteger(x+"");
            // 对转换出来的数字进行加密
            String encode = encode(M, publicKeyE, publicKeyN);
            // 将加密的结果存放到 result 容器中
            result.add(new BigInteger(encode));
        }
        return result;
    }

    /**
     * 利用私钥 d 和 公钥 n 对密文 C 进行解密
     * 公式:M = c ^ d % n
     * @param C 密文
     * @param privateKeyD 私钥 d
     * @param publicKeyN 公钥 n
     * @return 返回解密后的明文
     */
    public String decode(BigInteger C, BigInteger privateKeyD, BigInteger publicKeyN){
        BigInteger temp = one;
        // temp = c^d%n
        while (privateKeyD.compareTo(new BigInteger("0")) == 1){
            // temp = c*temp%n
            temp = C.multiply(temp).mod(publicKeyN);
            // d--
            privateKeyD = privateKeyD.subtract(one);
        }

        return  temp.toString();
    }
    
    /**
     * 对加密的字符进行解密
     * @param encode
     * @param privateKeyD
     * @param publicKeyN
     * @return
     * @throws UnsupportedEncodingException 
     */
    public String decodeMessage(List<BigInteger> encode, BigInteger privateKeyD, BigInteger publicKeyN) throws UnsupportedEncodingException{

        String decode = "";
        String x;
        for (int i = 0; i < encode.size(); i++) {
            // 根据私钥 D,公钥 N,对密文进行解密
            x = this.decode(encode.get(i), privateKeyD, publicKeyN);

            /**
             * 由于加密时的字符串的长度不一定是 bytes 的整数倍
             * 所以当处理到最后一个密文时,需要计算加密时,最后一次的加密
             * 加密了几个字符,然后最后解密就一次解密出几个字符
             *
             * 如:bytes = 3, 明文 length = 5
             * 第一次能一次性处理前三个字符,最后剩余的两个字符也一次处理
             * 得到的第一个密文是由前三个字符处理加密得到的,第二个密文是
             * 由最后两个字符处理加密得到的。
             * 所以解密时解密第二个密文,只需处理两次,然后解密得到两个明文
             */
            int count = this.getDealBytes(publicKeyN);
            // 当处理最后一个密文时
            if (i == encode.size()-1){
                // 判断最后一个密文加密时被一次处理了几个字符个数是否是bytes的整数倍
                if (messagelength % bytes != 0){
                   count = messagelength % bytes;
                }
            }

            /**
             * 对同时处理加密的字符进行移位解密,一次解密出 count 个字符
             */
            for (int j = 0; j < count; j++) {
              BigInteger temp = new BigInteger(x);

                /**
                 * 假设对 x 是 AB 两个字符的移位处理的结果(看加密过程)
                 * 即 x = 8513
                 * x 的二进制为 0010 0001 0100 0001
                 * j = 0 --> 8513 % 128 = 65 --> A
                 * j = 1 --> 0010 0001 0100 0001>>7 --> 0000 0000 0100 0010 = 66 --> B
                 */
                BigInteger mod = temp.shiftRight(7 * j).mod(new BigInteger("128"));
              decode += (char) Integer.parseInt(mod.toString());
            }
        }
        
        //对解密出来的字符串进行中文解码
        byte[] bytes = Base64.getDecoder().decode(decode);
        decode = new String(bytes);
        return decode;
    }

}

1.3 测试

解密的结果为数字,这里用到了 jdk 自带的 Base64 类进行字符编码,把数字编码成英文。

package com.rsa;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;

public class RSATest {

	public static void main(String[] args) throws UnsupportedEncodingException {
		// TODO Auto-generated method stub
        RSA rsa = new RSA();
        // 获取公钥 N
        BigInteger publicKeyN = rsa.getPublicKeyN();
        // 获取公钥 E
        BigInteger publicKeyE = rsa.getPublicKeyE();
        // 获取私钥 D
        BigInteger privateKeyD = rsa.getPrivateKeyD();
        System.out.println("公钥 publicKeyN = " + publicKeyN);
        System.out.println("公钥 publicKeyE = " + publicKeyE);
        System.out.println("私钥 privateKeyD = " + privateKeyD);

        int bytes = rsa.getDealBytes(publicKeyN);
        
        System.out.println("一次加密字符个数:" + bytes);
        
        String content = "这是明文...";
        
        /**
         * 加密
         */
        List<BigInteger> list = rsa.encodeMessage(bytes, content, publicKeyE, publicKeyN);

        //把密文进行拼接,拼接用空格进行隔开
        String encodeMessage = "";

        for (BigInteger m : list) {
            encodeMessage += m.toString() + " ";
        }
        System.out.println("密文:\n" + encodeMessage);
        
        //用 Base64 进行编码
        byte[] encodeBase64 = Base64.getEncoder().encode(encodeMessage.getBytes());
        String encodeBase64Str = new String(encodeBase64);

        System.out.println("编码后密文:");
        System.out.println(encodeBase64Str);
        
        //将密文进行解码
        byte[] decodeBase64Byte = Base64.getDecoder().decode(encodeBase64Str);
        String decodeBase64Str = new String(decodeBase64Byte);
        System.out.println("解码后密文:");
        System.out.println(encodeBase64Str);
        
        //将解码后的密文以空格分隔开
        String[] decodeSplit = decodeBase64Str.split(" ");

        //将分隔开的密文加入到 List 容器中
        List decodeBase64List = new ArrayList();
        for (String b : decodeSplit) {
            decodeBase64List.add((new BigInteger(b)));
        }

        //将密文进行解密
        String decodeMessage = rsa.decodeMessage(decodeBase64List, privateKeyD, publicKeyN);
        System.out.println("解密结果:\n" + decodeMessage);
	}

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值