1.JAVA加解密-基础篇

目录

目录

一.发展历史

1.2 近代密码学

1.3 现代密码学

二. 编码算法

2.1 Base64

2.2 URL编码

三. 摘要算法

3.1 定义

3.2 应用场景

3.3 常见算法

3.4 代码实现

3.4.1 MD5

3.4.2 SHA256

3.4.3 SHA512

3.4.4 MAC

四. 对称加密算法

4.1 定义

4.2 常见算法

4.3 分类

4.4 块加密常用的加密模式

4.5 块加密常用的填充模式

4.6 代码实现

4.6.1 DES代码实现

4.6.2 AES代码实现

4.6.3 对KEY长度进行优化

五. 非对称加密算法

5.1 定义

5.2 为什么会出现

5.3 常见算法

5.4 应用场景

6. 总结

6.1 内容概览

 6.2 编码总结


一.发展历史

1.1 古典密码学

凯撒密码,滚筒密码

1.2 近代密码学

Enigma机

1.3 现代密码学

二. 编码算法

不是加密和解密,只是为了方便传输数据或者本地存储而产生的。

2.1 Base64

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

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

(1)jdk实现

package com.john.javacrypto;

import org.junit.Test;

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

public class JdkBase64Test {

    @Test
    public void jdkBase64Encode() {

        String srcStr = "我的java加密课程";

        String encodedStr = jdkBase64Encode(srcStr);
        System.out.println("jdk base64 encoded str: " + encodedStr);

        String decodedStr = jdkBase64Decode(encodedStr);
        System.out.println("jdk base64 decoded str: " + decodedStr);

    }


    private String jdkBase64Encode(String srcStr){
        return Base64.getEncoder().encodeToString(srcStr.getBytes(StandardCharsets.UTF_8));
    }

    private String jdkBase64Decode(String encodedString){
        byte[] decodeBytes = Base64.getDecoder().decode(encodedString.getBytes(StandardCharsets.UTF_8));
        return new String(decodeBytes, StandardCharsets.UTF_8);
    }

}

(2)commons-codec实现

package com.john.javacrypto;

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

import java.nio.charset.StandardCharsets;

public class CommonsCodecBase64Test {

    @Test
    public void commonsCodecBase64Encode() {

        String srcStr = "我的java加密课程";

        String encodedStr = commonsCodecBase64Encode(srcStr);
        System.out.println("commonsCodec base64 encoded str: " + encodedStr);

        String decodedStr = commonsCodecBase64Decode(encodedStr);
        System.out.println("commonsCodec base64 decoded str: " + decodedStr);

    }


    private String commonsCodecBase64Encode(String srcStr){
        return Base64.encodeBase64String(srcStr.getBytes(StandardCharsets.UTF_8));
    }

    private String commonsCodecBase64Decode(String encodedString){
        byte[] decodeBytes = Base64.decodeBase64(encodedString.getBytes(StandardCharsets.UTF_8));
        return new String(decodeBytes, StandardCharsets.UTF_8);
    }

}

2.2 URL编码

表单提交时:application/x-www-form-urlencoded

(1)jdk实现

package com.john.javacrypto.urlencode;

import org.junit.Test;

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

public class JdkUrlEncodedTest {

    @Test
    public void testUrlEncode() throws UnsupportedEncodingException {
        String srcUrl = "我的java加密课程";

        String encodedUrl = urlEncode(srcUrl);
        //encodedUrl: %E6%88%91%E7%9A%84java%E5%8A%A0%E5%AF%86%E8%AF%BE%E7%A8%8B
        System.out.println("encodedUrl: " + encodedUrl);

        String decodedUrl = urlDecode(encodedUrl);
        //decodedUrl: 我的java加密课程
        System.out.println("decodedUrl: " + decodedUrl);
    }

    private String urlEncode(String srcUrl) throws UnsupportedEncodingException {
        return URLEncoder.encode(srcUrl, StandardCharsets.UTF_8.name());
    }

    private String urlDecode(String encodedUrl) throws UnsupportedEncodingException {
        return URLDecoder.decode(encodedUrl, StandardCharsets.UTF_8.name());
    }
}

(2)commons-codec实现

package com.john.javacrypto.urlencode;

import org.apache.commons.codec.net.URLCodec;
import org.junit.Test;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;

public class CommonsCodecUrlCodeTest {

    @Test
    public void testUrlEncode() throws Exception {
        String srcUrl = "我的java加密课程";

        String encodedUrl = urlEncode(srcUrl);
        //encodedUrl: %E6%88%91%E7%9A%84java%E5%8A%A0%E5%AF%86%E8%AF%BE%E7%A8%8B
        System.out.println("encodedUrl: " + encodedUrl);

        String decodedUrl = urlDecode(encodedUrl);
        //decodedUrl: 我的java加密课程
        System.out.println("decodedUrl: " + decodedUrl);
    }

    private String urlEncode(String srcUrl) throws UnsupportedEncodingException {
        URLCodec urlCodec = new URLCodec();
        return urlCodec.encode(srcUrl, StandardCharsets.UTF_8.name());
    }

    private String urlDecode(String encodedUrl) throws Exception {
        URLCodec urlCodec = new URLCodec();
        return urlCodec.decode(encodedUrl, StandardCharsets.UTF_8.name());
    }

}

三. 摘要算法

3.1 定义

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

3.2 应用场景

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

3.3 常见算法

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

3.4 代码实现

3.4.1 MD5

(1)JDK实现

package com.john.javacrypto.messagedigest;

import org.junit.Test;

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

public class JdkMD5 {

    @Test
    public void test() throws Exception {
        String srcString = "你好,Hellow World!";
        byte[] srcBytes = srcString.getBytes(StandardCharsets.UTF_8);

        MessageDigest md5 = MessageDigest.getInstance("MD5");
        byte[] digestBytes = md5.digest(srcBytes);
        /**
         * 当srcBytes较大时:可以将srcBytes进行切分,然后循环update
         * md5.update(srcBytes);
         * md5.digest();
         */
        //md5 hex string: 4ff1dfc5c3f2f3ebc03a7747b6453597
        System.out.println("md5 hex string: " + converBytes2HexStr(digestBytes));

    }

    private String converBytes2HexStr(byte[] bytes) {

        StringBuilder res = new StringBuilder();
        for (byte b : bytes) {
            //获取b的补码的后8位
            String hex = Integer.toHexString((int) b & 0xFF);
            if (hex.length() == 1) {
                res.append("0");
            }
            res.append(hex);
        }
        return res.toString();
    }
}

(2)commons-codec实现

package com.john.javacrypto.messagedigest;

import org.apache.commons.codec.digest.DigestUtils;
import org.junit.Test;

public class CodecMD5 {

    @Test
    public void test() {
        String srcString = "你好,Hellow World!";
        //md5 hex string: 4ff1dfc5c3f2f3ebc03a7747b6453597
        System.out.println("md5 hex string: " + DigestUtils.md5Hex(srcString));
    }

}

3.4.2 SHA256

(1)JDK实现

package com.john.javacrypto.sha;

import com.john.javacrypto.utils.MessageDigestUtils;
import org.junit.Test;

public class SHA256Test {

    @Test
    public void test() throws Exception {
        String srcString = "你好,Hellow World!";
        //SHA-256 hex string: 422b4d37714568aade2818bf6ae0ea41bb9d89f36b13cc101986c4440b15019d
        System.out.println("SHA-256 hex string: " + MessageDigestUtils.doDigestAlgorithm(srcString, "SHA-256"));
    }
}

(2)codec实现

package com.john.javacrypto.sha;

import org.apache.commons.codec.digest.DigestUtils;
import org.junit.Test;

public class CodecSHA256 {

    @Test
    public void test() {
        String srcString = "你好,Hellow World!";
        //sha256 hex string: 422b4d37714568aade2818bf6ae0ea41bb9d89f36b13cc101986c4440b15019d
        System.out.println("sha256 hex string: " + DigestUtils.sha256Hex(srcString));
    }

}

3.4.3 SHA512

(1)JDK实现

package com.john.javacrypto.sha;

import com.john.javacrypto.utils.MessageDigestUtils;
import org.junit.Test;

public class SHA512Test {

    @Test
    public void test() throws Exception {
        String srcString = "你好,Hellow World!";
        //SHA-256 hex string: 87e7644b3913cb4c98735dde05520e7340989a5cf47294d7ba33d2e80f13b7fd09a8971bd440d4b58a02b12533815442efa12efc4a1dc2153a8c7d32be07d1a1
        System.out.println("SHA-256 hex string: " + MessageDigestUtils.doDigestAlgorithm(srcString, "SHA-512"));
    }
}

(2)codec实现

package com.john.javacrypto.sha;

import org.apache.commons.codec.digest.DigestUtils;
import org.junit.Test;

public class CodecSHA512 {

    @Test
    public void test() {
        String srcString = "你好,Hellow World!";
        //sha256 hex string: 87e7644b3913cb4c98735dde05520e7340989a5cf47294d7ba33d2e80f13b7fd09a8971bd440d4b58a02b12533815442efa12efc4a1dc2153a8c7d32be07d1a1
        System.out.println("sha256 hex string: " + DigestUtils.sha512Hex(srcString));
    }

}

3.4.4 MAC

(1)JDK实现

package com.john.javacrypto.mac;

import com.john.javacrypto.utils.MessageDigestUtils;
import org.junit.Test;

public class MacTest {

    private static final String srcString = "你好,Hellow World!";
    private static final String key = "123";

    @Test
    public void testHmacMD5() throws Exception {
        String algo = "HmacMD5";
        //8c53b339bf5a34113aa4273d9ee224f9
        System.out.println(MessageDigestUtils.doMacDigest(srcString, key, algo));
    }
    @Test
    public void testHmacSHA256() throws Exception {
        String algo = "HmacSHA256";
        //bb693cfa6f80f73e79a3149e09c59fa6a02c8d0c28de4414e8bb9d316c9b82ba
        System.out.println(MessageDigestUtils.doMacDigest(srcString, key, algo));
    }
    @Test
    public void testHmacSHA512() throws Exception {
        String algo = "HmacSHA512";
        //1e0458f2502a0ca993ff345ee7695e02388c2dda49cc6f64630a18d4e9512e5afe10e814875efe9517116afab4e89a21156f05a87f722d3538ebcd6fc47a96b9
        System.out.println(MessageDigestUtils.doMacDigest(srcString, key, algo));
    }

}
package com.john.javacrypto.utils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MessageDigestUtils {

    public static String doDigest(String srcString, String algo) throws NoSuchAlgorithmException {

        byte[] srcBytes = srcString.getBytes(StandardCharsets.UTF_8);

        MessageDigest md5 = MessageDigest.getInstance(algo);
        byte[] digestBytes = md5.digest(srcBytes);

        return HexUtils.converBytes2HexStr(digestBytes);
    }

    public static String doMacDigest(String srcString, String key, String algo) throws Exception {

        byte[] srcBytes = srcString.getBytes(StandardCharsets.UTF_8);

        Mac mac = Mac.getInstance(algo);
        Key secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), algo);
        mac.init(secretKey);
        byte[] bytes = mac.doFinal(srcBytes);

        return HexUtils.converBytes2HexStr(bytes);
    }
}

(2)codec实现

package com.john.javacrypto.mac;

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

public class CodecMacTest {

    private static final String srcString = "你好,Hellow World!";
    private static final String key = "123";

    @Test
    public void testHmacMD5() {
        //8c53b339bf5a34113aa4273d9ee224f9
        System.out.println(HmacUtils.hmacMd5Hex(key, srcString));
    }

    @Test
    public void testHmacSHA256() throws Exception {
        //bb693cfa6f80f73e79a3149e09c59fa6a02c8d0c28de4414e8bb9d316c9b82ba
        System.out.println(HmacUtils.hmacSha256Hex(key, srcString));
    }

    @Test
    public void testHmacSHA512() throws Exception {
        //1e0458f2502a0ca993ff345ee7695e02388c2dda49cc6f64630a18d4e9512e5afe10e814875efe9517116afab4e89a21156f05a87f722d3538ebcd6fc47a96b9
        System.out.println(HmacUtils.hmacSha512Hex(key, srcString));
    }

}

四. 对称加密算法

4.1 定义

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

4.2 常见算法

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

4.3 分类

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

4.4 块加密常用的加密模式

  • ECB:

electronic code book,电码本模式,将整个明文分成若干段相同的小段,然后对每一小段进行加密。每段之间相互不依赖,可以并行处理;同样的明文总是生成同样的密文。

1.JAVA加解密-基础篇_第1张图片

  •  CBC:

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

只能进行串行处理,同样的明文使用不同的IV会生成不同的密文。

1.JAVA加解密-基础篇_第2张图片

4.5 块加密常用的填充模式

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

  • no padding

  des时要求原文必需是8byte的整数倍,aes时要求是16byte的整数倍

  • PKCS5Padding

填充模式的应用:

package com.john.javacrypto.symmetric;

import org.apache.commons.codec.binary.Base64;
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.Key;
import java.security.SecureRandom;

public class AesPaddingTest {
    /**
     * AES的默认填充模式是ECB,加密模式是PKCS5Padding
     */
    private static final String ALGO = "AES/CBC/PKCS5Padding";
    private static final String ALGO_TYPE = "AES";
    private static final String IV = "abcdefghabcdefgh";
    private static final String KEY = "1234567";

    @Test
    public void test() throws Exception {

        String originString = "你好,Hellow World!";
        String encryptString = encrypt(originString);
        //AES加密后结果: YFVHAt/hd7ktgeIT7XO6evHKb3kLtBsbZEF4JwSwJXE=
        System.out.println("AES加密后结果: " + encryptString);
        //解密后结果: 你好,Hellow World!
        System.out.println("解密后结果: " + decrypt(encryptString));

    }

    private String encrypt(String text) throws Exception {
        Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, KEY);
        byte[] encryptBytes = cipher.doFinal(text.getBytes(StandardCharsets.UTF_8));

        return Base64.encodeBase64String(encryptBytes);
    }

    private String decrypt(String text) throws Exception {

        Cipher cipher = getCipher(Cipher.DECRYPT_MODE, KEY);
        byte[] encryptBytes = cipher.doFinal(Base64.decodeBase64(text.getBytes(StandardCharsets.UTF_8)));

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


    private Cipher getCipher(int type, String seed) throws Exception {

        //设置生成指定长度key的算法,在这里就是:"SHA1PRNG"
        SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
        secureRandom.setSeed(seed.getBytes(StandardCharsets.UTF_8));

        KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGO_TYPE);
        //keySize只能是128, 192 or 256
        keyGenerator.init(128, secureRandom);
        SecretKey secretKey = keyGenerator.generateKey();

        Key keySpec = new SecretKeySpec(secretKey.getEncoded(), ALGO_TYPE);
        Cipher cipher = Cipher.getInstance(ALGO);
        //cbc的时候需要
        IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8));

        cipher.init(type, keySpec, ivParameterSpec);
        return cipher;
    }


}

4.6 代码实现

4.6.1 DES代码实现

package com.john.javacrypto.symmetric;

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

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

public class DesTest {

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

    @Test
    public void test() throws Exception {

        String originString = "你好,Hellow World!";
        String encryptString = encrypt(originString);
        //DES加密后结果: bVI453QkPGSL9kLjZJZmMgyFomSEPn9V
        System.out.println("DES加密后结果: " + encryptString);
        //解密后结果: 你好,Hellow World!
        System.out.println("解密后结果: " + decrypt(encryptString));

    }

    private String encrypt(String text) throws Exception {
        Key keySpec = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), ALGO);

        Cipher cipher = Cipher.getInstance(ALGO);
        cipher.init(Cipher.ENCRYPT_MODE, keySpec);
        byte[] encryptBytes = cipher.doFinal(text.getBytes(StandardCharsets.UTF_8));

        return Base64.encodeBase64String(encryptBytes);
    }

    private String decrypt(String text) throws Exception {
        Key keySpec = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), ALGO);

        Cipher cipher = Cipher.getInstance(ALGO);
        cipher.init(Cipher.DECRYPT_MODE, keySpec);
        byte[] encryptBytes = cipher.doFinal(Base64.decodeBase64(text.getBytes(StandardCharsets.UTF_8)));

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

4.6.2 AES代码实现

package com.john.javacrypto.symmetric;

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

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.NoSuchAlgorithmException;

public class AesTest {

    private static final String ALGO = "AES";
    //支持的Key的长度:AESConstants.AES_KEYSIZES 16, 24, 32
    private static final String KEY = "1234567890123456";

    @Test
    public void test() throws Exception {

        String originString = "你好,Hellow World!";
        String encryptString = encrypt(originString);
        //AES加密后结果: 3Nqo7tqj314tCXoqVDPoAjJOmxzwZTlnsBHzxFf7qpk=
        System.out.println("AES加密后结果: " + encryptString);
        //解密后结果: 你好,Hellow World!
        System.out.println("解密后结果: " + decrypt(encryptString));

    }

    private String encrypt(String text) throws Exception {
        Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, KEY);
        byte[] encryptBytes = cipher.doFinal(text.getBytes(StandardCharsets.UTF_8));

        return Base64.encodeBase64String(encryptBytes);
    }

    private String decrypt(String text) throws Exception {

        Cipher cipher = getCipher(Cipher.DECRYPT_MODE, KEY);
        byte[] encryptBytes = cipher.doFinal(Base64.decodeBase64(text.getBytes(StandardCharsets.UTF_8)));

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

    private Cipher getCipher(int type, String seed) throws Exception {
        Key keySpec = new SecretKeySpec(seed.getBytes(StandardCharsets.UTF_8), ALGO);
        Cipher cipher = Cipher.getInstance(ALGO);
        cipher.init(type, keySpec);
        return cipher;
    }
}

4.6.3 对KEY长度进行优化

package com.john.javacrypto.symmetric;

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

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public class AesTest {

    private static final String ALGO = "AES";
    //支持的Key的长度:AESConstants.AES_KEYSIZES 16, 24, 32
    private static final String KEY = "1234567";

    @Test
    public void test() throws Exception {

        String originString = "你好,Hellow World!";
        String encryptString = encrypt(originString);
        //AES加密后结果: 3Nqo7tqj314tCXoqVDPoAjJOmxzwZTlnsBHzxFf7qpk=
        System.out.println("AES加密后结果: " + encryptString);
        //解密后结果: 你好,Hellow World!
        System.out.println("解密后结果: " + decrypt(encryptString));

    }

    private String encrypt(String text) throws Exception {
        Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, KEY);
        byte[] encryptBytes = cipher.doFinal(text.getBytes(StandardCharsets.UTF_8));

        return Base64.encodeBase64String(encryptBytes);
    }

    private String decrypt(String text) throws Exception {

        Cipher cipher = getCipher(Cipher.DECRYPT_MODE, KEY);
        byte[] encryptBytes = cipher.doFinal(Base64.decodeBase64(text.getBytes(StandardCharsets.UTF_8)));

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

    /**
     * seed的长度需要进行控制
     * @param type
     * @param seed
     * @return
     * @throws Exception
     */
    private Cipher getCipherSimple(int type, String seed) throws Exception {
        Key keySpec = new SecretKeySpec(seed.getBytes(StandardCharsets.UTF_8), ALGO);
        Cipher cipher = Cipher.getInstance(ALGO);
        cipher.init(type, keySpec);
        return cipher;
    }

    private Cipher getCipher(int type, String seed) throws Exception {

        //设置生成指定长度key的算法,在这里就是:"SHA1PRNG"
        SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
        secureRandom.setSeed(seed.getBytes(StandardCharsets.UTF_8));

        KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGO);
        //keySize只能是128, 192 or 256
        keyGenerator.init(128, secureRandom);
        SecretKey secretKey = keyGenerator.generateKey();

        Key keySpec = new SecretKeySpec(secretKey.getEncoded(), ALGO);
        Cipher cipher = Cipher.getInstance(ALGO);
        cipher.init(type, keySpec);
        return cipher;
    }


}

五. 非对称加密算法

5.1 定义

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

5.2 为什么会出现

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

5.3 常见算法

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

5.4 应用场景

  • 加解密

    使用公钥加密,私钥解密;或者使用私钥加密,公钥解密。

    代码:

package com.john.javacrypto.asymmetric;

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;

public class RsaTest {

    private static final String ALGO = "RSA";
    private static String PUBLICKEY_PATH;
    private static String PRIVATEKEY_PATH;

    /**
     * RSAd按此加密的明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;
    private static final int MAX_DECRYPT_BLOCK = 128;

    static {
        PUBLICKEY_PATH = "D:/Java/workspace/java-crypto/src/main/resources/rsa.pub";
        PRIVATEKEY_PATH = "D:/Java/workspace/java-crypto/src/main/resources/rsa.pri";
    }

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

    @Test
    public void testRsa() throws Exception {

        String msg = "你好,hellow world!";

        //公钥加密,私钥解密
        String encryptedByPublicKey = encrypt(msg, getPublicKey());
        //D8udHGoFH0Gk0apeYMOYg/ZmSkwYabekc0HaIoOd1XsDKombghn8qLSmCW1bzpkRW1Z8cLxc3ymgaQ4B5klPR0LrXhV5rp2Td9I1TB0Z/kKa7cEUyDnDroCScGTo8e/gZYJ/h6ycuQQqffSwp64UJXdTyMPRXPz5pnI2sIVLeho=
        System.out.println("公钥加密后的数据: " + encryptedByPublicKey);
        //你好,hellow world!
        String decryptedByPrivateKey = decrypt(encryptedByPublicKey, getPrivateKey());
        System.out.println("私钥解密后的数据:" + decryptedByPrivateKey);

        //私钥加密,公钥解密
        String encryptedByPrivateKey = encrypt(msg, getPrivateKey());
        //H7ihIEiQXFuJXPG/CNuzEnYhL8ELHhHIw165P9A6iOARoChsaccwM/quhbTVMofAIwOJxpfxDgau8M22/dm6aaNpor5+qr9CaC4RztFagHT7AOa9YTc7E+aFxEf2/lfw77FYt3OP4xNlZ5dJmxHJZovfQviosFlPejjzr+57dfY=
        System.out.println("私钥加密后的数据: " + encryptedByPublicKey);
        String decryptedByPublicKey = decrypt(encryptedByPrivateKey, getPublicKey());
        //你好,hellow world!
        System.out.println("公钥解密后的数据:" + decryptedByPrivateKey);

    }

    public String encrypt(String srcString, Key key) throws Exception {
        Cipher cipher = Cipher.getInstance(ALGO);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] encryptedBytes = doCodec(cipher, srcString.getBytes(StandardCharsets.UTF_8), MAX_ENCRYPT_BLOCK);
        return Base64.encodeBase64String(encryptedBytes);
    }

    public String decrypt(String encryptedStr, Key key) throws Exception {
        byte[] encryptedBytes = Base64.decodeBase64(encryptedStr);

        Cipher cipher = Cipher.getInstance(ALGO);
        cipher.init(Cipher.DECRYPT_MODE, key);
        byte[] decryptedBytes = doCodec(cipher, encryptedBytes, MAX_DECRYPT_BLOCK);
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }

    private byte[] doCodec(Cipher cipher, byte[] bytes, int maxBlockSize) throws Exception {
        int inputlen = bytes.length;
        int offset = 0;
        byte[] cache;
        int i = 0;
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        //循环分段处理
        while (inputlen > offset) {
            if ((inputlen - offset) > maxBlockSize) {
                cache = cipher.doFinal(bytes, offset, maxBlockSize);
            } else {
                cache = cipher.doFinal(bytes, offset, (inputlen - offset));
            }
            byteArrayOutputStream.write(cache, 0, cache.length);
            offset = (++i) * maxBlockSize;
        }

        byte[] res = byteArrayOutputStream.toByteArray();
        byteArrayOutputStream.close();
        return res;

    }

    /**
     * 从生成好的公钥文件rsa.pub(经Base64编码后存储的)中获取公钥
     *
     * @return
     */
    private PublicKey getPublicKey() throws Exception {
        String publicKeyBase64String = FileUtils.readFileToString(new File(PUBLICKEY_PATH), StandardCharsets.UTF_8);
        byte[] pubKeyBytes = Base64.decodeBase64(publicKeyBase64String);

        //公钥的规则就是X509
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(pubKeyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(ALGO);
        return keyFactory.generatePublic(x509EncodedKeySpec);
    }

    /**
     * 从生成好的私钥文件rsa.pri(经Base64编码后存储的)中获取公钥
     *
     * @return
     */
    private PrivateKey getPrivateKey() throws Exception {
        String privateKeyBase64String = FileUtils.readFileToString(new File(PRIVATEKEY_PATH), StandardCharsets.UTF_8);
        byte[] priKeyBytes = Base64.decodeBase64(privateKeyBase64String);

        //私钥的规则就是PKCS8
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(priKeyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(ALGO);
        return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
    }

    /**
     * 生成公私钥文件
     */
    private void writeKey2File() throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGO);
        keyPairGenerator.initialize(1024);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();

        PublicKey publicKey = keyPair.getPublic();
        byte[] publicKeyEncoded = publicKey.getEncoded();
        String publicKeyBase64String = Base64.encodeBase64String(publicKeyEncoded);
        FileUtils.writeStringToFile(new File(PUBLICKEY_PATH), publicKeyBase64String, StandardCharsets.UTF_8);

        PrivateKey privateKey = keyPair.getPrivate();
        byte[] privateKeyEncoded = privateKey.getEncoded();
        String privateKeyBase64String = Base64.encodeBase64String(privateKeyEncoded);
        FileUtils.writeStringToFile(new File(PRIVATEKEY_PATH), privateKeyBase64String, StandardCharsets.UTF_8);

    }
}
  • 数字签名

发送方:原始内容--->通过摘要算法获取hash值str1 --->把hash用发送方的私钥加密--->数字签名

接收方:用发送方的用药校验签名并解密--->对邮件内容进行摘要算法str2--->比较str1 == str2(保证未被串改)

注意:不是用公钥加密hash,如果用公钥,但是别人没有你的私钥,进而无法进行验证。

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

目的:验证发送发就是发送发,校验完整性。

代码:

package com.john.javacrypto.asymmetric;

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

import java.security.PrivateKey;
import java.security.Signature;

public class SignatureTest {

    private static final String ALGO = "sha256withrsa";

    public static void main(String[] args) throws Exception{
        String content = "你好,hellow world!";
        byte[] contentBytes = content.getBytes();
        String sign = sign(contentBytes);
        //签名:5c9668d62a0affd74cc036450ac2fe18610f759d60d42bf1234ec4a521b906ecc74694aa71f8ecfb8e92108739290ccd6161ef9e75b1f44d799d1e9ce8d037e12c1c95e14190d54b532f97d3a65d6f06dc5f50ef9af718fb8357f3537458d96cb0368b87b7a878aefd18d923c592449755fce4c1c9b41e3a3303d3403fe7b5ca
        System.out.println("签名:" + sign);
        //验证结果:true
        boolean status = verify(contentBytes, sign);
        System.out.println("验证结果:" + status);
    }

    private static String sign(byte[] data) throws Exception{
        PrivateKey privateKey = RsaTest.getPrivateKey();
        Signature signature = Signature.getInstance(ALGO);

        signature.initSign(privateKey);
        signature.update(data);
        return Hex.encodeHexString(signature.sign());
    }

    private static boolean verify(byte[] data, String sign) throws Exception{
        Signature signature = Signature.getInstance(ALGO);
        signature.initVerify(RsaTest.getPublicKey());
        signature.update(data);
        return signature.verify(Hex.decodeHex(sign));
    }
}
  • 数字信封

对称加密的密钥分发不安全--->接收方的rsa公钥对密钥进行加密--->接收方用rsa私钥进行解密

注意:密钥需要分发的原因是由于密钥会随着不同数据类型而使用不同的密钥,因此需要将对应数据类型的密钥一起发送给接收方。

  • 数字证书

考虑以下情景:

A将信息发送给B,B中存储了A的公钥,这时候,A先用A自己的私钥对信息进行数字签名,然后B拿着A的公钥进行验证。

如果在AB之外有个C,将B的中应该是A的公钥换成了C的公钥,A发送的信息也换成了C的私钥生成的签名,那么A向B发送消息时,消息会被C换掉,并且B无法知道消息已经被篡改。

所以我们需要有一种手段来证明“B中存储的A的公钥就是A的公钥”。

1.JAVA加解密-基础篇_第3张图片

  • ca:

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

  • ca根证书:

Q: B除了存储A的数字证书对应的ca公钥,假设还有N个人给B发信息,那么N个数字证书的CA公钥。

A:CA认证中心可以给B一份“根证书”,里面存储的CA公钥可以验证所有CA分中心办法的数字证书。 

代码:

package com.john.javacrypto.asymmetric;

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

import java.io.FileInputStream;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;

public class NumberCertificateTest {

    public static void main(String[] args) throws Exception {
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        String filePah = "D:/Java/workspace/java-crypto/src/main/resources/test.cer";

        FileInputStream in = new FileInputStream(filePah);
        Certificate certificate = cf.generateCertificate(in);
        PublicKey publicKey = certificate.getPublicKey();
        String base64PublicKey = Base64.encodeBase64String(publicKey.getEncoded());
        //解析数字证书中的公钥为: MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmSh5SHDDqFSmafnVmTTZdhBoZK
        System.out.println("解析数字证书中的公钥为: " + base64PublicKey);
    }
}

证书:只需要从chrome中导出一份证书就可以进行测试了

-----BEGIN CERTIFICATE-----
MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk
MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH
bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD
QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc
8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke
hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI
KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg
515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO
xwy8p2Fp8fc74SrL+SvzZpA3
-----END CERTIFICATE-----

全文代码下载链接:

https://github.com/JohnZhaowen/crypto.git

6. 总结

6.1 内容概览

1.JAVA加解密-基础篇_第4张图片

 6.2 编码总结

1.JAVA加解密-基础篇_第5张图片

 

你可能感兴趣的:(加解密,java,开发语言,后端)