加密算法介绍及RSA实战
文章目录
一、算法的分类
通常分为可逆性加密算法、不可逆性算法。
二、算法介绍
2.1、可逆加密算法
解释:加密后, 密文可以反向解密得到密码原文.
2.1.1、对称加密
【文件加密和解密使用相同的密钥,即加密密钥也可以用作解密密钥】
解释:
在对称加密算法中,数据发信方将明文和加密密钥一起经过特殊的加密算法处理后,使其变成复杂的加密密文 发送出去;
收信方收到密文后,若想解读出原文,则需要使用加密时用的密钥以及相同加密算法的逆算法对密文进行。
解密,才能使其回复成可读明文。在对称加密算法中,使用的密钥只有一个,收发双方都使用这个密钥,这就需要解
密方事先知道加密密钥。
优点: 对称加密算法的优点是算法公开、计算量小、加密速度快、加密效率高。
缺点:没有非对称加密安全.
用途: 一般用于保存用户手机号、身份证等敏感但能解密的信息。
常见的对称加密算法有: AES、DES、3DES、Blowfish、IDEA、RC4、RC5、RC6、HS256
2.1.2、非对称加密
【两个密钥:公开密钥(publickey)和私有密钥,公有密钥加密,私有密钥解密】
解释:
同时生成两把密钥:私钥和公钥,私钥隐秘保存,公钥可以下发给信任客户端.
加密与解密:
私钥加密,持有私钥或公钥才可以解密
公钥加密,持有私钥才可解密
签名:
私钥签名, 持有公钥进行验证是否被篡改过.
优点:非对称加密与对称加密相比,其安全性更好;
缺点: 非对称加密的缺点是加密和解密花费时间长、速度慢,只适合对少量数据进行加密。
用途: 一般用于签名和认 证。私钥服务器保存, 用来加密, 公钥客户拿着用于对于令牌或者签名的解密或者校验使用.
常见的非对称加密算法有: RSA、DSA(数字签名用)、ECC(移动设备用)、RS256 (采用SHA-256 的 RSA 签名)
2.2、不可逆加密算法
解释: 一旦加密就不能反向解密得到密码原文.
种类:Hash加密算法, 散列算法, 摘要算法等
**用途:**一般用于效验下载文件正确性,一般在网站上下载文件都能见到;存储用户敏感信息,如密码、 卡号等不可解密的信息。
常见的不可逆加密算法有: MD5、SHA、HMAC
三、扩展之Base64编码
解释:Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一。Base64编码可用于在HTTP环境下传递较长的标识信息。采用Base64编码解码具有不可读性,即所编码的数据不会被人用肉眼所直接看到。
注意:Base64只是一种编码方式,不算加密方法。
在线编码工具: http://www.jsons.cn/img2base64/
四、RSA详解
解释:同时生成两把密钥:私钥和公钥,私钥隐秘保存,公钥可以下发给信任客户端。
私钥加密,持有私钥或公钥才可以解密 ;
公钥加密,持有私钥才可解密 。
优点:安全,难以破解
缺点:算法比较耗时,为了安全,可以接受
历史:三位数学家Rivest、Shamir 和 Adleman 设计了一种算法,可以实现非对称加密。这种算法用他们三
个人的名字缩写:RSA。
五、RSA实战
1、生成私钥、公钥;
根据如下generateKey方法获取公钥、私钥,然后文件名分别命名为id_key_rsa、id_key_rsa.pub,并放入resources下。
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.util.Base64;
/**
* @author :
* @date :Created in 16:30 2022/7/20
* @description :
* @version: 1.0
*/
public class RsaUtils {
private static final int DEFAULT_KEY_SIZE = 2048;
/**
* 根据密文,生成rsa公钥和私钥,并写入指定文件
*
* @param publicKeyFilename 公钥文件路径
* @param privateKeyFilename 私钥文件路径
* @param secret 生成密钥的密文
*/
public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret, int keySize) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
SecureRandom secureRandom = new SecureRandom(secret.getBytes());
keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom);
KeyPair keyPair = keyPairGenerator.genKeyPair();
// 获取公钥并写出
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
publicKeyBytes = Base64.getEncoder().encode(publicKeyBytes);
writeFile(publicKeyFilename, publicKeyBytes);
// 获取私钥并写出
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
privateKeyBytes = Base64.getEncoder().encode(privateKeyBytes);
writeFile(privateKeyFilename, privateKeyBytes);
}
private static void writeFile(String destPath, byte[] bytes) throws IOException {
File dest = new File(destPath);
if (!dest.exists()) {
dest.createNewFile();
}
Files.write(dest.toPath(), bytes);
}
}
id_key_rsa
id_key_rsa.pub
2、使用配置类读取;
使用配置类加载resources下的私钥、公钥。
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.security.PrivateKey;
import java.security.PublicKey;
/**
* @author :
* @date :Created in 19:38 2022/7/18
* @description :
* @version: 1.0
*/
@Component
public class RsaKeyProperties {
private String pubKeyFile= "id_key_rsa.pub";
private String priKeyFile= "id_key_rsa";
private PublicKey publicKey;
private PrivateKey privateKey;
@PostConstruct
public void createRsaKey() throws Exception {
publicKey = RsaUtils.getPublicKey(new ClassPathResource(pubKeyFile).getInputStream().readAllBytes());
privateKey = RsaUtils.getPrivateKey(new ClassPathResource(priKeyFile).getInputStream().readAllBytes());
}
public String getPubKeyFile() {
return pubKeyFile;
}
public void setPubKeyFile(String pubKeyFile) {
this.pubKeyFile = pubKeyFile;
}
public String getPriKeyFile() {
return priKeyFile;
}
public void setPriKeyFile(String priKeyFile) {
this.priKeyFile = priKeyFile;
}
public PublicKey getPublicKey() {
return publicKey;
}
public void setPublicKey(PublicKey publicKey) {
this.publicKey = publicKey;
}
public PrivateKey getPrivateKey() {
return privateKey;
}
public void setPrivateKey(PrivateKey privateKey) {
this.privateKey = privateKey;
}
}
3、使用工具类加解密;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
* @author :
* @date :Created in 19:30 2022/7/19
* @description :
* @version: 1.0
*/
public class RsaUtils {
private static final int DEFAULT_KEY_SIZE = 2048;
/**
* 从文件中读取公钥
*
* @param filename 公钥保存路径,相对于classpath
* @return 公钥对象
* @throws Exception
*/
public static PublicKey getPublicKey(String filename) throws Exception {
byte[] bytes = readFile(filename);
return getPublicKey(bytes);
}
/**
* 从文件中读取密钥
*
* @param filename 私钥保存路径,相对于classpath
* @return 私钥对象
* @throws Exception
*/
public static PrivateKey getPrivateKey(String filename) throws Exception {
byte[] bytes = readFile(filename);
return getPrivateKey(bytes);
}
/**
* 获取公钥
*
* @param bytes 公钥的字节形式
* @return
* @throws Exception
*/
public static PublicKey getPublicKey(byte[] bytes) throws Exception {
bytes = Base64.getDecoder().decode(bytes);
X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePublic(spec);
}
/**
* 获取密钥
*
* @param bytes 私钥的字节形式
* @return
* @throws Exception
*/
public static PrivateKey getPrivateKey(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
bytes = Base64.getDecoder().decode(bytes);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePrivate(spec);
}
/**
* 根据密文,生存rsa公钥和私钥,并写入指定文件
*
* @param publicKeyFilename 公钥文件路径
* @param privateKeyFilename 私钥文件路径
* @param secret 生成密钥的密文
*/
public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret, int keySize) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
SecureRandom secureRandom = new SecureRandom(secret.getBytes());
keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom);
KeyPair keyPair = keyPairGenerator.genKeyPair();
// 获取公钥并写出
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
publicKeyBytes = Base64.getEncoder().encode(publicKeyBytes);
writeFile(publicKeyFilename, publicKeyBytes);
// 获取私钥并写出
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
privateKeyBytes = Base64.getEncoder().encode(privateKeyBytes);
writeFile(privateKeyFilename, privateKeyBytes);
}
private static byte[] readFile(String fileName) throws Exception {
return Files.readAllBytes(new File(fileName).toPath());
}
private static void writeFile(String destPath, byte[] bytes) throws IOException {
File dest = new File(destPath);
if (!dest.exists()) {
dest.createNewFile();
}
Files.write(dest.toPath(), bytes);
}
}
六、扩展之AES加解密工具类
解释:AES即高级加密标准(AES,Advanced Encryption Standard)为最常见的对称加密算法(微信小程序加密传输就是用这个加密算法的)。对称加密算法也就是加密和解密用相同的密钥。
注: 缺少的导包可以去我的其他文章里面获取
工具类:
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
/**
* @Author:
* @Desc: AES加密工具
* @Date: 2022/7/18 19:44
* @Modify By:
*/
public class AesEncryptUtils {
/**
* 算法
*/
private static final String ALGORITHMSTR = "AES/ECB/PKCS5Padding";
/**
* 秘钥
**/
private static final String ENCRYPT_KEY = "nanMu";
/**
* base 64 encode
* @param bytes 待编码的byte[]
* @return 编码后的base 64 code
*/
private static String base64Encode(byte[] bytes){
return Base64.encodeBase64String(bytes);
}
/**
* base 64 decode
* @param base64Code 待解码的base 64 code
* @return 解码后的byte[]
* @throws Exception 抛出异常
*/
private static byte[] base64Decode(String base64Code) throws Exception{
return StringUtils.isEmpty(base64Code) ? null : java.util.Base64.getDecoder().decode(base64Code);
}
/**
* AES加密
* @param content 待加密的内容
* @return 加密后的byte[]
*/
private static byte[] aesEncryptToBytes(String content) {
String encryptKey = ENCRYPT_KEY;
try{
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES"));
return cipher.doFinal(content.getBytes("utf-8"));
}catch (Exception e){
throw new BusinessException(ExceptionCodeEnum.SERVICE_ERROR,"AES加密失败");
}
}
/**
* AES加密为base 64 code
*
* @param content 待加密的内容
* @return 加密后的base 64 code
*/
public static String aesEncrypt(String content) {
String ciphertext = base64Encode(aesEncryptToBytes(content));
if(StringUtils.isEmpty(ciphertext)){
throw new BusinessException(ExceptionCodeEnum.SERVICE_ERROR,"AES加密失败");
}
return ciphertext;
}
/**
* AES解密
*
* @param encryptBytes 待解密的byte[]
* @param decryptKey 解密密钥
* @return 解密后的String
*/
private static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), "AES"));
byte[] decryptBytes = cipher.doFinal(encryptBytes);
return new String(decryptBytes);
}
/**
* 将base 64 code AES解密
* @param encryptStr 待解密的base 64 code
* @param decryptKey 解密密钥
* @return 解密后的string
*/
public static String aesDecrypt(String encryptStr, String decryptKey) throws Exception {
return StringUtils.isEmpty(encryptStr) ? null : aesDecryptByBytes(base64Decode(encryptStr), decryptKey);
}
}
七、扩展之MD5+加盐加密工具类
解释:md5内容不可逆,相同的内容不论执行多少次,md5加密算法生成的结果都是一致的。
结果:生成的结果是一个32位的16进制字符串。
工具类:
import java.security.MessageDigest;
public class MD5Util {
private static final char[] HEXDIGESTS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
public static String encode(String source) {
try {
MessageDigest md5 = MessageDigest.getInstance("md5");
md5.update(source.getBytes("utf-8"));
byte[] digests = md5.digest();
char[] result = new char[digests.length * 2];
int resultIndex = 0;
for (int i = 0; i < digests.length; i++) {
byte bytex = digests[i];
/***
* 25=0 0 0 1 1 0 0 1>>>4=======0 0 0 0 0 0 0 1& 0 0 0 0 1 1 1 1----0 0 0 0 0 0 0 1
* 25=0 0 0 1 1 0 0 1&0 0 0 0 1 1 1 1=------0 0 0 0 1 0 0 1
*
*/
result[resultIndex++] = HEXDIGESTS[bytex >>> 4 & 0xf];
result[resultIndex++] = HEXDIGESTS[bytex & 0xf];
}
return new String(result);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static boolean compare(String source, String encoded) {
return encode(source).equals(encoded);
}
public static String encodeWithSalt(String source, String salt) {
return encode(source + salt);
}
public static boolean compareWithSalt(String source, String salt, String encoded) {
return compare(encodeWithSalt(source, salt), encoded);
}
public static void main(String[] args) {
//String encode = encode("123456");
//System.out.println(encode);
//加盐
String withSalt = encodeWithSalt("123456", "xiaohu");
System.out.println(withSalt);
String withSalt1 = encodeWithSalt("zzz", "123");
System.out.println(withSalt1);
String withSalt2 = encodeWithSalt("123", "123");
System.out.println(withSalt2);
//慢hash算法
}
}