当前位置: 首页 > news >正文

密码相关----对称加密,非对称加密

文章目录

  • 二、编码算法
    • 2.1 base64
      • jdk1.8提供的base64类
      • codec的base64
    • 2.2 URL编码
      • jdk提供的工具
  • 三、摘要算法
    • 3.1 常见算法
      • MD5
        • jdk原生实现
        • codec的api调用
        • spring中的md5
      • sha256
        • jdk原生实现
        • codec实现方式
      • sha512
        • jdk原生实现
        • codec实现
      • Mac
        • jdk中HmacMD5
        • jdk中HmacSHA256
        • jdk中HmacSHA512
        • codec中实现方式
  • 四、对称加密
    • 常见算法
      • DES
        • jdk实现DES
      • AES
        • jdk实现AES
        • key长度处理
    • 分类
    • 块加密常用的加密模式
    • 块加密常用的填充模式
  • 五、非对称加密
    • 常见算法
    • 应用场景
      • 加解密
      • 数字签名
      • 数字信封
      • 数字证书

一、发展历史

1.1 古典密码学

如凯撒密码、滚筒密码

1.2 近代密码学

如德国Enigma机,被图灵破解

1.3 现代密码学

二、编码算法

不是加密和解密,为了在网络间更方便的传输数据而产生

2.1 base64

由 A-Z、a-z、0-9、+、/ 共64个字符组成,去掉 i I o O + / 即base58

注意:base64以三个字节为一组,如果最后一组不足3个字节,则使用=号补充。

jdk1.8提供的base64类

import org.junit.Test;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

/**
 * jdk base64测试
 *
 * @author zs
 * @date 2022-09-02
 */
public class jdkBase64Test {

    @Test
    public void test01() {
        String str = "小白一起学编程";

        //编码
        String encodeStr = Base64.getEncoder().encodeToString(str.getBytes( StandardCharsets.UTF_8));
        System.out.println(encodeStr);

        //解码
        String decodeStr = new String(Base64.getDecoder().decode(encodeStr.getBytes(StandardCharsets.UTF_8)),StandardCharsets.UTF_8);
        System.out.println(decodeStr);
    }
}

codec的base64

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.15</version>
</dependency>
import org.apache.commons.codec.binary.Base64;
import org.junit.Test;
import java.nio.charset.StandardCharsets;


/**
 * 编解码器base64测试
 *
 * @author zs
 * @date 2022-09-03
 */
public class codecBase64Test {

    @Test
    public void test01()  {
        String str = "小白一起学编程";

        //编码
        String encodeStr = Base64.encodeBase64String(str.getBytes(StandardCharsets.UTF_8));
        System.out.println(encodeStr);

        //解码
        String decodeStr = new String(Base64.decodeBase64(encodeStr.getBytes(StandardCharsets.UTF_8)),StandardCharsets.UTF_8);
        System.out.println(decodeStr);
    }
}

2.2 URL编码

jdk提供的工具

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

public class URLTest {

    @Test
    public void test01() throws UnsupportedEncodingException {
        String str = "小白一起学编程";

        //编码
        String encode = URLEncoder.encode(str, StandardCharsets.UTF_8.name());
        System.out.println(encode);

        //解码
        String decode = URLDecoder.decode(encode, StandardCharsets.UTF_8.name());
        System.out.println(decode);

    }
}

三、摘要算法

  • 定义

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

  • 应用场景

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

3.1 常见算法

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

MD5

jdk原生实现

import org.junit.Test;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * jdk md5测试
 *
 * @author zs
 * @date 2022-09-03
 */
public class JdkMd5Test {

    @Test
    public void test01() throws NoSuchAlgorithmException {
        String str = "小白一起学编程";
        String algorithm = "MD5";

        // 获取消息摘要算法对象
        MessageDigest md = MessageDigest.getInstance(algorithm);

        // 获取原始内容的字节数组
        byte[] originalBytes = str.getBytes(StandardCharsets.UTF_8);

        // 获取摘要结果
        byte[] digestBytes = md.digest(originalBytes);

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

        //md5占128位->16字节->1字节=8位 可以转为一个16进制 --> 16个16进制的数
        //将每个字节转为16进制字符,最终拼接起来即可
        String hexStr = convertBytes2HexStr(digestBytes);

        System.out.println("hexStr:" + hexStr);

    }

    private String convertBytes2HexStr(byte[] digestBytes) {
        StringBuilder sb = new StringBuilder();
        //循环16个字节
        for (byte b : digestBytes) {
            //获取b的补码的后8位
            String hex = Integer.toHexString(((int)b)&0xff);
            // 15 --> Integer.toHexString(15&0xff) --> 0f
            // 16 --> Integer.toHexString(16&0xff)
            if (hex.length() == 1) {
                hex = "0" +hex;
            }
            sb.append(hex);
        }
        return sb.toString();
    }
}
//hexStr:2d4c6bc71a670cce33f75b9691488742

简单封装

public class HexUtils {

    /**
     * 把字节数组转为16进制字符串,如果一个字节转为16进制字符后不足两位,则在前面补0
     *
     * @param digestBytes 字节数组
     * @return {@link String}
     */
    public static String convertBytes2HexStr(byte[] digestBytes) {
        StringBuilder sb = new StringBuilder();
        //循环16个字节
        for (byte b : digestBytes) {
            //获取b的补码的后8位
            String hex = Integer.toHexString(((int)b)&0xff);
            // 15 --> Integer.toHexString(15&0xff) --> 0f
            // 16 --> Integer.toHexString(16&0xff)
            if (hex.length() == 1) {
                hex = "0" +hex;
            }
            sb.append(hex);
        }
        return sb.toString();
    }
}
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

public class MessageDigestUtils {

    /**
     * 执行消息摘要
     *
     * @param originalContent 原创字符串
     * @param algorithm       算法名称
     * @return {@link String} 摘要内容
     */
    public static String doDigest(String originalContent,String algorithm) {
        try {
            // 获取消息摘要算法对象
            MessageDigest md = MessageDigest.getInstance(algorithm);

            // 获取原始内容的字节数组
            byte[] originalBytes = originalContent.getBytes(StandardCharsets.UTF_8);

            // 获取摘要结果
            byte[] digestBytes = md.digest(originalBytes);

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

            //md5占128位->16字节->1字节=8位 可以转为一个16进制 --> 16个16进制的数
            //将每个字节转为16进制字符,最终拼接起来即可
            return HexUtils.convertBytes2HexStr(digestBytes);
        }catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

测试

/**
 * jdk md5测试
 *
 * @author zs
 * @date 2022-09-03
 */
public class JdkMd5Test {
    @Test
    public void test01() throws NoSuchAlgorithmException {
        String str = "小白一起学编程";
        String algorithm = "MD5";
        String hexStr = MessageDigestUtils.doDigest(str, algorithm);
        System.out.println("hexStr:" + hexStr);

    }
}
//hexStr:2d4c6bc71a670cce33f75b9691488742

codec的api调用

codec中byte数组转hex字符串方法

@Test
public void test02(){
  byte[] bytes = new byte[]{15,16};
  System.out.println(Hex.encodeHexString(bytes));
}

MD5

@Test
public void test04(){
    String str = "小白一起学编程";
    System.out.println(org.apache.commons.codec.digest.DigestUtils.md5Hex(str));
}

spring中的md5

@Test
public void test03(){
    String str = "小白一起学编程";
    System.out.println(DigestUtils.md5DigestAsHex(str.getBytes(StandardCharsets.UTF_8)));
}

sha256

jdk原生实现

@Test
public void test01() throws NoSuchAlgorithmException {
    String str = "小白一起学编程";
    String algorithm = "SHA-256";
    String hexStr = MessageDigestUtils.doDigest(str, algorithm);
    System.out.println("hexStr:" + hexStr);
}

codec实现方式

@Test
public void test04(){
    String str = "小白一起学编程";
    System.out.println(DigestUtils.sha256Hex(str));
}

sha512

jdk原生实现

@Test
public void test01() throws NoSuchAlgorithmException {
    String str = "小白一起学编程";
    String algorithm = "SHA-512";
    String hexStr = MessageDigestUtils.doDigest(str, algorithm);
    System.out.println("hexStr:" + hexStr);
}

codec实现

@Test
public void test04(){
    String str = "小白一起学编程";
    System.out.println(DigestUtils.sha512Hex(str));
}

Mac

jdk中HmacMD5

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

public class MessageDigestUtils {
    
    /**
     * 执行消息摘要
     *
     * @param originalContent 原创字符串
     * @param algorithm       算法名称
     * @return {@link String} 摘要内容
     */
    public static String doMacDigest(String originalContent,String key,String algorithm) {
        try {
            // 获取消息摘要算法对象
            Mac mac = Mac.getInstance(algorithm);

            // 获取key对象并初始化mac
            SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8),algorithm);
            mac.init(secretKey);

            // 获取原始内容的字节数组
            byte[] originalBytes = originalContent.getBytes(StandardCharsets.UTF_8);

            // 获取到摘要结果
            byte[] digestBytes = mac.doFinal(originalBytes);

            // 把每一个字节转为16进制字符,最终再拼接起来这些16进制字符
            return HexUtils.convertBytes2HexStr(digestBytes);
        }catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
public class HexUtils {

    /**
     * 把字节数组转为16进制字符串,如果一个字节转为16进制字符后不足两位,则在前面补0
     *
     * @param digestBytes 字节数组
     * @return {@link String}
     */
    public static String convertBytes2HexStr(byte[] digestBytes) {
        StringBuilder sb = new StringBuilder();
        //循环16个字节
        for (byte b : digestBytes) {
            //获取b的补码的后8位
            String hex = Integer.toHexString(((int)b)&0xff);
            // 15 --> Integer.toHexString(15&0xff) --> 0f
            // 16 --> Integer.toHexString(16&0xff)
            if (hex.length() == 1) {
                hex = "0" +hex;
            }
            sb.append(hex);
        }
        return sb.toString();
    }
}
import org.junit.Test;
import java.security.NoSuchAlgorithmException;

public class JdkMd5Test {
    /**
     * jdk HmacMd5 实现方式
     *
     * @throws NoSuchAlgorithmException 没有这样算法异常
     */
    @Test
    public void test01() throws NoSuchAlgorithmException {
        String str = "小白一起学编程";

        String algorithm = "HmacMD5";
        // 指定秘钥,mac摘要和digest算法(md5,sha)不同的地方就是加了盐
        String key = "123";
        String hexStr = MessageDigestUtils.doMacDigest(str,key, algorithm);
        System.out.println("hexStr:" + hexStr);
    }
}

jdk中HmacSHA256

@Test
public void test02() throws NoSuchAlgorithmException {
    String str = "小白一起学编程";

    String algorithm = "HmacSHA256";
    // 指定秘钥,mac摘要和digest算法(md5,sha)不同的地方就是加了盐
    String key = "123";
    String hexStr = MessageDigestUtils.doMacDigest(str,key, algorithm);
    System.out.println("hexStr:" + hexStr);
}

jdk中HmacSHA512

/**
 * jdk HmacSHA512 实现方式
 *
 * @throws NoSuchAlgorithmException 没有这样算法异常
 */
@Test
public void test03() throws NoSuchAlgorithmException {
    String str = "小白一起学编程";

    String algorithm = "HmacSHA512";
    // 指定秘钥,mac摘要和digest算法(md5,sha)不同的地方就是加了盐
    String key = "123";
    String hexStr = MessageDigestUtils.doMacDigest(str,key, algorithm);
    System.out.println("hexStr:" + hexStr);
}

codec中实现方式

@Test
public void test04() throws NoSuchAlgorithmException {
    String str = "小白一起学编程";
    String key = "123";

    //HmacUtils.hmacMd5Hex(); 过时
    String hmacMD5HexStr = new HmacUtils(HmacAlgorithms.HMAC_MD5, key.getBytes(StandardCharsets.UTF_8))
            .hmacHex(str.getBytes(StandardCharsets.UTF_8));
    String hmacSHA256HexStr = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, key.getBytes(StandardCharsets.UTF_8))
            .hmacHex(str.getBytes(StandardCharsets.UTF_8));
    String hmacSHA512HexStr = new HmacUtils(HmacAlgorithms.HMAC_SHA_512, key.getBytes(StandardCharsets.UTF_8))
            .hmacHex(str.getBytes(StandardCharsets.UTF_8));

    System.out.println(hmacMD5HexStr);
    System.out.println(hmacSHA256HexStr);
    System.out.println(hmacSHA512HexStr);
}

四、对称加密

定义:

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

常见算法

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

DES加密,只允许密钥是8个字节的。

AES加密,密钥必须是16个字节的.

也就是说 key=“12345678”可以,key="123456789"就会报错。

DES

jdk实现DES

import org.apache.commons.net.util.Base64;
import org.junit.Test;


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


public class DesTest {

    private static final String ALGORITHM = "DES";
    private static final String KEY = "12345678";


    /**
     * 加密
     *
     * @param text 待加密的内容
     * @return {@link String}
     * @throws Exception 异常
     */
    private String encrypt(String text) throws Exception {

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

        /*
        创建加解密的规则
         */
        SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM);

        //指定加密模式还是解密模式,秘钥对象
        /*
        1. 加解密模式 2.秘钥规则
         */
        cipher.init(Cipher.ENCRYPT_MODE,secretKey);

        byte[] encodedBytes = cipher.doFinal(text.getBytes(StandardCharsets.UTF_8));

        /*
        加密的字节数组如何展示?
        第一种:Base64
        第二种:转成16进制字符串
         */
        return Base64.encodeBase64String(encodedBytes);
    }


    /**
     * 解密
     *
     * @param text 待解密的内容
     * @return {@link String}
     * @throws Exception 异常
     */
    private String decrypt(String text) throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE,secretKey);

        byte[] decryptedBytes = cipher.doFinal(Base64.decodeBase64(text.getBytes(StandardCharsets.UTF_8)));

        return new String(decryptedBytes,StandardCharsets.UTF_8);
    }


    @Test
    public void test() throws Exception {
        String encode = encrypt("小白一起学编程");
        System.out.println(encode);

        String decode = decrypt(encode);
        System.out.println(decode);
    }
}

AES

jdk实现AES

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

public class CipherUtils {

    private static final String ALGORITHM = "AES";
    /**
     * 得到cipher对象
     *
     * @param type 加解密模式
     * @param seed 秘钥key
     * @return {@link Cipher}
     */
    public static Cipher getCipher(int type,String seed) throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        SecretKeySpec secretKey = new SecretKeySpec(seed.getBytes(StandardCharsets.UTF_8), ALGORITHM);
        cipher.init(type,secretKey);
        return cipher;
    }
}
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.junit.Test;

import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;

public class AesTest {

    @Test
    public void encrypt() throws Exception {
        //加密
        String text = "小白一起学编程";
        Cipher cipher1 = CipherUtils.getCipher(Cipher.ENCRYPT_MODE, "12345678");
        byte[] encodedBytes = cipher1.doFinal(text.getBytes(StandardCharsets.UTF_8));

        String baseStr = Base64.encodeBase64String(encodedBytes);
        String hexStr = Hex.encodeHexString(encodedBytes);
        System.out.println(baseStr);
        System.out.println(hexStr);


        //解密
        Cipher cipher2 = CipherUtils.getCipher(Cipher.DECRYPT_MODE, "12345678");
        byte[] decodeBytes1 = cipher2.doFinal(Base64.decodeBase64(baseStr.getBytes(StandardCharsets.UTF_8)));
        byte[] decodeBytes2 = cipher2.doFinal(Hex.decodeHex(hexStr));
        String decodeStr1 = new String(decodeBytes1, StandardCharsets.UTF_8);
        String decodeStr2 = new String(decodeBytes2, StandardCharsets.UTF_8);
        System.out.println(decodeStr1);
        System.out.println(decodeStr2);
    }

}

key长度处理

/**
 * 得到cipher对象
 *
 * @param type 加解密模式
 * @param seed 秘钥key
 * @return {@link Cipher}
 */
public static Cipher getCipherIgnoreKeyLength(int type,String seed) throws Exception {
    Cipher cipher = Cipher.getInstance(ALGORITHM);

    // 创建keyGenerator对象,可以根据传入的key生成一个指定长度的key
    KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
    // 初始化secureRandom,并指定生成指定长度key的算法
    SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
    secureRandom.setSeed(seed.getBytes(StandardCharsets.UTF_8));
    keyGenerator.init(128,secureRandom);
    // 通过keyGenerator生成新的秘钥
    SecretKey secretKey = keyGenerator.generateKey();
    byte[] encoded = secretKey.getEncoded();

    SecretKeySpec secretKeySpec = new SecretKeySpec(encoded, ALGORITHM);
    cipher.init(type,secretKey);
    return cipher;
}
@Test
public void test02() throws Exception {
    //加密
    String text = "小白一起学编程";
    Cipher cipher1 = CipherUtils.getCipherIgnoreKeyLength(Cipher.ENCRYPT_MODE, "0123456789abcdef12341234");
    byte[] encodedBytes = cipher1.doFinal(text.getBytes(StandardCharsets.UTF_8));

    String baseStr = Base64.encodeBase64String(encodedBytes);
    String hexStr = Hex.encodeHexString(encodedBytes);
    System.out.println(baseStr);
    System.out.println(hexStr);


    //解密
    Cipher cipher2 = CipherUtils.getCipherIgnoreKeyLength(Cipher.DECRYPT_MODE, "0123456789abcdef12341234");
    byte[] decodeBytes1 = cipher2.doFinal(Base64.decodeBase64(baseStr.getBytes(StandardCharsets.UTF_8)));
    byte[] decodeBytes2 = cipher2.doFinal(Hex.decodeHex(hexStr));
    String decodeStr1 = new String(decodeBytes1, StandardCharsets.UTF_8);
    String decodeStr2 = new String(decodeBytes2, StandardCharsets.UTF_8);
    System.out.println(decodeStr1);
    System.out.println(decodeStr2);
}

分类

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

块加密常用的加密模式

  • ECB (默认加密模式)

    定义:electronic code book, 电码本模式,将整个明文分成若干段相同的小段,然后对每一小段进行加密。

    特点:每段之间互不依赖,可以并行处理,同样的明文总是生成同样的密文

image-20220903150148843

  • CBC

    定义:cipher block chaining,密文分组链模式,所谓链,即密文分组之间像链条一样相互连接在一起。先将明文切分成若干小段,然后每一小段与上一段的密文段(第一个块因没有上一个密文段,使用的是IV)进行运算后,再与密钥进行加密

    特点:串行处理;同样的明文每次生成的密文不一样。

    image-20220903150504237

    private static final String ALGORITHM = "AES";
    private static final String ALGORITHM_TYPE_CBC = "AES/CBC/PKCS5Padding";
    private static final String ALGORITHM_TYPE_ECB = "AES/ECB/PKCS5Padding";

		/**
     * 得到cipher对象
     *
     * @param type 加解密模式
     * @param seed 秘钥key
     * @return {@link Cipher}
     */
    public static Cipher getCipherCBC(int type,String seed) throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM_TYPE_CBC);
        SecretKeySpec secretKey = new SecretKeySpec(seed.getBytes(StandardCharsets.UTF_8), ALGORITHM);
        //iv向量
        IvParameterSpec ivParameterSpec = new IvParameterSpec("1234123412341236".getBytes(StandardCharsets.UTF_8));
        cipher.init(type,secretKey,ivParameterSpec);
        return cipher;
    }


    /**
     * 得到cipher对象
     *
     * @param type 加解密模式
     * @param seed 秘钥key
     * @return {@link Cipher}
     */
    public static Cipher getCipherECB(int type,String seed) throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM_TYPE_ECB);
        SecretKeySpec secretKey = new SecretKeySpec(seed.getBytes(StandardCharsets.UTF_8), ALGORITHM);
        cipher.init(type,secretKey);
        return cipher;
    }




    @Test
    public void test03() throws Exception {
        //加密
        String text = "小白一起学编程";
        Cipher cipher1 = CipherUtils.getCipherCBC(Cipher.ENCRYPT_MODE, "0123456789abcdef");
        byte[] encodedBytes = cipher1.doFinal(text.getBytes(StandardCharsets.UTF_8));

        String baseStr = Base64.encodeBase64String(encodedBytes);
        String hexStr = Hex.encodeHexString(encodedBytes);
        System.out.println(baseStr);
        System.out.println(hexStr);


        //解密
        Cipher cipher2 = CipherUtils.getCipherCBC(Cipher.DECRYPT_MODE, "0123456789abcdef");
        byte[] decodeBytes1 = cipher2.doFinal(Base64.decodeBase64(baseStr.getBytes(StandardCharsets.UTF_8)));
        byte[] decodeBytes2 = cipher2.doFinal(Hex.decodeHex(hexStr));
        String decodeStr1 = new String(decodeBytes1, StandardCharsets.UTF_8);
        String decodeStr2 = new String(decodeBytes2, StandardCharsets.UTF_8);
        System.out.println(decodeStr1);
        System.out.println(decodeStr2);
    }


    @Test
    public void test04() throws Exception {
        //加密
        String text = "小白一起学编程";
        Cipher cipher1 = CipherUtils.getCipherECB(Cipher.ENCRYPT_MODE, "0123456789abcdef");
        byte[] encodedBytes = cipher1.doFinal(text.getBytes(StandardCharsets.UTF_8));

        String baseStr = Base64.encodeBase64String(encodedBytes);
        String hexStr = Hex.encodeHexString(encodedBytes);
        System.out.println(baseStr);
        System.out.println(hexStr);


        //解密
        Cipher cipher2 = CipherUtils.getCipherECB(Cipher.DECRYPT_MODE, "0123456789abcdef");
        byte[] decodeBytes1 = cipher2.doFinal(Base64.decodeBase64(baseStr.getBytes(StandardCharsets.UTF_8)));
        byte[] decodeBytes2 = cipher2.doFinal(Hex.decodeHex(hexStr));
        String decodeStr1 = new String(decodeBytes1, StandardCharsets.UTF_8);
        String decodeStr2 = new String(decodeBytes2, StandardCharsets.UTF_8);
        System.out.println(decodeStr1);
        System.out.println(decodeStr2);
    }

块加密常用的填充模式

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

  • NoPadding: 不自动填充

    des时要求原文必须是8个字节的整数倍,aes时是16个字节的整数倍

  • PKCS5Padding(限制了块大小为8个byte的PKCS7Padding) /PKCS7Padding

    PKCS:Public-Key Cryptography Standards,公钥密码学标准

五、非对称加密

定义

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

为什么会出现

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

对称加密容易破解,非对称不容易破解

常见算法

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

image-20220903221355034

image-20220903221623383

应用场景

  • 加解密

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

  • 数字签名

  • 数字信封

  • 数字证书

加解密

package com.zs.codeTest.asymmetric;

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

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * rsa测试
 *
 * @author zs
 * @date 2022-09-03
 */
public class RsaTest {

    private static final String ALGORITHM = "RSA";

    private static String publicKeyPath;
    private static String privateKeyPath;


    /**
     * RSA单次最大加密的明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;
    /**
     * RSA单次最大解密的密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;

    static {
        ClassLoader cl = RsaTest.class.getClassLoader();
        publicKeyPath = cl.getResource("rsa.pub").getPath();
        privateKeyPath = cl.getResource("rsa.pri").getPath();
    }


    /**
     * 加密
     *
     * @param originalCount 原始数
     * @param key           公钥或私钥
     * @return {@link String} base64b编码后的加密内容
     */
    public String encrypt(String originalCount, Key key) throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] bytes = doCodec(cipher, originalCount.getBytes(StandardCharsets.UTF_8),MAX_ENCRYPT_BLOCK);
        return Base64.encodeBase64String(bytes);
    }

    /**
     * 解密
     *
     * @param encryptedStr 加密后的内容
     * @param key          公钥或私钥
     * @return {@link String} 原始内容
     */
    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, StandardCharsets.UTF_8);
    }

    /**
     * 执行加密或者解密
     *
     * @param cipher       密码
     * @param bytes        字节
     * @param maxBlockSize 最大block大小
     * @return {@link byte[]}
     */
    private byte[] doCodec(Cipher cipher, byte[] bytes, int maxBlockSize) throws Exception {
        //字节数组的长度
        int inputLen = bytes.length;
        //偏移量,相当于指针来用
        int offset = 0;

        //分段缓冲数组
        byte[] cache;

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        //循环变量
        int i = 0;
        //循环分段处理
        while ((inputLen - offset) > 0) {
            //判断处理数据的长度是否需要分段处理
            if ((inputLen - offset) > maxBlockSize) {
                //第三个参数是要处理多长
                cache = cipher.doFinal(bytes,offset,maxBlockSize);
            } else {
                //不需要分段处理
                cache = cipher.doFinal(bytes,offset,inputLen - offset);
            }
            //把当前cache的内容存起来
            baos.write(cache,0,cache.length);
            //偏移量的增加
            offset = ++i * maxBlockSize;
        }
        //加密或解密后的结果
        byte[] codecBytes = baos.toByteArray();
        baos.close();
        return codecBytes;
    }


    /**
     * 获取公钥
     * 从生成好的公钥文件rsa.pub(进过base64编码后存储的)中获取公钥对象
     *
     * @return {@link PublicKey}
     */
    private PublicKey getPublicKey() throws Exception {
        String publicKeyBase64Str = FileUtils.readFileToString(new File(publicKeyPath), StandardCharsets.UTF_8);
        byte[] decodeBase64 = Base64.decodeBase64(publicKeyBase64Str);
        //公钥的规则就是 x509
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(decodeBase64);
        //生成公钥对象
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
        return keyFactory.generatePublic(x509EncodedKeySpec);
    }

    /**
     * 获取私钥
     * 从生成好的私钥文件rsa.pri(进过base64编码后存储的)中获取私钥对象
     *
     * @return {@link PublicKey}
     */
    private PrivateKey getPrivateKey() throws Exception {
        String privateKeyBase64Str = FileUtils.readFileToString(new File(privateKeyPath), StandardCharsets.UTF_8);
        byte[] decodeBase64 = Base64.decodeBase64(privateKeyBase64Str);
        // 私钥的规则就是 PKCS8
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(decodeBase64);
        //生成公钥对象
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
        return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
    }

    /**
     * 生成经过base64编码后的密钥对(公钥/私钥)并存储在文件中
     */
    private void writeKey2File() throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
        keyPairGenerator.initialize(1024);
        // 通过KeyPair生成器生成KeyPair
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        //生成公钥对象,获取公钥编码值 把对象转成字节数组,转成base64字符串
        PublicKey publicKey = keyPair.getPublic();
        byte[] publicKeyEncoded = publicKey.getEncoded();
        String publicKeyBase64Str = Base64.encodeBase64String(publicKeyEncoded);
        //生成私钥对象,获取私钥编码值 把对象转成字节数组,转成base64字符串
        PrivateKey privateKey = keyPair.getPrivate();
        byte[] privateKeyEncoded = privateKey.getEncoded();
        String privateKeyBase64Str = Base64.encodeBase64String(privateKeyEncoded);

        //分别吧公钥字符串和私钥字符串写入文件
        FileUtils.writeStringToFile(new File(URLDecoder.decode(publicKeyPath, StandardCharsets.UTF_8.name())),
                publicKeyBase64Str,StandardCharsets.UTF_8);
        FileUtils.writeStringToFile(new File(URLDecoder.decode(privateKeyPath, StandardCharsets.UTF_8.name())),
                privateKeyBase64Str,StandardCharsets.UTF_8);
    }


    @Test
    public void testWriteKey2File() throws Exception {
        writeKey2File();
    }

    @Test
    public void testRsa() throws Exception {
        String str = "小白一起学编程";
        // 测试  公钥加密 --> 私钥解密
        String encryptedStr = encrypt(str, getPublicKey());
        System.out.println("公钥加密结果:" + encryptedStr);
        String decryptedStr = decrypt(encryptedStr, getPrivateKey());
        System.out.println("私钥解密结果:" + decryptedStr);

        // 测试  私钥加密 --> 公钥解密
        encryptedStr = encrypt(str, getPrivateKey());
        System.out.println("私钥加密结果:" + encryptedStr);
        decryptedStr = decrypt(encryptedStr, getPublicKey());
        System.out.println("公钥解密结果:" + decryptedStr);
    }

}

公钥加密结果:nnc5MyIAICAliEa8yyyo7z3h2aurGDXFvuDs0dXaSGvu1YeWNl/4s6p21UaW3nzwGu5GcaEXyh57J0TDSSrmO8V2Mlr2EMWXnvUQ0LhjK/eY89TwXfvGNS75Dx6BWizEPenIBMA4zQGXUJOJWMdIwKCu9MCnkzxPMUW2ZhXBElk=
私钥解密结果:小白一起学编程
私钥加密结果:Ds81AdB/fCP2ULDe70AKTq4zwmofmaWAy6riY98OSeGOkJxrszdclrddnf/fe371py5HZxL+8YC7CzBSidoWqjVkrjJFZ257vpmIq54p6zcmdvrRIDl1qHn7rrttMP3YR+jY9PAx87pG61Fm+jEkS5F+kiM9y6WzQ8fDt7f5nlQ=
公钥解密结果:小白一起学编程

注意:秘钥需要先生成到文件中

数字签名

发送方A:

原始内容

① 摘要算法 ==》原始内容摘要 str

②摘要 str ==》A私钥加密 ==》 数字签名。

③发送给 B: 数字签名 + A的公钥

接收方B:

① A公钥解密 数字签名 ==》摘要 str

package com.zs.codeTest.asymmetric;

import org.apache.commons.codec.binary.Hex;

import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;

/**
 * 签名测试
 *
 * @author zs
 * @date 2022-09-03
 */
public class SignatureTest {

//    public static final String SIGNTURE_ALGORITHM = "mD5withRSA";//大小写无所谓
    public static final String SIGNTURE_ALGORITHM = "sha256withrsa";//大小写无所谓


    public static void main(String[] args) throws Exception {
        String content = "今晚8点,行动!";

        byte[] contentBytes = content.getBytes(StandardCharsets.UTF_8);
        String sign = sign(contentBytes);
        System.out.println("签名:" + sign);
        boolean status = verify(contentBytes,sign);
        System.out.println("验证结果:\r" + status);
    }


    /**
     * 对信息使用私钥生成数字签名
     *
     * @param data 原始数据
     * @return {@link String}
     */
    private static String sign(byte[] data) throws Exception {
        //获取私钥
        PrivateKey privateKey = new RsaTest().getPrivateKey();
        //用指定的算法初始化签名对象:进行摘要再进行加密
        Signature signature = Signature.getInstance(SIGNTURE_ALGORITHM);
        signature.initSign(privateKey);
        signature.update(data);

        return Hex.encodeHexString(signature.sign());
    }


    /**
     * 验证数字签名
     *
     * @param data 原始数据
     * @param sign 数字签名
     * @return boolean
     */
    private static boolean verify(byte[] data, String sign) throws Exception {
        Signature signature = Signature.getInstance(SIGNTURE_ALGORITHM);
        signature.initVerify(new RsaTest().getPublicKey());
        signature.update(data);

        return signature.verify(Hex.decodeHex(sign));
    }
}

数字信封

对称加密的秘钥分发不安全 ==》 发送方用接收方的公钥 【加密秘钥】 ==》 接收方用私钥解开

注意: 分发秘钥的场景,秘钥会随着每个零件的数据一起发送给接收方

数字证书

Q: 有了数字签名就可以证明发送者的身份了,还有什么问题?

A:B这里存储的A的公钥被换成C的公钥,A发送的信息也换了C的私钥生成的签名。现在需要一种手段,

来证明 B 这里存储的A的公钥,就是A的公钥

image-20220903222913713

涉及的一些概念:

  • ca

    certificate authority, 使用pki(public key Infrastructure) 技术的机构,也可自己内部搭建,如使用ejbca.

  • ca 根证书

    Q: B 除了存储A的数字证书对应的ca公钥,假设还有N个人给B发信息,难道B都要保存一份他们的数字证书的CA公钥吗?

    A:不需要,CA认证中心可以给B一份“根证书”,里面存储的CA公钥可以验证所有CA分中心颁发的数字证书

public class NumberCertificateTest {

    @Test
    public void test() throws Exception {
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        //证书是使用jdk自带的keytool工具生成的
        String filePath = NumberCertificateTest.class.getClassLoader().getResource("certificate/tomcat.pem").getPath();
        FileInputStream in = new FileInputStream(filePath);

        //生成一个证书对象并使用从输入流中读取的数据对他进行初始化
        Certificate c = cf.generateCertificate(in);
        PublicKey publicKey = c.getPublicKey();

      	//获取到了证书中的公钥
        String key = Base64.encodeBase64String(publicKey.getEncoded());
        System.out.println(key);
    }
}

相关文章:

  • 02.1、数据操作
  • 离散数学 --- 命题逻辑 -- 命题符号化与命题公式
  • 回坑记之或许是退役赛季?
  • 初识OpenGL (-)EBO元素缓冲对象(Element Buffer Object)
  • typescript真的有学习的必要吗?
  • PyTorch Lightning入门教程(二)
  • 【滤波跟踪】基于变分贝叶斯卡尔曼滤波器实现目标跟踪附matlab代码
  • C++ mutex 与 condition_variable
  • 基础 | Spring - [单例创建过程]
  • K8S集群Pod资源自动扩缩容方案
  • SPPNet
  • java多线程-多线程技能
  • 网课查题接口 该怎么搭建
  • Elasticsearch学习-- 聚合查询
  • 网课搜题公众号接口
  • 【干货分享】SpringCloud微服务架构分布式组件如何共享session对象
  • Github访问慢解决办法
  • JS变量作用域
  • mongodb--安装和初步使用教程
  • Next.js之基础概念(二)
  • React Transition Group -- Transition 组件
  • SpiderData 2019年2月16日 DApp数据排行榜
  • Swift 中的尾递归和蹦床
  • Swoft 源码剖析 - 代码自动更新机制
  • TypeScript实现数据结构(一)栈,队列,链表
  • vue-cli在webpack的配置文件探究
  • vue-loader 源码解析系列之 selector
  • Vue实战(四)登录/注册页的实现
  • WePY 在小程序性能调优上做出的探究
  • Zepto.js源码学习之二
  • 测试如何在敏捷团队中工作?
  • 基于Volley网络库实现加载多种网络图片(包括GIF动态图片、圆形图片、普通图片)...
  • 经典排序算法及其 Java 实现
  • 类orAPI - 收藏集 - 掘金
  • 前嗅ForeSpider中数据浏览界面介绍
  • 强力优化Rancher k8s中国区的使用体验
  • 通过几道题目学习二叉搜索树
  • 一起参Ember.js讨论、问答社区。
  •  一套莫尔斯电报听写、翻译系统
  • Python 之网络式编程
  • # Java NIO(一)FileChannel
  • $.proxy和$.extend
  • $L^p$ 调和函数恒为零
  • (附源码)python旅游推荐系统 毕业设计 250623
  • (附源码)spring boot儿童教育管理系统 毕业设计 281442
  • (理论篇)httpmoudle和httphandler一览
  • (每日持续更新)jdk api之StringBufferInputStream基础、应用、实战
  • (十五)Flask覆写wsgi_app函数实现自定义中间件
  • (详细版)Vary: Scaling up the Vision Vocabulary for Large Vision-Language Models
  • (原创)Stanford Machine Learning (by Andrew NG) --- (week 9) Anomaly DetectionRecommender Systems...
  • (转载)从 Java 代码到 Java 堆
  • * CIL library *(* CIL module *) : error LNK2005: _DllMain@12 already defined in mfcs120u.lib(dllmodu
  • .NET 使用 ILMerge 合并多个程序集,避免引入额外的依赖
  • .NET/C# 项目如何优雅地设置条件编译符号?
  • .sys文件乱码_python vscode输出乱码