数据的加密,在数据传输过程中是常用到的,前面几天在公众号上看到一篇文章讲的加密算法,感觉写得很不错,于是乎记录写自己感觉重要的再加上写自己的理解并粘上代码。
一.什么是加密算法
数据加密的基本过程就是对原来为明文的文件或数据按某种算法进行处理,使其成为不可读的一段代码,通常称为“密文”,使其只能在输入相应的密钥之后才能显示出本来内容,通过这样的途径来达到保护数据不被非法人窃取,阅读的目的。该过程的逆过程为解密,即将该编码信息转化为其原始数据的过程.Java的加密知识也是Java的常见的领域之一,加密技术的底层确实很复杂,运用了大量的数学知识,要弄明白非常复杂。但是Java的语言中运用密码加密工具却是非常简单。我们在Java的里面运用这些加密技术,只需要把原理和使用场景等搞明白就可以了,具体底层实现不用研究。
二,分类
常用的加密算法有对称加密算法,非对称加密算法,哈希算法,数字签名等几类。
对称加密顾名思义就是加密和解密是对称的,加密时用一个秘钥去加密,解密时用同一个秘钥去解密,由信息发送方和接收方共同约定一个秘钥。缺点是风险都在这个秘钥上面,一旦被窃取,信息会暴露。所以安全级别不够高。常用对称加密算法有DES,3DES,AES等。在JDK中也都有封装。
非对称加密,顾名思义就是加密与解密的过程不是对称的,不是用的同一个秘钥。非对称加密有个公私钥对的概念,也就是有两把秘钥,一把是公钥,一把是私钥,一对公私钥有固定的生成方法,在加密的时候,用公钥去加密,接收方再用对应的私钥去解密。使用时可以由接收方生成公私钥对,然后将公钥传给加密方,这样私钥不会在网络中传输,没有被窃取的风险。比如GitHub的底层的SSH协议就是公私钥非对称加密。并且公钥是可以由私钥推导出来的,反过来却不行,由通过公钥无法推导出私钥。常用算法有RSA,ECC等.ECC也是比特币底层用的比较多的算法。通过和对称加密的对比,可以看到,非对称加密解决了秘钥传输中的安全问题。
哈希算法,简单说就是将任意数据都转换成一个固定长度的字符串。通过哈希后的值几乎无法推导出原文。而且两个不同的原文哈希后结果一定不同。常用算法有MD5, SHA256等等。常用场景,MD5常用场景是数据库的密码存储.sha256在挖矿中可以用到。
非对称加密也有一个问题,就是内容在发送前可能被篡改,因为公钥是有可能被窃取的,所以窃取者完全可以改为发送别的内容。
解决的办法就是数字签名。数字签名和非对称加密是反过来的,也是有公私钥对,但是是用私钥签名,用公钥去验证签名。比如发送方除了发送用公钥加密后的密文,还要发送签名,签名内容通常是密文哈希后的字符串,接收方首先验证签名是否正确,如果正确那么密文解密后就是真正需要并且没有被篡改过的内容。注意,签名和非对称用的是两对不同的公私钥。
上面是对几个加密算法的一个简单讲解,除了上面的还有base58等,比特币底层安全也是依赖于加密。后面会一个一个介绍要用到的加密算法的介绍和使用,但是仅仅是使用,底层不会讲。
三,算法详解
MD5
MD5即消息摘要算法5(信息 - 摘要算法5),用于确保信息传输完整一致。是计算机广泛使用的杂凑算法之一(又译摘要算法,哈希算法),主流编程语言普遍已有MD5实现。将数据(如汉字)运算为另一固定长度值,是杂凑算法的基础原理,MD5的前身有MD2,MD3和MD4。
MD5算法具有以下特点:
1,压缩性:任意长度的数据,算出的MD5值长度都是固定的。
2,容易计算:从原数据计算出MD5值很容易。
3,抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
4,强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
MD5的作用是让大容量信息在用数字签名软件签署私人密钥前被"压缩"成一种保密的格式(就是把一个任意长度的字节串变换成一定长的十六进制数字串)。除了MD5以外,其中比较有名的还有sha-1、RIPEMD以及Haval等。
JDK就自带了md5加密算法,直接调用很方便。需要引入一个类:
import java.security.MessageDigest;
具体代码如下(直接copy就可以使用):
import java.security.MessageDigest;
/**
* @version :2018年3月11日 上午10:35:12 类说明
*/
public class MD5Util {
public static String bytesToHexString(byte[] src) {
StringBuilder stringBuilder = new StringBuilder("");
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
/**
* 解析
*
* @param hexString
* @return
*/
public static byte[] hexStringToBytes(String hexString) {
if (hexString == null || hexString.equals("")) {
return null;
}
hexString = hexString.toUpperCase();
int length = hexString.length() / 2;
char[] hexChars = hexString.toCharArray();
byte[] d = new byte[length];
for (int i = 0; i < length; i++) {
int pos = i * 2;
d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
}
return d;
}
/**
* 将指定byte数组以16进制的形式打印到控制台
*
* @param b
*/
public static void printHexString(byte[] b) {
for (int i = 0; i < b.length; i++) {
String hex = Integer.toHexString(b[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
System.out.print(hex.toUpperCase());
}
}
/**
* Convert char to byte
*
* @param c
* char
* @return byte
*/
private static byte charToByte(char c) {
return (byte) "0123456789abcdef".indexOf(c);
}
/**
* 加密
*
* @param str
* @return
*/
public static String encodeMD5(String str) {
String strDigest = "";
try {
// 此 MessageDigest 类为应用程序提供信息摘要算法的功能,必须用try,catch捕获
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] data = md5.digest(str.getBytes("utf-8"));// 转换为MD5码
strDigest = bytesToHexString(data);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
return strDigest;
}
}
SHA25
学Java的对哈希算法都不陌生,毕竟每个类都有hashCode方法。
散列算法(Hash Algorithm),又称哈希算法,杂凑算法,是一种从任意文件中创造小的数字「指纹」的方法。与指纹一样,散列算法就是一种以较短的信息来保证文件唯一性的标志,这种标志与文件的每一个字节都相关,而且难以找到逆向规律。因此,当原有文件发生改变时,其标志值也会发生改变,从而告诉文件使用者当前的文件已经不是你所需求的文件。
一个优秀的 hash 算法,将能实现:
正向快速:给定明文和 hash 算法,在有限时间和有限资源内能计算出 hash 值。
逆向困难:给定(若干) hash 值,在有限时间内很难(基本不可能)逆推出明文。
输入敏感:原始输入信息修改一点信息,产生的 hash 值看起来应该都有很大不同。
冲突避免:很难找到两段内容不同的明文,使得它们的 hash 值一致(发生冲突)。即对于任意两个不同的数据块,其hash值相同的可能性极小;对于一个给定的数据块,找到和它hash值相同的数据块极为困难。
但在不同的使用场景中,如数据结构和安全领域里,其中对某一些特点会有所侧重。
安全散列算法(英语:Secure Hash Algorithm,缩写为SHA)是一个密码散列函数家族,是FIPS所认证的安全散列算法。能计算出一个数字消息所对应到的,长度固定的字符串(又称消息摘要)的算法。且若输入的消息不同,它们对应到不同字符串的机率很高。
SHA家族的五个算法,分别是SHA-1,SHA-224,SHA-256,SHA-384,和SHA-512。主要适用于数字签名标准(DigitalSignature Standard DSS)里面定义的数字签名算法(Digital Signature)算法DSA)。比特币里面的就是SHA-256算法。
说简单一些,就是对一个对象的多个关键不重复信息组合起来,通过算法生成一个加密字符串。
引入的加密类和MD5一样:
import java.security.MessageDigest;
下面是算法的具体内容:
/**
* 利用java原生的摘要实现SHA256加密
* @param str 加密后的报文
* @return
*/
public static String getSHA256StrJava(String str){
MessageDigest messageDigest;
String encodeStr = "";
try {
messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(str.getBytes("UTF-8"));
encodeStr = byte2Hex(messageDigest.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return encodeStr;
}
/**
* 将byte转为16进制
* @param bytes
* @return
*/
private static String byte2Hex(byte[] bytes){
StringBuffer stringBuffer = new StringBuffer();
String temp = null;
for (int i=0;i<bytes.length;i++){
temp = Integer.toHexString(bytes[i] & 0xFF);
if (temp.length()==1){
//1得到一位的进行补0操作
stringBuffer.append("0");
}
stringBuffer.append(temp);
}
return stringBuffer.toString();
}
AES
AES是一个对称密码,旨在取代DES成为广泛使用的标准。
具体代码如下:
/**
* aes加密
* @param content
* @param password
* @return
*/
public static String encrypt(String content, String password) {
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(password.getBytes());
kgen.init(128, random);
SecretKey secretKey = kgen.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
Cipher cipher = Cipher.getInstance("AES");
byte[] byteContent = content.getBytes("utf-8");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] result = cipher.doFinal(byteContent);
String str = Base64.getEncoder().encodeToString(result);
return str;
} catch (NoSuchAlgorithmException e) {
// e.printStackTrace();
} catch (NoSuchPaddingException e) {
// e.printStackTrace();
} catch (InvalidKeyException e) {
// e.printStackTrace();
} catch (UnsupportedEncodingException e) {
// e.printStackTrace();
} catch (IllegalBlockSizeException e) {
// e.printStackTrace();
} catch (BadPaddingException e) {
// e.printStackTrace();
}
return null;
}
/**
* aes解密
* @param str
* @param password
* @return
*/
public static String decrypt(String str, String password) {
try {
byte[] content = Base64.getDecoder().decode(str);
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(password.getBytes());
kgen.init(128, secureRandom);
SecretKey secretKey = kgen.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
Cipher cipher = Cipher.getInstance("AES");// 创建密码器
cipher.init(Cipher.DECRYPT_MODE, key);// 初始化
byte[] result = cipher.doFinal(content);
return new String(result,"UTF-8");
} catch (NoSuchAlgorithmException e) {
// e.printStackTrace();
} catch (NoSuchPaddingException e) {
// e.printStackTrace();
} catch (InvalidKeyException e) {
// e.printStackTrace();
} catch (IllegalBlockSizeException e) {
// e.printStackTrace();
} catch (BadPaddingException e) {
// e.printStackTrace();
} catch (Exception e) {
// e.printStackTrace();
}
return "";
}