登录认证、鉴权这些都做好了过后。就开始我们的加密设计了、这里采用了简化数字信封进行加密。首先客户端(浏览器)先请求一份RSA非对称密钥、如果我们采用了openresty或者有能力在nginx开发C模块的插件,就可以在这里保留一份用户的私钥,如果不行就直接在应用网关上面保存(也可以在应用网关直接读取redis获得);然后在浏览器发起请求的时候、请求体加密时本地自己生成AES密钥、在使用获得的公钥对AES密钥进行一次加密(加密结果放在请求头中),最后将请求发送到后端。后端先使用RSA的私钥解密请求头中的AES加密密钥,得到了AES明文密钥后对请求体进行解密。后端返回时,如果需要加密返回结果、也可以使用该AES密钥对结果进行加密。下图以有nginx自定义C模块或者openresty进行加解密的方式进行描述。

AES 和Res aes和res加密_java加密

 

AES加密

        采用AES进行对称加密时、需要注意的是需要采用CBC模式。CBC模式会增加向量来保证加密的强度。还有为什么明明使用了RSA还需要在用AES做什么呢?为什么不全部使用RSA呢?

        首先RSA的强度是要比AES更强的、但是RSA有一个缺陷就是密文太长的话、解密的速度太慢。所以综合了两者的优点、使用RSA对AES的密钥加密,使用AES对报文体进行加密。这样既保证了密码的强度、又保证了加解密的速度问题。

AES加解密工具类

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

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

/**
 * AES 加密工具类
 */
public class AESUtils {

    public static final String CHAR_ENCODING = "UTF-8";
    public static final String AES_ALGORITHM = "AES/CBC/PKCS5Padding";

    /**
     * 加密
     *
     * @param data 需要加密的内容
     * @param key  加密密码
     * @return
     */
    public static byte[] encrypt(byte[] data, byte[] key, byte[] ivKey) {
        if (key.length != 16) {
            throw new RuntimeException("Invalid AES key length (must be 16 bytes)");
        }
        try {
            SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
            byte[] enCodeFormat = secretKey.getEncoded();
            SecretKeySpec seckey = new SecretKeySpec(enCodeFormat, "AES");
            Cipher cipher = Cipher.getInstance(AES_ALGORITHM);// 创建密码器
            IvParameterSpec iv = new IvParameterSpec(ivKey);//使用CBC模式,需要一个向量iv,可增加加密算法的强度
            cipher.init(Cipher.ENCRYPT_MODE, seckey, iv);// 初始化
            byte[] result = cipher.doFinal(data);
            return result; // 加密
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("encrypt fail!", e);
        }
    }

    /**
     * 解密
     *
     * @param data 待解密内容
     * @param key  解密密钥
     * @return
     */
    public static byte[] decrypt(byte[] data, byte[] key,byte[] ivKey) {
        if (key.length != 16) {
            throw new RuntimeException("Invalid AES key length (must be 16 bytes)");
        }
        try {
            SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
            byte[] enCodeFormat = secretKey.getEncoded();
            SecretKeySpec seckey = new SecretKeySpec(enCodeFormat, "AES");
            // 创建密码器
            Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
            // 使用CBC模式,需要一个向量iv,可增加加密算法的强度
            IvParameterSpec iv = new IvParameterSpec(ivKey);
            // 初始化
            cipher.init(Cipher.DECRYPT_MODE, seckey, iv);
            byte[] result = cipher.doFinal(data);
            return result; // 解密
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("decrypt fail!", e);
        }
    }

    public static String encryptToBase64(String data, String key, String iVkey) {
        try {
            byte[] valueByte = encrypt(data.getBytes(CHAR_ENCODING), key.getBytes(CHAR_ENCODING),iVkey.getBytes(CHAR_ENCODING));
            return new String(Base64.encodeBase64(valueByte));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("encrypt fail!", e);
        }
    }

    public static String decryptFromBase64(String data, String key, String iVkey) {
        try {
            byte[] originalData = Base64.decodeBase64(data.getBytes());
            final char[] chars = getChars(decrypt(originalData, key.getBytes(CHAR_ENCODING),iVkey.getBytes(CHAR_ENCODING)));
            StringBuffer sb = new StringBuffer();
            for(int i= 0 ;i < chars.length; i++){
                if(chars[i] == '\0'){
                    continue;
                }
                sb.append(chars[i]);
            }
            Arrays.fill(chars,' ');
            return sb.toString();
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("decrypt fail!", e);
        }
    }

    private static char[] getChars(byte[] bytes){
        Charset cs = Charset.forName(CHAR_ENCODING);
        ByteBuffer bb = ByteBuffer.allocate(bytes.length);
        bb.put(bytes);
        bb.flip();
        CharBuffer cb = cs.decode(bb);
        return cb.array();
    }

    public static byte[] genarateRandomKey() {
        KeyGenerator keygen = null;
        try {
            keygen = KeyGenerator.getInstance(AES_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(" genarateRandomKey fail!", e);
        }
       /* SecureRandom random = new SecureRandom();
        keygen.init(random);*/
        keygen.init(128);
        Key key = keygen.generateKey();
        return key.getEncoded();
    }
    
}

RSA加密

        上面提到会在nginx那边存储一份私钥、那么如何保证客户端(浏览器)的每一对公私玥都是能对应上的呢。这里就需要在做一个设计、就是客户端在请求公钥之前先获取一个临时的token、临时token跟正式token的区别在于临时token的权限是固定的一些URI。并且临时token中的userId是随机生成的虚拟的,临时token的时效也不用设置太长。当用户登录成功后token中的tokenId(之前提到的使用雪花字符串或者UUID)不变、只需要替换里面的userId跟重新设置redis的有效时长即可。这样nginx就可以使用token作为关键字存储每一个客户端对应的私钥了。

RSA工具类

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.*;
import java.util.HashMap;
import java.util.Map;


/**
 * RSA处理工具类
 *
 * @author hhs
 * @date 2019-10-22 09:54
 * @since JDK1.8
 */
@Slf4j
public class RsaPassUtils {

    /**
     * 填充模式
     */
    private final static String RSA_CIPHER = "RSA/ECB/OAEPWithSHA-1AndMGF1PADDING";
    /**
     * 密钥位数
     */
    private final int KEY_SIZE = 2048;

    private  KeyPair KEY_PAIR;

    public RsaPassUtils(){
        try {
            SecureRandom random = new SecureRandom();
            KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
            generator.initialize(KEY_SIZE, random);
            KEY_PAIR = generator.generateKeyPair();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 生成rsa公钥和私钥
     *
     * @return Map {@link HashMap}: publicKey-公钥(RSAPublicKey),privateKey-私钥(RSAPrivateKey)
     * @author hhs
     * @date 2019-10-22 09:54
     */
    public Map<String, Object> getRsaKey() {
        Map<String, Object> keyInfo = new HashMap<>(2);
        // 公钥
        RSAPublicKey publicKey = (RSAPublicKey) KEY_PAIR.getPublic();
        keyInfo.put("publicKey", publicKey);
        // 私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) KEY_PAIR.getPrivate();
        keyInfo.put("privateKey", privateKey);
        return keyInfo;
    }


    /**
     * 生成rsa公钥和私钥
     *
     * @return Map {@link HashMap}: publicKey-公钥(X509格式),privateKey-私钥(PKCS8格式)
     * @author hhs
     * @date 2019-10-22 09:54
     */
    public Map<String, String> getX509AndPKCS8Key() {
        Map<String, Object> keyInfo = getRsaKey();
        Map<String, String> x509AndPKCS8Key = new HashMap<>(2);
        // 公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyInfo.get("publicKey");
//        String rsaPublicKey = (new BASE64Encoder()).encodeBuffer(publicKey.getEncoded());
        String rsaPublicKey = Base64.encodeBase64String(publicKey.getEncoded());
        x509AndPKCS8Key.put("publicKey", rsaPublicKey);
        // 私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyInfo.get("privateKey");
//        String rsaPrivateKey = (new BASE64Encoder()).encodeBuffer(privateKey.getEncoded());
        String rsaPrivateKey = Base64.encodeBase64String(privateKey.getEncoded());
        x509AndPKCS8Key.put("privateKey", rsaPrivateKey);
        return x509AndPKCS8Key;
    }


    /**
     * PKCS8的私钥字符串还原为RSA私钥
     *
     * @param pkcs8Key {@link String} 待还原私钥字符串
     * @return RSAPrivateKey {@link RSAPrivateKey}
     * @author hhs
     * @date 2019-10-22 09:54
     */
    private RSAPrivateKey getPrivateKey(String pkcs8Key) {
        PrivateKey privateKey = null;
        try {
//            byte[] decodeKey = (new BASE64Decoder()).decodeBuffer(pkcs8Key);
            byte[] decodeKey = Base64.decodeBase64(pkcs8Key);
            PKCS8EncodedKeySpec pkcs8 = new PKCS8EncodedKeySpec(decodeKey);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            privateKey = keyFactory.generatePrivate(pkcs8);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            log.error("----------RSAUtils---------->PKCS8的私钥字符串还原为RSA私钥出错:{}", e.getMessage());
        }
        return (RSAPrivateKey) privateKey;
    }


    /**
     * X509的公钥字符串还原为RSA公钥
     *
     * @param x509Key {@link String} 待还原公钥字符串
     * @return RSAPublicKey {@link RSAPublicKey}
     * @author hhs
     * @date 2019-10-22 09:54
     */
    private RSAPublicKey getPublicKey(String x509Key) {
        PublicKey publicKey = null;
        try {
//            byte[] decodeKey = (new BASE64Decoder()).decodeBuffer(x509Key);
            byte[] decodeKey = Base64.decodeBase64(x509Key);
            X509EncodedKeySpec x509 = new X509EncodedKeySpec(decodeKey);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            publicKey = keyFactory.generatePublic(x509);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            log.error("----------RSAUtils---------->X509的公钥字符串还原为RSA公钥:{}", e.getMessage());
        }
        return (RSAPublicKey) publicKey;
    }


    /**
     * <p>使用模和指数生成RSA公钥
     * 注意:【此代码用了默认补位方式,为RSA/None/PKCS1Padding,不同JDK默认的补位方式可能不同,如Android默认是RSA/None/NoPadding】
     * </p>
     *
     * @param modulus        {@link BigInteger} 模
     * @param publicExponent {@link BigInteger} 公钥指数
     * @return RSAPublicKey {@link RSAPublicKey}
     * @author hhs
     * @date 2019-10-22 09:54
     */
    public RSAPublicKey getPublicKey(BigInteger modulus, BigInteger publicExponent) {
        RSAPublicKey publicKey = null;
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, publicExponent);
            publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
            log.info("----------RSAUtils---------->生成公钥:{}", publicKey);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            log.error("----------RSAUtils---------->生成公钥异常:{}", e.getMessage());
        }
        return publicKey;
    }


    /**
     * <p>
     * 使用模和指数生成RSA私钥
     * 注意:【此代码用了默认补位方式,为RSA/None/PKCS1Padding,不同JDK默认的补位方式可能不同,如Android默认是RSA/None/NoPadding】
     * </p>
     *
     * @param modulus         {@link BigInteger} 模
     * @param privateExponent {@link BigInteger} 私钥指数
     * @return RSAPrivateKey {@link RSAPrivateKey}
     * @author hhs
     * @date 2019-10-22 09:54
     */
    public RSAPrivateKey getPrivateKey(BigInteger modulus, BigInteger privateExponent) {
        RSAPrivateKey privateKey = null;
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(modulus, privateExponent);
            privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
        } catch (Exception e) {
            log.error("----------RSAUtils---------->生成私钥异常:{}", e.getMessage());
        }
        return privateKey;
    }


    /**
     * 公钥加密
     *
     * @param publicKey {@link String} X509格式公钥
     * @param plaintext {@link String} 明文
     * @return String {@link String} 密文
     * @author hhs
     * @date 2019-10-22 09:54
     */
    public String encryptPublicKey(String publicKey, String plaintext) {
        RSAPublicKey rsaPublicKey = getPublicKey(publicKey);
        String result = "";
        try {
            byte[] bytes = encryptByRsaKey(rsaPublicKey, plaintext.getBytes());
            result = Base64.encodeBase64String(bytes);
        } catch (Exception e) {
            log.error("----------RSAUtils---------->公钥加密异常:{}", e.getMessage());
        }
        return result;
    }


    /**
     * 公钥解密
     *
     * @param publicKey  {@link String} X509格式公钥
     * @param ciphertext {@link String} 密文
     * @return String {@link String} 明文
     * @author hhs
     * @date 2019-10-22 09:54
     */
    public String decryptPublicKey(String publicKey, String ciphertext) {
        byte[] bytes = Base64.decodeBase64(ciphertext);
        RSAPublicKey rsaPublicKey = getPublicKey(publicKey);
        String result = "";
        try {
            result = new String(decryptByRsaKey(rsaPublicKey, bytes), StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.error("----------RSAUtils---------->私钥解密异常:{}", e.getMessage());
        }
        return result;
    }


    /**
     * 私钥加密
     *
     * @param privateKey {@link String} PKCS8格式私钥
     * @param plaintext  {@link String} 明文
     * @return String {@link String} 密文
     * @author hhs
     * @date 2019-10-22 09:54
     */
    public String encryptPrivateKey(String privateKey, String plaintext) {
        RSAPrivateKey rsaPrivateKey = getPrivateKey(privateKey);
        String result = "";
        try {
            byte[] bytes = encryptByRsaKey(rsaPrivateKey, plaintext.getBytes());
            result = Base64.encodeBase64String(bytes);
        } catch (Exception e) {
            log.error("----------RSAUtils---------->公钥加密异常:{}", e.getMessage());
        }
        return result;
    }


    /**
     * 私钥解密
     *
     * @param privateKey {@link String} PKCS8格式私钥
     * @param ciphertext {@link String} 密文
     * @return String {@link String}
     * @author hhs
     * @date 2019-10-22 09:54
     */
    public String decryptPrivateKey(String privateKey, String ciphertext) {
        byte[] bytes = Base64.decodeBase64(ciphertext);
        RSAPrivateKey rsaPrivateKey = getPrivateKey(privateKey);
        String result = "";
        try {
            result = new String(decryptByRsaKey(rsaPrivateKey, bytes), StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.error("----------RSAUtils---------->私钥解密异常:{}", e.getMessage());
        }
        return result;
    }


    /**
     * 数据分组解密
     *
     * @param key                 {@link Key} RSA公钥或者私钥
     * @param ciphertext-密文byte数组
     * @return byte[] 明文byte数组
     * @throws Exception {@link Exception} 异常数据
     * @author hhs
     * @date 2019-10-22 09:54
     */
    private byte[] decryptByRsaKey(Key key, byte[] ciphertext) throws Exception {
        // Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding");
        Cipher cipher = Cipher.getInstance(RSA_CIPHER);
        cipher.init(2, key);
        int inputLen = ciphertext.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        for (int i = 0; inputLen - offSet > 0; offSet = i * 256) {
            byte[] cache;
            if (inputLen - offSet > 256) {
                cache = cipher.doFinal(ciphertext, offSet, 256);
            } else {
                cache = cipher.doFinal(ciphertext, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            ++i;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }


    /**
     * 数据分组加密
     *
     * @param key                {@link Key} Rsa公钥或私钥
     * @param plaintext-明文byte数组
     * @return byte[] 密文byte数组
     * @throws Exception {@link Exception} 异常数据
     * @author hhs
     * @date 2019-10-22 09:54
     */
    private byte[] encryptByRsaKey(Key key, byte[] plaintext) throws Exception {
        // Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding");
        Cipher cipher = Cipher.getInstance(RSA_CIPHER);
        cipher.init(1, key);
        int inputLen = plaintext.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        for (int i = 0; inputLen - offSet > 0; offSet = i * 244) {
            byte[] cache;
            if (inputLen - offSet > 244) {
                cache = cipher.doFinal(plaintext, offSet, 244);
            } else {
                cache = cipher.doFinal(plaintext, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            ++i;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return encryptedData;
    }

}

注意在填充时、不要在构造中添加"BC"参数,该模式配合到C语言进行解密时,C语言不好处理、所以需要将该参数去掉:

Cipher cipher = Cipher.getInstance(RSA_CIPHER,"BC");