Java 安全算法(摘要/加密/国密算法)
一、安全算法
1. 算法分类:摘要算法、加密算法和国密算法;
2. 摘要算法:指加密过程不需要秘钥,密文无法被解密,并且只有输入相同的明文数据经过相同的消息摘要算法才能得到相同的密文,如:MD5和SHA1,其中MD5加密后是一个定长字符串;
3. 加密算法
A. 分类:对称加密、非对称加密及Hash算法;
B. 对称加密:指加密和解密使用相同的秘钥,如DES和AES算法;
C. 非对称加密:指加密和解密使用不同的秘钥,包含公钥和私钥,公钥可以公开,私钥自己保管,公钥加密私钥解密或私钥加密公钥解密,如RSA和DSA,该技术安全性更好,但性能更慢,主要用于登录、数字签名、数字证书认证等场景;
D. Hash算法:指通过算法将值映射到表中一个位置来访问记录,以加快查询速度,如MD5和SHA;
4. 国密算法:指国家密码局认定的国产密码算法,如:SM1(对称加密)、SM2(非对称加密)、SM3(消息摘要)、SM4等,其中SM1为对称加密,该算法不公开,需要通过加密的芯片接口进行调用。
二、AES(对称加密算法)
1. 特点
A. 加密速度更快,安全性更好,会替代DES,是目前最流行的对称加密算法;
B. 使用128位的加密块,还有192和256位长度的秘钥;
2. 算法分组
A. ECB模式;
B. CBC模式:是ECB模式的改进版,应用最广;
3.示例
package com.ruhuanxingyun.javabasic.util; import cn.hutool.core.util.StrUtil; 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.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Base64; /** * @description: AES加解密工具 * @author: ruphie * @date: Create in 2020/7/29 22:21 * @company: ruhuanxingyun */ public class AESUtils { /** * 加密算法AES */ private static final String KEY_ALGORITHM = "AES"; /** * key大小 */ private static final int KEY_SIZE = 128; /** * CBC工作模式 */ private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; /** * 随机生成秘钥 * * @return 秘钥 * @throws NoSuchAlgorithmException 异常 */ public static String generateKey() throws NoSuchAlgorithmException { KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM); keyGenerator.init(KEY_SIZE); SecretKey secretKey = keyGenerator.generateKey(); byte[] keyBytes = secretKey.getEncoded(); return encryptByBase64(keyBytes); } /** * 初始化向量 * * @return 向量字节 */ public static byte[] generateIv() { byte[] randomBytes = new byte[16]; SecureRandom sr = new SecureRandom(); sr.nextBytes(randomBytes); return randomBytes; } /** * 通过AES秘钥加密 * * @param content 需加密内容 * @param key 秘钥 * @param ivBytes 向量 * @return 密文内容 * @throws Exception 异常 */ public static String encryptByKey(String content, String key, byte[] ivBytes) throws Exception { // Base64解密的秘钥 byte[] keyBytes = decryptByBase64(key); SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, KEY_ALGORITHM); // AES加密 Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec); byte[] contentBytes = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8)); return encryptByBase64(contentBytes); } /** * 通过AES秘钥解密 * * @param content 被解密内容 * @param key 秘钥 * @param ivBytes 向量 * @return 明文内容 * @throws Exception 异常 */ public static String decryptByKey(String content, String key, byte[] ivBytes) throws Exception { // Base64解密的秘钥 byte[] keyBytes = decryptByBase64(key); SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, KEY_ALGORITHM); // AES解密 Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); byte[] contentBytes = decryptByBase64(content); byte[] bytes = cipher.doFinal(contentBytes); return new String(bytes, StandardCharsets.UTF_8); } /** * 通过Base64加密 * * @param content 需加密内容 * @return 密文内容 */ private static String encryptByBase64(byte[] content) { return Base64.getEncoder().encodeToString(content); } /** * 通过Base64解密 * * @param content 被解密内容 * @return 明文内容 */ private static byte[] decryptByBase64(String content) { return Base64.getDecoder().decode(content); } public static void main(String[] args) throws Exception { String content = "ruphie@5379"; String key = AESUtils.generateKey(); byte[] ivBytes = AESUtils.generateIv(); String ciphertext = encryptByKey(content, key, ivBytes); String plaintext = decryptByKey(ciphertext, key, ivBytes); System.out.println("秘钥为:" + key); System.out.println("加密后密文为:" + ciphertext); System.out.println("解密后明文为:" + plaintext); System.out.println("加密前明文与解密后明文对比为:" + StrUtil.equals(content, plaintext)); } }
三、RSA(非对称加密)
1. 特点
A. RSA只能加密少量数据,数据量大时运算代价大且耗时;
B. RSA加密对明文长度有限制,最大117字节;
C. 秘钥长度有1024位、2048位、4096位等,支付宝推荐2048位,同时更长的秘钥必定导致更大的性能开销;
2. 使用场景
A. 用户登录时用户名和密码的加密传输,一般使用公钥加密,私钥解密;
B. 防止URL参数被篡改需加签名;
3. 示例
package com.ruhuanxingyun.javabasic.util; import cn.hutool.core.util.StrUtil; import javax.crypto.Cipher; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; import java.util.HashMap; import java.util.Map; /** * @description: RSA加解密工具 * @author: ruphie * @date: Create in 2020/7/30 22:21 * @company: ruhuanxingyun */ public class RSAUtils { /** * 加密算法RSA */ private static final String KEY_ALGORITHM = "RSA"; /** * key大小 */ private static final int KEY_SIZE = 2048; /** * 公钥key */ private static final String PUBLIC_KEY = "publicKey"; /** * 私钥key */ private static final String PRIVATE_KEY = "privateKey"; /** * 随机生成秘钥对 * * @return 秘钥对 * @throws NoSuchAlgorithmException 异常 */ public static Map<String, String> generateKey() throws NoSuchAlgorithmException { // 密钥对生成器 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM); // 初始化 keyPairGenerator.initialize(KEY_SIZE); // 生成一个秘钥对 KeyPair keyPair = keyPairGenerator.generateKeyPair(); // 公钥 RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 私钥 RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); Map<String, String> map = new HashMap<>(3); map.put(PUBLIC_KEY, encryptByBase64(publicKey.getEncoded())); map.put(PRIVATE_KEY, encryptByBase64(privateKey.getEncoded())); return map; } /** * 通过RSA公钥加密 * * @param content 需加密内容 * @param publicKey 公钥 * @return 密文内容 * @throws Exception 异常 */ public static String encryptByPublicKey(String content, String publicKey) throws Exception { // Base64解密的公钥 byte[] keyBytes = decryptByBase64(publicKey); X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); RSAPublicKey rsaPublicKey = (RSAPublicKey) keyFactory.generatePublic(x509EncodedKeySpec); // RSA加密 Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); cipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey); byte[] contentBytes = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8)); return encryptByBase64(contentBytes); } /** * 通过RSA私钥解密 * * @param content 被解密内容 * @param privateKey 私钥 * @return 明文内容 * @throws Exception 异常 */ public static String decryptByPrivateKey(String content, String privateKey) throws Exception { // Base64解密的私钥 byte[] keyBytes = decryptByBase64(privateKey); PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8EncodedKeySpec); // RSA解密 Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); cipher.init(Cipher.DECRYPT_MODE, rsaPrivateKey); byte[] contentBytes = decryptByBase64(content); byte[] bytes = cipher.doFinal(contentBytes); return new String(bytes, StandardCharsets.UTF_8); } /** * 通过Base64加密 * * @param content 需加密内容 * @return 密文内容 */ private static String encryptByBase64(byte[] content) { return Base64.getEncoder().encodeToString(content); } /** * 通过Base64解密 * * @param content 被解密内容 * @return 明文内容 */ private static byte[] decryptByBase64(String content) { return Base64.getDecoder().decode(content); }
/**
* 注意base64 url参数传输时会自动将+转化为空格,故应使用urlBase64编码
*/
private static byte[] decryptByBase64Url(String content) {
return Base64.getUrlDecoder().decode(content);
}
public static void main(String[] args) throws Exception { String content = "ruphie@5379"; Map<String, String> map = RSAUtils.generateKey(); String publicKey = map.get(PUBLIC_KEY); String privateKey = map.get(PRIVATE_KEY); String ciphertext = encryptByPublicKey(content, publicKey); String plaintext = decryptByPrivateKey(ciphertext, privateKey); System.out.println("公钥为:" + publicKey); System.out.println("私钥为:" + privateKey); System.out.println("加密后密文为:" + ciphertext); System.out.println("解密后明文为:" + plaintext); System.out.println("加密前明文与解密后明文对比为:" + StrUtil.equals(content, plaintext)); } }