Java常见加密使用和分析
最近项目里碰到很多加解密的场景,使用多种加密的方式,大概整理了一下,一共有一下几种
加密方式 | 安全性 | 是否可逆 | |
---|---|---|---|
1 | MD5 | 高 | 不可逆 |
2 | AES | 中 | 可逆 |
3 | RSA | 高 | 可逆 |
4 | Base64 | 低 | 可逆 |
1.MD5加密
MD5即Message-Digest Algorithm 5(信息-摘要算法5),用于确保信息传输完整一致。是计算机广泛使用的杂凑算法之一(又译摘要算法、哈希算法)
MD5的一般在项目中的使用场景是密码加密,在校验密码时直接比较加密后的密文,对于这种不需要解密和展示的字段我们可以直接采用MD5的方式进行加密。MD5的本质就是将数据进行hash取得数据摘要,在一定程度上来说,MD5是无法被破解的。当然也可以通过撞库的方式来进行尝试破解,这个投入成本也是非常大的。为了避免撞库的命中率,还可以对MD5进行加盐。投入不同的随机字符串与数据进行拼接,亦或者在hash完的数据进行&运算。降低MD5撞库破解的威胁性。
Java中的使用MD5
public class MD5Util {
//默认盐
public static String LOCAL_SALT = "23543dfggeelysdafaqj23ou89ZXcj@#$@#$#@KJdjklj;D../dSF.,";
//md5加密类
private static MessageDigest md5;
static {
try {
//获取md5实例
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
/**
* MD5加密
* @param data
* @return
*/
public static String encrypt(String data){
//md5加密
return byteArrayToHexString(md5.digest(data.getBytes()));
}
/**
* MD5加盐加密
* @param data
* @param salt
* @return
*/
public static String encrypt(String data,String salt){
//加盐
data +=salt;
//md5加密
return byteArrayToHexString(md5.digest(data.getBytes()));
}
//这里主要是遍历8个byte,转化为16位进制的字符,即0-F
private static String byteArrayToHexString(byte[] b) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++) {
resultSb.append(byteToHexString(b[i]));
}
return resultSb.toString();
}
//这里是针对单个byte,256的byte通过16拆分为d1和d2
private static String byteToHexString(byte b) {
int n = b;
if(n < 0) {
n += 256;
}
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
}
java中获取到MessageDigest的md5实例后就可以完成MD5加密。
2.AES加密
高级加密标准(AES,Advanced Encryption Standard)为最常见的对称加密算法(微信小程序加密传输就是用这个加密算法的)。也就是我们常说的对称密钥加密,加密和解密用相同的密钥。
加密原理:轮密钥加实际是一种Vernam密码形式,其本身不难被破解。另外三个阶段一起提供了混淆、扩散和非线性功能。这三个阶段没有涉及密钥,就它们自身而言,并未提供算法的安全性。然而,该算法经历一个分组的XOR加密(轮密钥加),再对该分组混淆扩散(其他三个阶段),再接着又是XOR加密,如此交替进行,这种方式非常有效非常安全。
可逆原理:每个阶段均可逆。对字节代替、行移位和列混淆,在解密算法中用它们相对应的逆函数。轮密钥加的逆就是用同样的轮密钥和分组相异或,其原理就是A⊕B⊕B = A。和大多数分组密码一样,AES解密算法按逆序利用扩展密钥,然而其解密算法和加密算法并不一样,这是由AES的特定结构决定的。图5.3中加密和解密流程在纵向上是相反的,在每个水平点上,state数组在加密和解密函数中都是一样的。
AES加密的使用场景一般是在敏感信息在数据传输过程,微信小程序使用的AES加密传输数据。因为AES加解使用的是同一个密钥,AES可逆,如果密钥泄露也就意味着数据可以被解密。数据在频繁传输过程中,可能需要动态变更密钥来保证数据传输的安全性。
Java中AES加密
/**
* AES对称密钥加解密
*/
public class AESUtil {
/**
* 生成AES密钥
* @return
* @throws NoSuchAlgorithmException
*/
public static String generateKey() throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("RSA");
//初始化生成器
keyGenerator.init(1024, new SecureRandom());
//生成AES密钥
SecretKey secretKey = keyGenerator.generateKey();
byte[] keyByte = secretKey.getEncoded();
return HexBin.encode(keyByte);
}
/**
* 生成AES密钥
* @param keySize 密钥最大长度
* @return
* @throws NoSuchAlgorithmException
*/
public static String generateKey(Integer keySize) throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("RSA");
//初始化生成器
keyGenerator.init(keySize, new SecureRandom());
//生成AES密钥
SecretKey secretKey = keyGenerator.generateKey();
byte[] keyByte = secretKey.getEncoded();
return HexBin.encode(keyByte);
}
/**
* AES加密
* @param str
* @param secreKey
* @return
* @throws Exception
*/
public static String encrypt( String str, String secreKey ) throws Exception{
//密钥
Key key = new SecretKeySpec(HexBin.decode(secreKey),"AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
//初始化加密
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] result = cipher.doFinal(str.getBytes());
return HexBin.encode(result);
}
/**
* AES解密
* @param str
* @param secreKey
* @return
* @throws Exception
*/
public static String decrypt(String str,String secreKey) throws Exception{
Key key = new SecretKeySpec(HexBin.decode(secreKey),"AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
//初始化解密
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] result = cipher.doFinal(str.getBytes());
return new String(result);
}
}
java中的AES的加密完全依赖 Cipher类。RSA加解密使用也是这个类。
3.RSA加密
RSA是一种非对称加密算法。现在,很多登陆表单的密码的都采用RSA加密,例如京东中的登陆使用公钥对密码进行加密。
RSA这个加密算法就是经常听到非对称密钥加密,顾名思义,RSA加解密使用的不是同一个密钥,RSA中存在两个密钥,一个是公钥,一个是私钥,公钥暴露给数据发送端,私钥在数据接收端保留,数据发送端在数据传输前使用公钥加密数据,数据接受端在接收到数据时使用私钥解密,拿到真实数据。来保证数据传输的安全性。
RSA主要使用大整数分解这个数学难题进行设计,巧妙地利用了数论的概念,如今,只有短的 RSA 密钥才有可能被强力方式解破。长度足够长的密钥是安全系数时很高的。
Java中RSA加密
/**
* RSA加解密工具类
* @author machenike
*/
public class RASUtil {
/**
* 密钥对存储场所
*/
private static KeyStore keyStore = new LocalKeyStore();
/**
* 当前线程持有的公钥
*/
private static ThreadLocal<String> currentPublicKey = new ThreadLocal<>();
/**
* 生成RSA密钥对
* @return
* @throws NoSuchAlgorithmException
*/
public static String[] generateKeyPair(){
// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
KeyPairGenerator keyPairGen = null;
try {
keyPairGen = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
e.printStackTrace();
return null;
}
// 初始化密钥对生成器,密钥大小为96-1024位
keyPairGen.initialize(1024,new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
// 得到私钥
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// 得到公钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
// 得到私钥字符串
String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
// 将公钥和私钥保存到String数组
String[] result = new String[2];
result[0] = publicKeyString;
result[1] = privateKeyString;
return result;
}
/**
* 生成RSA密钥对
* @param keySize 密钥最大长度
* @return
* @throws NoSuchAlgorithmException
*/
public static String[] generateKeyPair(Integer keySize){
// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
KeyPairGenerator keyPairGen = null;
try {
keyPairGen = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
// 初始化密钥对生成器,密钥大小为96-1024位
keyPairGen.initialize(keySize,new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
/* 得到私钥 */
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// 得到公钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
// 得到私钥字符串
String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
// 将公钥和私钥保存到String数组
String[] result = new String[2];
result[0] = publicKeyString;
result[1] = privateKeyString;
return result;
}
/**
* RSA公钥加密
*
* @param str
* 加密字符串
* @param publicKey
* 公钥
* @return 密文
* @throws Exception
* 加密过程中的异常信息
*/
public static String encrypt( String str, String publicKey ) throws Exception{
//base64编码的公钥
byte[] decoded = Base64.decodeBase64(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
//RSA加密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
String outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes("UTF-8")));
return outStr;
}
/**
* 使用私钥进行RSA解密
*
* @param str
* 加密字符串
* @param privateKey
* 私钥
* @return 铭文
* @throws Exception
* 解密过程中的异常信息
*/
public static String decrypt(String str, String privateKey) throws Exception{
//64位解码加密后的字符串
byte[] inputByte = Base64.decodeBase64(str.getBytes("UTF-8"));
//base64编码的私钥
byte[] decoded = Base64.decodeBase64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
String outStr = new String(cipher.doFinal(inputByte));
return outStr;
}
/**
* 通过当前的线程的公钥进行RSA私钥解密
*
* @param str
* 加密字符串
* 私钥
* @return 铭文
* @throws Exception
* 解密过程中的异常信息
*/
public static String decrypt(String str) throws Exception{
//64位解码加密后的字符串
byte[] inputByte = Base64.decodeBase64(str.getBytes("UTF-8"));
//base64编码的私钥
byte[] decoded = Base64.decodeBase64(keyStore.getPrivateKey(currentPublicKey.get()));
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
String outStr = new String(cipher.doFinal(inputByte));
return outStr;
}
/**
* 获取当前线程得公共键
* @param currentPublicKey
*/
public static void setCurrentPublicKey(String currentPublicKey){
RASUtil.currentPublicKey.set(currentPublicKey);
}
/**
* 设定存储密钥的实现
* @param keyStore
*/
public static void setKeyStore(KeyStroke keyStore){
keyStore = keyStore;
}
}
使用KeyPairGenerator生成密钥对,同时服务端会将公钥返回到客户端,客户端是RSA进行加密,而私钥保存在系统内部,不对外暴露,收到数据时使用cipher进行解密。保证数据传输的安全性,就算数据被抓包抓到,只要数据没有经过后台,那么数据就是无法解密。
4.Base64加密
Base64编码是从二进制到字符的过程,可用于在HTTP环境下传递较长的标识信息。采用Base64编码具有不可读性,需要解码后才能阅读
其实base64不算是一种加密方式,只能说是一种特定的编码方式,目的是为了方便数据传输。比如图片可以转成base64字符串进行传输,接收完数据后,在进行base64解码就可把图片还原出来。
Java中Base64
/**
* Base64解码编码工具类
*/
public class Base64Util {
//解码器
private static final Base64.Decoder decoder = Base64.getDecoder();
//编码器
private static final Base64.Encoder encoder = Base64.getEncoder();
/**
* Base64编码
* @param data
* @return
*/
public static String encode(String data){
return encoder.encodeToString(data.getBytes());
}
/**
* Base64解码
* @param data
* @return
*/
public static String decode(String data){
return new String(decoder.decode(data.getBytes()));
}
}
上面所有的常用方法我都已经整理集成到我的安全包里。
https://github.com/DavidLei08/s-encrypt.git