密码学-java信息安全,摘要算法,对称加密(AES)/非对称加密(RSA)

一、编码算法

主要是为了在网络间更方便的传输数据/本地存储字节数组而产生

1、base64

由A-Z、a-z、0-9、+、/共64个字符组成,去掉i、I、o、O、+、/即base58
密码学-java信息安全,摘要算法,对称加密(AES)/非对称加密(RSA)_第1张图片
注意:base64以三个字节为一组,如果最后一组不足三个字节,则使用=号补充
案例:

package com.yypttest.Utils;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class Base64Test {
    //指定字符集
    private static final String UTF8 = StandardCharsets.UTF_8.name();
    //使用jdk原生来实现base64
    @Test
    public void test1() throws Exception {
        String str = "base64测试";
        //getEncoder:编码
        //jdk1.8之后才能使用
        String encode = Base64.getEncoder().encodeToString(str.getBytes(UTF8));
        System.out.println("encodeStr: "+encode);
        byte[] decode = Base64.getDecoder().decode(encode.getBytes(UTF8));
        System.out.println("decoder: "+new String(decode,UTF8));
    }

    //使用commons-codec来实现base64
    @Test
    public void test2() throws Exception {
        String str = "使用commons-codec来实现base64";
        //getEncoder:编码
        //jdk1.8之后才能使用
        String encodeStr = org.apache.commons.codec.binary.Base64.encodeBase64String(str.getBytes(UTF8));
        System.out.println("encodeStr: "+encodeStr);
        byte[] decodeStr = org.apache.commons.codec.binary.Base64.decodeBase64(encodeStr.getBytes(UTF8));
        System.out.println("decoder: "+new String(decodeStr,UTF8));
    }
}

2、URL编码

application/x-www-from-urlencoded
前端默认使用这种方式编码传递到后端

测试案例:

package com.yypttest.Utils;
import org.junit.Test;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class UrlTest {
    //指定字符集
    private static final String UTF8 = StandardCharsets.UTF_8.name();
    @Test
    public void test1() throws Exception{
        String str = "测试applicationxwwfurlened";
        String encode = URLEncoder.encode(str,UTF8);
        System.out.println("编码后:"+encode);
        String decode = URLDecoder.decode(encode, UTF8);
        System.out.println("解码后:"+decode);
    }
}

只对中文进行编码

二、摘要算法

1、定义

又叫Hash算法、散列函数、数字摘要、消息摘要。它是一种单向算法,用户可以通过hash算法对目标信息生成一段特定长度的唯一hash值,但不能通过这个hash值重新获得目标信息。

2、应用场景

  • 密码、信息完整性校验、数字签名

3、常见算法

  • MD5:Message-Digest Algorithm,结果占128位==>16个byte
  • SHA(Secure Hash Algorithm):安全散列算法
    • sha-256
    • 其他如:sha-0,sha-1,sha512
  • MAC(Message Authentication Code):消息认证码,是一种带有密钥的hash函数
  • 其他如:MD2、MD4、HAVAL

4、案例

1、使用jdk原生api实现md5,字节数组与16进制字符串之间的转换

package com.yypttest.Utils;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;

public class MD5Test {

    //使用jdk原生api实现md5
    //指定字符集
    private static final String UTF8 = StandardCharsets.UTF_8.name();
    @Test
    public void test1() throws Exception{
        String str = "使用jdk原生api实现md5";
        String algorithm = "MD5";
        //获取消息摘要算法对象
        MessageDigest md = MessageDigest.getInstance(algorithm);
        //获取原始内容的自己数组
        byte[] bytes = str.getBytes(UTF8);
        //获取到摘要结果,这里的结果还是字节数
        byte[] digest = md.digest(bytes);

        // 当digest比较大的时候,循环的进行update()
        // md.update(bytes);
        // md.digest();

        /**
         * MD5:Message-Digest Algorithm,结果占128位==>16个byte
         * 每个字节占8位2进制数,每四位二进制可转换为一个十六进制数,所以这里16个字节就可以用32位十六进制数表示。
         * 把每一个字节转为16进制字符,最终再来拼接起来这些16进制字符
         */
        String hexStr = convertBytes2HexStr(digest);
        System.out.println(hexStr);

    }
    /*
    * 把字节数组转为16进制字符串,如果一个字节转为16进制字符后不足两位,则前面补0
    * */
    private String convertBytes2HexStr(byte[] digest) {
        StringBuilder buffer = new StringBuilder();

        for (byte b:digest){
            //获取b的补码后8位
            String hex = Integer.toHexString(((int)b)&0xff);
            if (hex.length()==1){
                hex="0"+hex;
            }
            buffer.append(hex);
        }
        return buffer.toString();
    }
}

 /**
     * 把16进制字符串(这里的字符串一定是偶数,因为在convertBytes2HexStr中已经处理过了)转为字节数组
     * @param hexStr 16进制字符串
     * @return 字节数组
     */
    public static byte[] convertHex2Btyes(String hexStr){
        //一个字符可以转为2个16进制字符
        int length = hexStr.length()/2;
        //16进制的个数
        byte[] result = new byte[length];
        for (int i = 0; i < length; i++) {
            //假如:hexStr=abcd
            //Integer.parseInt:将指定的进制数转成10进制数(参数1:指定字符,参数2:字符的是什么进制数)
            //获取每个字节的高4位二进制数,也就是第一位16进制数,substring指定字符串范围截取字符串,含头不含尾(]
            int high4 = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
            //获取每个字节的低4位二进制数,也就是第二位16进制数,substring指定字符串范围截取字符串,含头不含尾(]
            int low4 = Integer.parseInt(hexStr.substring(i * 2+1, i * 2 + 2), 16);
            result[i] = (byte) (high4*16+low4);

        }
        return result;
    }

    @Test
    public void test() throws DecoderException {
        String hexStr = "abcd";
        byte[] hex2Btyes = convertHex2Btyes(hexStr);
        System.out.println(Arrays.toString(hex2Btyes));

        //使用codec
        System.out.println(Arrays.toString(Hex.decodeHex(hexStr)));
    }

2、使用commons-codec实现md5

	//import org.apache.commons.codec.digest.*;
    //使用commons-codec实现md5
    @Test
    public void test2() throws Exception{
        String str = "使用commons-codec实现md5";
        System.out.println(DigestUtils.md5DigestAsHex(str.getBytes(UTF8)));
    }

3、使用commons-codec实现sha256

package com.yypttest.Utils;

import org.junit.Test;
import org.apache.commons.codec.digest.*;
import static com.alibaba.fastjson.util.IOUtils.UTF8;

public class Sha256 {
    @Test
    public void test1(){
        String str = "使用commons-codec实现sha256";
        System.out.println(DigestUtils.sha1Hex(str.getBytes(UTF8)));
    }
}

4、MAC(Message Authentication Code):消息认证码

mac摘要和digest算法(MD5,sha)不同的地方就是加了盐

package com.yypttest.Utils;

import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;
import org.junit.Test;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;

public class MacTest {
    //指定字符集
    private static final String UTF8 = StandardCharsets.UTF_8.name();
    /**
     * 获取mac的消息摘要
     * @param originalContent 原始内容
     * @param key mac算法的key
     * @param algorithm 算法名称,如HmacMD5
     * @return
     */
    public static String doMacDigest(String originalContent,String key,String algorithm){

        try {
            //获取消息摘要算法对象
            Mac mac = Mac.getInstance(algorithm);
            //获取key对象并初始化mac
            SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(UTF8), algorithm);
            //初始化
            mac.init(secretKeySpec);
            //获取原始内容的字节数组
            byte[] bytes = originalContent.getBytes(UTF8);
            //获取到摘要结果
            byte[] doFinal = mac.doFinal(bytes);
            //把每一个字节转换位16进制字符,最终在拼接成16进制
            MD5Test md5Test = new MD5Test();
            return md5Test.convertBytes2HexStr(doFinal);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
    @Test
    public void test1() throws Exception{
        String str = "使用jdk原始api实现mac";
        //指定密钥,mac摘要和digest算法(MD5,sha)不同的地方就是加了盐
        String key = "123";
        String algorithm = "HmacMD5";
        String macMD5Digest = doMacDigest(str, key, algorithm);
        System.out.println("macMD5Digest: "+macMD5Digest);

        String str1 = "使用jdk原始api实现mac";
        //指定密钥,mac摘要和digest算法(MD5,sha)不同的地方就是加了盐
        String key1 = "123";
        String algorithm1 = "HmacSHA256";
        String macSHA265Digest = doMacDigest(str1, key1, algorithm1);
        System.out.println("macSHA265Digest: "+macSHA265Digest);

        String str2 = "使用jdk原始api实现mac";
        //指定密钥,mac摘要和digest算法(MD5,sha)不同的地方就是加了盐
        String key2 = "123";
        String algorithm2 = "HmacSHA512";
        String macSHA512Digest = doMacDigest(str2, key2, algorithm2);
        System.out.println("macSHA512Digest: "+macSHA512Digest);
    }

    //使用codec实现mac
    @Test
    public void test2() throws UnsupportedEncodingException {
        String str2 = "使用codec实现mac";
        String key2 = "123";
        String macMD5 = new HmacUtils(HmacAlgorithms.HMAC_MD5, key2.getBytes(UTF8)).hmacHex(str2.getBytes(UTF8));
        String macSHA256 = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, key2.getBytes(UTF8)).hmacHex(str2.getBytes(UTF8));
        String macSHA512 = new HmacUtils(HmacAlgorithms.HMAC_SHA_512, key2.getBytes(UTF8)).hmacHex(str2.getBytes(UTF8));
        System.out.println("macMD5: "+macMD5);
        System.out.println("macSHA256: "+macSHA256);
        System.out.println("macSHA512: "+macSHA512);
    }
}

三、对称加密

1、定义

也叫单密钥加密,所谓单密钥,指的是加密和解密的过程使用相同的密钥,相比非对称加密,因只有一把钥匙,因而速度更快,更适合加解密大文件

2、常见算法

  • DES:data encryption standard,已经过时
  • AES:advanced encryption standard,替代des
  • 其他如:3DES、Blowfish、IDEA、RC4、RC5、RC6

3、分类

  • 分组加密,又叫快加密
  • 序列加密

4、块加密常用的加密模式

  • ECB

定义: electronic code book,电码本模式,将整个明文分成若干段相同的小段,然后对每一小段进行加密。特点: 每段之间互不依赖,可以并行处理;同样的明文总是生成同样的密文
密码学-java信息安全,摘要算法,对称加密(AES)/非对称加密(RSA)_第2张图片

  • CBC

定义: cipher block chaining,密文分组链模式,所谓链,即密文分组之间像链条一样相与连接在一起。先将明文切分成若干小段,然后每一小段与上一段的密文段(第一个块因没有上一个密文段,使用的是IV进行运算后,再与密钥进行加密
特点: 串行处理,同样的明文每次生成的密文不一样。
密码学-java信息安全,摘要算法,对称加密(AES)/非对称加密(RSA)_第3张图片

5、测试用例

DES测试用例

package com.yypttest.Utils;

import org.apache.commons.codec.binary.Base64;
import org.junit.Test;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

public class DesTest {
    //算法类型
    private static final String ALGORITHM = "DES";
    //密钥 DES的秘钥默认是8位秘钥
    private static final String KEY = "12345677";
    //字符集
    private static final String UTF8 = StandardCharsets.UTF_8.name();

    /**
     * 加密
     * @param text 待加密的内容
     * @return
     */
    public String encrypt(String text) throws Exception{
        //获取实例
        Cipher instance = Cipher.getInstance(ALGORITHM);
        /*
        创建加解密的规则
         */
        SecretKey secretKey = new SecretKeySpec(KEY.getBytes(UTF8), ALGORITHM);
        /*
        初始化
        第一个参数:加解密模式(加密模式)
        第二个参数:规则
         */
        instance.init(Cipher.ENCRYPT_MODE,secretKey);
        //获取加密数组
        byte[] encodedBytes = instance.doFinal(text.getBytes(UTF8));

        //展示字节数组,1:使用base64,2:使用转成16进制字符串
        //这里使用base64
        return Base64.encodeBase64String(encodedBytes);
    }

    /**
     * 解密
     * @param text 加密后的字符串
     * @return
     * @throws Exception
     */
    public String decrypt(String text) throws Exception{
        //获取实例
        Cipher instance = Cipher.getInstance(ALGORITHM);
        /*
        创建加解密的规则
         */
        SecretKey secretKey = new SecretKeySpec(KEY.getBytes(UTF8), ALGORITHM);
        /*
        初始化
        第一个参数:加解密模式(解密模式)
        第二个参数:规则
         */
        instance.init(Cipher.DECRYPT_MODE,secretKey);
        byte[] decryptedBytes = instance.doFinal(Base64.decodeBase64(text.getBytes(UTF8)));
        return new String(decryptedBytes,UTF8);
    }

    @Test
    public void test()throws Exception{
        String str = "对称加密DES模式测试";
        String encrypt = encrypt(str);
        String decrypt = decrypt(encrypt);
        System.out.println("加密后的结果:"+encrypt);
        System.out.println("解码后的结果:"+decrypt);
    }
}

AES测试用例

package com.yypttest.Utils;

import org.apache.commons.codec.binary.Base64;
import org.junit.Test;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

public class AesTest {
    //算法类型
    private static final String ALGORITHM = "AES";
    //密钥,AES的秘钥规定是16、24、32位秘钥
    private static final String KEY = "12345678qwerasdf";
    //字符集
    private static final String UTF8 = StandardCharsets.UTF_8.name();

    /**
     * 加密
     * @param text 待加密的内容
     * @return
     */
    public String encrypt(String text) throws Exception{
        //获取实例
        Cipher instance = Cipher.getInstance(ALGORITHM);
        /*
        创建加解密的规则
         */
        SecretKey secretKey = new SecretKeySpec(KEY.getBytes(UTF8), ALGORITHM);
        /*
        初始化
        第一个参数:加解密模式(加密模式)
        第二个参数:规则
         */
        instance.init(Cipher.ENCRYPT_MODE,secretKey);
        //获取加密数组
        byte[] encodedBytes = instance.doFinal(text.getBytes(UTF8));

        //展示字节数组,1:使用base64,2:使用转成16进制字符串
        //这里使用base64
        return MD5Test.convertBytes2HexStr(encodedBytes);
    }

    /**
     * 解密
     * @param text 加密后的字符串
     * @return
     * @throws Exception
     */
    public String decrypt(String text) throws Exception{
        byte[] bytes = MD5Test.convertHex2Btyes(text);
        //获取实例
        Cipher instance = Cipher.getInstance(ALGORITHM);
        /*
        创建加解密的规则
         */
        SecretKey secretKey = new SecretKeySpec(KEY.getBytes(UTF8), ALGORITHM);
        /*
        初始化
        第一个参数:加解密模式(解密模式)
        第二个参数:规则
         */
        instance.init(Cipher.DECRYPT_MODE,secretKey);
        byte[] decryptedBytes = instance.doFinal(bytes);
        return new String(decryptedBytes,UTF8);
    }

    @Test
    public void test()throws Exception{
        String str = "对称加密AES模式,采用16进制编码测试";
        String encrypt = encrypt(str);
        String decrypt = decrypt(encrypt);
        System.out.println("加密后的结果:"+encrypt);
        System.out.println("解码后的结果:"+decrypt);
    }
}

解决AES和DES的key默认固定了长度问题

package com.yypttest.Utils;

import org.junit.Test;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;

public class AesTest {
    //算法类型
    private static final String ALGORITHM = "AES";
    //密钥
    private static final String KEY = "12345678qsdf";
    //字符集
    private static final String UTF8 = StandardCharsets.UTF_8.name();

    /**
     * 加密
     * @param text 待加密的内容
     * @return
     */
    public String encrypt(String text) throws Exception{

        //获取实例
        Cipher instance = Cipher.getInstance(ALGORITHM);

        /*
        解决aes和des默认固定key的字符长度问题
         */
        //创建keyGenerator,可以根据传入的key,生成一个指定长度的key
        KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
        //初始化SecureRandom,指定生成key的算法
        SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
        //指定原始传入的key
        secureRandom.setSeed(KEY.getBytes(UTF8));
        //指定生成新的key的长度
        keyGenerator.init(128,secureRandom);
        //通过keyGenerator生成原始key
        SecretKey secretKey = keyGenerator.generateKey();
        //获取到新密钥的字节数组
        byte[] encoded = secretKey.getEncoded();
        /*
        创建加解密的规则
         */
        SecretKey secretKeySpec = new SecretKeySpec(encoded, ALGORITHM);
        /*
        初始化
        第一个参数:加解密模式(加密模式)
        第二个参数:规则
         */
        instance.init(Cipher.ENCRYPT_MODE,secretKeySpec);
        //获取加密数组
        byte[] encodedBytes = instance.doFinal(text.getBytes(UTF8));

        //展示字节数组,1:使用base64,2:使用转成16进制字符串
        //这里使用base64
        return MD5Test.convertBytes2HexStr(encodedBytes);
    }

    /**
     * 解密
     * @param text 加密后的字符串
     * @return
     * @throws Exception
     */
    public String decrypt(String text) throws Exception{
        byte[] bytes = MD5Test.convertHex2Btyes(text);
        //获取实例
        Cipher instance = Cipher.getInstance(ALGORITHM);

        /*
        解决aes和des默认固定key的字符长度问题
         */
        //创建keyGenerator,可以根据传入的key,生成一个指定长度的key
        KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
        //初始化SecureRandom,指定生成key的算法
        SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
        //指定原始传入的key
        secureRandom.setSeed(KEY.getBytes(UTF8));
        //指定生成新的key的长度
        keyGenerator.init(128,secureRandom);
        //通过keyGenerator生成原始key
        SecretKey secretKey = keyGenerator.generateKey();
        //获取到新密钥的字节数组
        byte[] encoded = secretKey.getEncoded();
        /*
        创建加解密的规则
         */
        SecretKey secretKeySpec = new SecretKeySpec(encoded, ALGORITHM);
        /*
        初始化
        第一个参数:加解密模式(解密模式)
        第二个参数:规则
         */
        instance.init(Cipher.DECRYPT_MODE,secretKeySpec);
        byte[] decryptedBytes = instance.doFinal(bytes);
        return new String(decryptedBytes,UTF8);
    }

    @Test
    public void test()throws Exception{
        String str = "对称加密AES模式,采用16进制编码测试,key可以任意长度";
        String encrypt = encrypt(str);
        String decrypt = decrypt(encrypt);
        System.out.println("加密后的结果:"+encrypt);
        System.out.println("解码后的结果:"+decrypt);
    }
}

6、块加密常用的填充模式

为什么要有?对于固定的加密算法,每个块有固定大小(BlockSize),比如8个byte,明文分块后,加密前需要保证对最后一个块的大小为8个byte,如果不够则使用特定数据进行填充。

  • NoPadding:不自动填充
    des时要求原文必须是8个字节的整数倍,aes时是16个字节的整数倍
  • PKCS5Padding(限制了块大小为8个byte的PKCS7Padding)/PKCS7Padding
    PKCS: Public-Key Cryptography standards,公钥密码学标准

测试用例

package com.yypttest.Utils;

import org.junit.Test;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
/**
 * @Author Jiangjinlong
 * @Date 2023/3/14 11:12
 * @PackageName:com.yypttest.Utils
 * @ClassName: AesTestTwo
 * @Description: 测试加密模式和填充模式
 */
public class AesTestTwo {
    /*
    AES加密默认的加密默认就是:ECB,默认的填充模式就是:PKCS5Padding
     */
    /*
    AES使用CBC加密模式时需要传入IV
     */
    //算法类型
    private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
    private static final String ALGORITHM_TYPE = "AES";
    //密钥
    private static final String KEY = "12345678qsdf";
    //定义IV,默认规定16个字节
    private static final String IV = "12345678qsdfasdf";
    //字符集
    private static final String UTF8 = StandardCharsets.UTF_8.name();
    /**
     * 加密
     * @param text 待加密的内容
     * @return
     */
    public String encrypt(String text) throws Exception{

        //获取实例
        Cipher instance = Cipher.getInstance(ALGORITHM);

        /*
        解决aes和des默认固定key的字符长度问题
         */
        //创建keyGenerator,可以根据传入的key,生成一个指定长度的key
        KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM_TYPE);
        //初始化SecureRandom,指定生成key的算法
        SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
        //指定原始传入的key
        secureRandom.setSeed(KEY.getBytes(UTF8));
        //指定生成新的key的长度
        keyGenerator.init(128,secureRandom);
        //通过keyGenerator生成原始key
        SecretKey secretKey = keyGenerator.generateKey();
        //获取到新密钥的字节数组
        byte[] encoded = secretKey.getEncoded();
        /*
        创建加解密的规则
         */
        SecretKey secretKeySpec = new SecretKeySpec(encoded, ALGORITHM_TYPE);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes(UTF8));
        /*
        初始化
        第一个参数:加解密模式(加密模式)
        第二个参数:规则
        第三个参数:使用CBC加密模式时需要多传一个ivParameterSpec向量
         */
        instance.init(Cipher.ENCRYPT_MODE,secretKeySpec,ivParameterSpec);
        //获取加密数组
        byte[] encodedBytes = instance.doFinal(text.getBytes(UTF8));

        //展示字节数组,1:使用base64,2:使用转成16进制字符串
        //这里使用base64
        return MD5Test.convertBytes2HexStr(encodedBytes);
    }

    /**
     * 解密
     * @param text 加密后的字符串
     * @return
     * @throws Exception
     */
    public String decrypt(String text) throws Exception{
        byte[] bytes = MD5Test.convertHex2Btyes(text);
        //获取实例
        Cipher instance = Cipher.getInstance(ALGORITHM);

        /*
        解决aes和des默认固定key的字符长度问题
         */
        //创建keyGenerator,可以根据传入的key,生成一个指定长度的key
        KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM_TYPE);
        //初始化SecureRandom,指定生成key的算法
        SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
        //指定原始传入的key
        secureRandom.setSeed(KEY.getBytes(UTF8));
        //指定生成新的key的长度
        keyGenerator.init(128,secureRandom);
        //通过keyGenerator生成原始key
        SecretKey secretKey = keyGenerator.generateKey();
        //获取到新密钥的字节数组
        byte[] encoded = secretKey.getEncoded();
        /*
        创建加解密的规则
         */
        SecretKey secretKeySpec = new SecretKeySpec(encoded, ALGORITHM_TYPE);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes(UTF8));
        /*
        初始化
        第一个参数:加解密模式(解密模式)
        第二个参数:规则
        第三个参数:使用CBC加密模式时需要多传一个ivParameterSpec向量
         */
        instance.init(Cipher.DECRYPT_MODE,secretKeySpec,ivParameterSpec);
        byte[] decryptedBytes = instance.doFinal(bytes);
        return new String(decryptedBytes,UTF8);
    }

    @Test
    public void test()throws Exception{
        String str = "对称加密AES模式,采用16进制编码测试";
        String encrypt = encrypt(str);
        String decrypt = decrypt(encrypt);
        System.out.println("加密后的结果:"+encrypt);
        System.out.println("解码后的结果:"+decrypt);
    }
}

四、非对称加密

1、定义

加密和解密使用的是两个不同的密钥 (public key 和 private key)。公钥可以给任何人,私钥总是自己保留。

2、为什么会出现?

对称加解密使用相同的秘钥,但对不同的原始内容加密会采用不同的秘钥,导致秘钥数量巨大,难以维护。

3、常见算法

  • RSA
  • 其他如: ECC,Diffie-Hellman,El Gamal,DSA

4 应用场景

  • 加解密

    可以使用公钥加密,对应的就是私钥解密;也可以使用私钥加密,对应的公钥解密

    测试用例

    package com.yypttest.Utils;
    
    import org.apache.commons.codec.binary.Base64;
    import org.apache.commons.io.FileUtils;
    import org.junit.Test;
    
    import javax.crypto.Cipher;
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.nio.charset.StandardCharsets;
    import java.security.*;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.security.spec.X509EncodedKeySpec;
    
    /**
     * @Author JiangJinlong
     * @Date 2023/3/14 13:22
     * @PackageName:com.yypttest.Utils
     * @ClassName: RsaTest
     * @Description: TODO
     */
    public class RsaTest {
        private static final String ALGORITHM = "RSA";
        private static final String UTF8 = StandardCharsets.UTF_8.name();
        private static final String publicKeyPath = "E:\\Work_file\\YyptTest\\src\\main\\resources\\rsa.pub";
        private static final String privateKeyPath = "E:\\Work_file\\YyptTest\\src\\main\\resources\\rsa.pri";
        /**
         * rsa单次最大加密的文明大小
         */
        private static final int MAX_ENCRYPT_BLOCK = 117;
    
        /**
         * rsa单次最大解密的密文大小
         */
        private static final int MAX_DECRYPT_BLOCK = 128;
    
        //获取公私钥文件位置,怀疑路径不能含中文
        /*
        private static final String publicKeyPath;
        private static final String privateKeyPath;
       static {
            ClassLoader cl = RsaTest.class.getClassLoader();
            publicKeyPath = cl.getResource("rsa.pub").getPath();
            privateKeyPath = cl.getResource("rsa.pri").getPath();
            System.out.println("公钥路径:"+publicKeyPath);
            System.out.println("私钥路径:"+privateKeyPath);
        }*/
    
        /**
         * 生成经过base64编码后的密钥对(公钥和私钥)并存储在文件中
         */
        private void writeKey2File() throws Exception{
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
            keyPairGenerator.initialize(1024);
            //通过keyPair生成器生成keyPair
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            /*
            公钥
             */
            PublicKey publicKey = keyPair.getPublic();
            //把对象转成字节数组
            byte[] encoded = publicKey.getEncoded();
            String publicKeyBase64Stri = Base64.encodeBase64String(encoded);
            /*
            私钥
             */
            PrivateKey privateKey = keyPair.getPrivate();
            //把对象转成字节数组
            byte[] privateKeyEncoded = privateKey.getEncoded();
            String privateKeyBase64Stri = Base64.encodeBase64String(privateKeyEncoded);
    
            //分别把公钥字符串和私钥字符串写入文件
            FileUtils.writeStringToFile(new File(publicKeyPath),publicKeyBase64Stri,UTF8);
            FileUtils.writeStringToFile(new File(privateKeyPath),privateKeyBase64Stri,UTF8);
    
        }
    
        /**
         * 从生成好的公钥文件rsa.pub(这里的公钥文件是通过Base64编码之后存储的数据)获取公钥对象
         * @return
         */
        private PublicKey getPulickey()throws Exception{
            //读取通过Base64编码之后的公钥文件
            String publicKeyBase64Str = FileUtils.readFileToString(new File(publicKeyPath), UTF8);
            //base64解码,获取字节数组
            byte[] decodeBase64 = Base64.decodeBase64(publicKeyBase64Str);
            //传入算法名字”RAS“
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
            //传入key的规则,对于公钥的规则就是x509,将公钥的字符串传入公钥规则里面
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(decodeBase64);
            return keyFactory.generatePublic(x509EncodedKeySpec);
        }
    
        /**
         * 从生成好的私钥文件rsa.pri(这里的公钥文件是通过Base64编码之后存储的数据)获取私钥对象
         * @return
         */
        private PrivateKey getPrivateKey()throws Exception{
            //读取通过Base64编码之后的公钥文件
            String privateKeyBase64Str = FileUtils.readFileToString(new File(privateKeyPath), UTF8);
            //base64解码,获取字节数组
            byte[] decodeBase64 = Base64.decodeBase64(privateKeyBase64Str);
            //传入算法名字”RAS“
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
            //传入key的规则,对于私钥的规则就是PKCS8,将公钥的字符串传入公钥规则里面
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(decodeBase64);
            return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        }
    
        /**
         * 加密
         * @param originalCont 原始内容
         * @param key 公钥或者私钥
         * @return base64编码后的加密内容
         * @throws Exception
         */
        public String encrypt(String originalCont, Key key) throws Exception{
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE,key);
            byte[] bytes = doCodec(cipher,originalCont.getBytes(UTF8),MAX_ENCRYPT_BLOCK);
            return Base64.encodeBase64String(bytes);
    
        }
    
        /**
         * 解密
         * @param encryptedStr 加密后内容
         * @param key 公钥或者私钥
         * @return 原始内容
         * @throws Exception
         */
        public String decrypt(String encryptedStr, Key key) throws Exception{
            byte[] decodeBase64 = Base64.decodeBase64(encryptedStr);
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE,key);
            byte[] decryptedBytes = doCodec(cipher,decodeBase64,MAX_DECRYPT_BLOCK);
            return new String(decryptedBytes);
    
        }
    
        /**
         * 执行加密或者解密
         * @param cipher
         * @param decodeBase64
         * @param maxDecryptBlock
         * @return
         */
        private byte[] doCodec(Cipher cipher, byte[] decodeBase64, int maxDecryptBlock) throws Exception{
            //获取字节长度
            int inputLen = decodeBase64.length;
            //迁移量
            int offset = 0;
            //本次完成加密之后的数组
            byte[] cache;
            //循环的次数
            int i= 0;
            //
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            while ((inputLen-offset)>0){
                if ((inputLen - offset)>maxDecryptBlock){
                    //第三个参数,要处理的长度
                    cache = cipher.doFinal(decodeBase64,offset,maxDecryptBlock);
    
                }else {
                    cache = cipher.doFinal(decodeBase64,offset,inputLen-offset);
                }
                baos.write(cache,0,cache.length);
                i++;
                offset = i*maxDecryptBlock;
            }
            //加密或者解密的结果
            byte[] bytes = baos.toByteArray();
            baos.close();
            return bytes;
        }
    
        /*
        生成公钥私钥,密钥对每次生成的都是不一样的,所以我们执行一次之后保存起来,其他时候就不需要在执行了
     */
        @Test
        public void testWriteKey() throws Exception{
            writeKey2File();
        }
    
        @Test
        public void testRsa()throws Exception{
            String str = "测试公钥加密————私钥解密";
            //测试公钥加密————私钥解密
            System.out.println("公钥加密结果:"+encrypt(str, getPulickey()));
            System.out.println("私钥解密结果:"+decrypt(encrypt(str, getPulickey()), getPrivateKey()));
            // 测试私钥加密————公钥解密
            String str1 = "测试私钥加密————公钥解密";
            System.out.println("私钥加密结果:"+encrypt(str1, getPrivateKey()));
            System.out.println("公钥解密结果:"+decrypt(encrypt(str1, getPrivateKey()), getPulickey()));
        }
    }
    
    
  • 数字签名
    发送方A: 原始内容-.->通过摘要算法获取原始内容的摘要str1–>把hash用发送方的私钥加密–>数字签名。
    接收方B: 用发送方的公钥验证签名并解密—>对原始内容进行摘要算法str2–.>比较str1==str2(保证未被篡改)
    注意:不是用公钥加密hash,如果用公钥,别人没你的私钥,怎么验证呢?

    因为签名是先进行摘要再进行rsa,所以在摘要算法一定的情况下,签名后得到的字符串长度总是一样的

    package com.yypttest.Utils;
    
    import org.apache.commons.codec.binary.Base64;
    import org.apache.commons.io.FileUtils;
    import org.junit.Test;
    
    import javax.crypto.Cipher;
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.nio.charset.StandardCharsets;
    import java.security.*;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.security.spec.X509EncodedKeySpec;
    
    /**
     * @Author JiangJinlong
     * @Date 2023/3/14 13:22
     * @PackageName:com.yypttest.Utils
     * @ClassName: RsaTest
     * @Description: TODO
     */
    public class SignatureTest {
        private static final String SIGNATURE_ALGORITHM = "sha256withrsa";
        @Test
        public void testRsa()throws Exception{
            String str = "测试数字签名SignatureTest";
            byte[] strBytes = str.getBytes();
            String sign = sign(strBytes);
            System.out.println("签名:"+sign);
            //校验
            boolean verify = verify(strBytes, sign);
            System.out.println("验证结果:"+verify);
    
        }
    
        /**
         * 对消息使用私钥生成数字签名
         * @param data 原始数据
         * @return
         * @throws Exception
         */
        private  static String sign(byte[] data)throws Exception{
            //获取私钥
            PrivateKey privateKey = new RsaTest().getPrivateKey();
            //用指定的算法初始化签名对象,先进行摘要再进行加密
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initSign(privateKey);
            signature.update(data);
            return MD5Test.convertBytes2HexStr(signature.sign());
        }
    
        /**
         * 校验数字签名
         * @param data 原始数据
         * @param sign 数字签名
         * @return
         * @throws Exception
         */
        private static boolean verify(byte[] data,String sign) throws Exception{
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initVerify(new RsaTest().getPulickey());
            signature.update(data);
            return signature.verify(MD5Test.convertHex2Btyes(sign));
        }
    }
    
    
  • 数字信封
    对称加密的秘钥分发不安全–>发送方用接收方的公钥进行加密–>接受方用私钥再解开
    注意:为什么会有分发秘钥呢? 不是约定好了吗? 结合我们前面的讲解,秘钥会着每个零件的数期一起发送给接收方。

  • 数字证书

    • Ca
      certificate authority,使用pkipublic key nfrastructure)技术的机构,也可自己内部搭建,如使用ejbca。
    • ca根证书
      Q:B除了存储A的数字证书对应的ca公钥,假设还有N个人给B发信息,难道B都要保存一份他们的数字证书的CA公钥吗?
      A: 不需要,CA认证中心给可以给B一份”根证书”,里面存储的CA公钥可以验证所有CA分中心颁发的数字证书

你可能感兴趣的:(学习笔记,jdk,java,密码学,算法)