一、AES和RSA

1、对称加密和非对称加密简介

目前常见的加密方式是有两种,一种是对称加密(AES为代表),一种是非对称加密(RSA为代表)。

对称加密只有一个秘钥,加密和解密都是用同一个秘钥,所以叫做对称加密。

非对称加密有两个秘钥,一个是公钥,一个是私钥。非对称的特点在于,公钥加密的私钥可以解密,但私钥加密的,公钥解不出来,只能验证是否由私钥进行加密

 这样可以保证就算有人拿到公钥,也解密不出私钥加密后的信息,公钥可以在网上安全的传输。而且公钥可以验证这个密文是不是由私钥加密出来的,也起到了验证的作用,一举两得。

一般公钥加密私钥解密的情况是用来传输数据,私钥加密公钥验证的情况是用来验证签名。

2、组合使用

对称加密(AES)的优势在于加密较快,但劣势在于秘钥一旦给出去就不安全了。非对称加密(RSA)的优势在于安全,就算提供公钥出去,别人也解密不了数据,但加密速度较慢。

实际使用的过程中常常将两者组合使用(AES+RSA):

1、先生成一个随机AES秘钥字符串

2、使用RSA公钥加密AES秘钥,然后再用AES秘钥加密真正的内容

3、把skey=加密的AES秘钥,body=AES秘钥加密的内容传过去。

4、对面使用RSA私钥解密AES秘钥,然后用AES秘钥解密出内容

这样可以安全的传输AES秘钥,避免了RSA加密的慢速度。

3、转换模式

在实际使用的过程中,往往提供过来的加密方法是这样一个格式 (AES/ECB/PKCS5Padding),这个格式为 算法/工作模式/填充模式,下面简单介绍一下:

算法:是指具体使用到的算法名称,比如“AES”,“RSA”,“SHA-256”。

工作模式:是指分组密码,工作模式需要加密的原始信息分成固定长度的数据块,然后用分组密码对这些数据块进行加密。目前有“ECB”,“CBC”,“CFB”,“OFB”,“CTR”五种工作模式。

填充模式:分组密码只能加密长度等于分组长度的单块数据,所以如果数据长度不足,就需要填充数据到匹配的长度。填充算法有 “NoPadding”, “PKCS#5”, “PKCS#7”, “ISO 10126”, “ANSI X9.23”和“ZerosPadding”。

常见的是“NoPadding”, “PKCS#5”, “PKCS#7”,“ZerosPadding”。

NoPadding:不填充。

PKCS#7 &  PKCS#5:缺几个字符就填几个缺的字节数,比如 |DD DD DD DD DD 03 03 03| (差3个字节,填3个03)。PKCS#5 和 PKCS#7 是完全一样的,不同的地方在于 PKCS#5 限制了块大小为 8 bytes 而  PKCS#7 没有限定。

ZerosPadding:全部填充0,无论缺多少都填0。

二、在Java 中的使用

1、AES工具类

package com.jing.app.common.encryption;


import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import javax.crypto.SecretKey;
import javax.crypto.KeyGenerator;
import java.security.Key;

/**
 * @author jing
 * @version 1.0
 * @desc AES 加解密工具类
 * @date 2021/2/7 0007 11:28
 **/
public class AESUtils {

    public static String MODE = "AES/ECB/PKCS5Padding";
    public static String KEY_ALGORITHM = "AES";
    public static String CHARSET = "utf-8";
    private static final int KEY_SIZE = 128;


    /**
	 * 获取密钥
	 * 
	 * @return
	 * @throws Exception
	 */
	private static Key getKey() throws Exception {
		// 实例
		KeyGenerator kg = KeyGenerator.getInstance(KEY_ALGORITHM);
		// AES 
		kg.init(KEY_SIZE);
		// 生成密钥
		SecretKey secretKey = kg.generateKey();
		return secretKey;
	}


    /**
     * 加密
     *
     * @param content 内容
     * @param key     秘钥
     * @return 加密后的数据
     */
    public static String encrypt(String content, String key) throws Exception {
        // 新建Cipher 类
        Cipher cipher = Cipher.getInstance(MODE);
        // 初始化秘钥
        SecretKeySpec sks = new SecretKeySpec(key.getBytes(CHARSET), KEY_ALGORITHM);
        // 初始化加密类
        cipher.init(Cipher.ENCRYPT_MODE, sks);
        // 进行加密
        byte[] encrypt = cipher.doFinal(content.getBytes());
        // 这一步非必须,是因为二进制数组不方便传输,所以加密的时候才进行base64编码
        encrypt = Base64.getEncoder().encode(encrypt);
        // 转成字符串返回
        return new String(encrypt, CHARSET);
    }

    /**
     * 解密数据
     *
     * @param content 内容
     * @param key     秘钥
     * @return 数据
     */
    public static String decrypt(String content, String key) throws Exception{
        // 替换base64里的换行,这一步也非必须,只是有些情况base64里会携带换行符导致解码失败
        content = content.replaceAll("[\\n\\r]", "");
        // base64 解码,跟上面的编码对称
        byte[] data = Base64.getDecoder().decode(content.getBytes(CHARSET));
        // 新建Cipher 类
        Cipher cipher = Cipher.getInstance(MODE);
        // 初始化秘钥
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(CHARSET), KEY_ALGORITHM);
        // 初始化类
        cipher.init(Cipher.DECRYPT_MODE, keySpec);
        // 解密
        byte[] result = cipher.doFinal(data);
        // 返回解密后的内容
        return new String(result);
    }


}

2、RSA工具类

这里引入了一个jar 包用来生成公钥和私钥

        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>1.59</version>
        </dependency>
package com.jing.app.common.encryption;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * @author jing
 * @version 1.0
 * @desc RSA
 * @date 2021/9/10 0010 10:20
 **/
public class RSAUtils {

    private final static String ASYMMETRIC_ALGORITHM = "RSA/None/PKCS1Padding";

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 初始化key pair
     *
     * @return KeyPair
     */
    private static KeyPair genrateKey() {
        try {
            // 随机数用于安全加密
            SecureRandom random = new SecureRandom();
            // 初始化秘钥
            KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
            generator.initialize(2048, random);
            return generator.generateKeyPair();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 解密数据
     *
     * @param arg           需要解密的字符串
     * @param privateKeyStr base64加密的秘钥
     * @return 解密后的字符串
     */
    public static String decryptBase64(String arg, String privateKeyStr) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(privateKeyStr);
        // 初始化私钥
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        // 进行解密
        return decryptBase64(arg, privateKey);
    }

    /**
     * 解密数据
     *
     * @param arg 需要解密的字符串
     * @return 解密后的字符串
     */
    public static String decryptBase64(String arg, PrivateKey privateKey) throws Exception {
        // Cipher 提供加密和解密功能
        Cipher cipher = Cipher.getInstance(ASYMMETRIC_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        // 解密数据
        byte[] plainText = cipher.doFinal(Base64.getDecoder().decode(arg));
        return new String(plainText);
    }

    /**
     * 加密数据
     *
     * @param arg       需要解密的字符串
     * @param pubKeyStr 公钥base64编码后
     * @return 解密后的字符串
     */
    public static String encryptBase64(String arg, String pubKeyStr) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(pubKeyStr);
        // 初始化公钥
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
        return encryptBase64(arg, publicKey);
    }


    /**
     * @param arg       待加密数据字节数
     * @param publicKey 公钥
     * @return 加密后数据
     */
    public static String encryptBase64(String arg, PublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance(ASYMMETRIC_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] encrypt = cipher.doFinal(arg.getBytes());
        return Base64.getEncoder().encodeToString(encrypt);
    }


    /**
     * @param arg       待加密数据字节数
     * @param publicKey 公钥
     * @return 加密后数据
     */
    public static String encryptBase64(String arg, PublicKey publicKey) throws Exception {
        Provider provider = new BouncyCastleProvider();
        Security.addProvider(provider);
        Cipher cipher = Cipher.getInstance(ASYMMETRIC_ALGORITHM, provider);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] encrypt = cipher.doFinal(arg.getBytes());
        return Base64.getEncoder().encodeToString(encrypt);
    }


    public static void main(String[] args) throws Exception {
        KeyPair keyPair = genrateKey();
        String privateKeyStr = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
        String publicKeyStr = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
        System.out.println(String.format("privateKey: %s", privateKeyStr));
        System.out.println(String.format("publicKey: %s", publicKeyStr));
        String content = "abcde";
        String cipherText = encryptBase64(content, publicKeyStr);
        String decryptText = decryptBase64(cipherText, privateKeyStr);
        System.out.println(String.format("解密数据:%s", decryptText));
    }
}

到此结束。

参考文章:(有兴趣可以读一下扩展)

为什么使用 Java Cipher 要指定转换模式? | Bigzuo's Blog

​​​​​​AES加密(3):AES加密模式与填充 - 知乎

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐