(1)对称加密算法
对称加密算法中,加密用的密钥和解密用的密钥是一样的,也就是说,加密和解密使用同一个密钥,密钥的保存和安全交换是一个问题。对称加密算法有DES(data encryption standard)数据加密标准,3DES(DESede),AES(Advanced encryption standard)高级加密标准。
(2)单向加密算法
MD4,MD5,SHA192,SHA256,SHA512单向的不可逆的摘要算法,用于验证数据的完整性。摘要算法的输出是固定的,192,256指的都是输出摘要的长度,例如MD5摘要算法就是把一个明文摘要成一个长16字节的结果,只要原文有一点变化,那么摘要结果就会完全不同(雪崩效应),不能从摘要结果去推到原文。比如Linux系统中的账号密码文件就是摘要以后的数据存放进去的,以确保安全。为了更好的提高安全性,也可以使用加盐(在明文中添加某些信息,混乱明文以后再进行摘要)的方法。目前MD5,SHA1都被淘汰了,他们的安全性不足,推荐使用SHA256以及以上的算法。
(3)非对称加密
在非对称加密算法中,加密使用的密钥和解密使用的密钥是不同的。非对称加密生成一个公钥和一个私钥,加密与解密只能在一对私钥和公钥之间进行,公钥加密的私钥解密。RSA,DSA是两个非对称加密算法,用于数字签名和加密,其中DSA只能用于签名
RSA密码体制是一种公钥密码体制,公钥公开,私钥保密,它的加密解密算法是公开的。由公钥加密的内容可以,并且只能由私钥解密,并且由私钥加密的内容可以并且只能由公钥进行解密。由此可以说,RSA的这一对公钥,私钥都可以用来进行加密和解密,并且一方加密的内容只能由对方进行解密,客户端用公钥加密的数据,之能是认证的服务端使用相应私钥解密。这也是这个算法能用于身份识别(数字签名)的原因。
为了实现身份认证,RSA体系中的私钥加密可以实现身份验证,验证过程是用私钥加密,传给对方,对方用之前获得的公开的公钥解密,只要能顺利的解开,就能验证对方的身份是合法的。
要谈RSA与完整性的关系。首先要说明什么是完整性。完整性就是保证传递的信息是通信双方原本想传递的内容,没有被破坏和篡改。签名保证了完整性。
这里介绍一下签名,签名就是在信息的后面再加上一段内容,可以证明信息没有被修改过。要想实现该目的,一般是对信息做一个散列计算得到一个hash值,这个过程是不可逆的(使用摘要算法),也就是说,无法通过hash值来推导出原来的明文。把这个hash值加密后作为一个签名与信息一同发出,接收方再收到以后,会重新计算信息的hash值,并和信息所附带的hash值(解密后)进行对比,如果一致,则说明信息的内容没有被修改过,因为hash计算可以保证不同的内容一定会得到不同的hash值,所以只要内容一旦被篡改,根据信息内容计算出来的hash值就会改变。
如果不加密的话,攻击者可以修改信息内容的同时也修改hash值,从而让它们可以相匹配,为防止这种情况,hash值一般都会加密后(也就是签名)再和信息一起发送,以保证这个hash值不被修改。
使用RSA来处理这个签名,用公钥加密“明文”+“哈希值”,私钥解密得到明文。当然,简单的对称加密也可以实现完整性的验证,但是不能实现身份认证。
非对称加密效率是很低的,比对称加密低出3个数量级左右。
混用对称加密与非对称加密,数据加密时使用对称加密算法,再把对称加密算法的密钥和完整性校验值(hash值),身份认证标识,一起使用RSA密钥加密,对方接受到之后先使用RSA密钥解密,得到对称密钥,完整性校验值和身份认证标识,看到对方认证没问题,再使用对称密钥解密密文,并验证完整性。大致流程图如下:
public class DigestUtils {
//这种做法是线程不安全的
private static Map<String,MessageDigest> map = new HashMap<String ,MessageDigest>();
public static MessageDigest getMDInstance (String algorithm) {
try {
if(map.get(algorithm) == null){
MessageDigest md = MessageDigest.getInstance(algorithm);
map.put(algorithm, md);
}
return map.get(algorithm);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
/**
* 通过摘要算法将所传入字符串加密,目前MD5和SHA1已经不安全了。而实际中SHA1加密强度是61位
* @param str 要加密的字符串
* @param list 可选摘要算法的列表,包括MD5.SHA1,SHA256,SHA512等算法
* @return
*/
public static byte[] encryptByDigest(String str,DigestAlgortihmList list){
if(str == null || str.length() == 0){
return null;
}
MessageDigest md;
try{
//这样做是线程安全的
md = MessageDigest.getInstance(list.getName());
md.update(str.getBytes("utf-8"));
return md.digest();
}
catch (Exception e){
e.printStackTrace();
return null;
}
}
/**
* 将字节数组转换为十六进制字符串
* @param array
* @return
*/
public static String parseToHex(byte[] array){
if(array == null || array.length == 0){
return null;
}
StringBuffer sb = new StringBuffer();
for(Byte b : array){
//System.out.println("b:" + b);
//System.out.println(Integer.toHexString(b));
/**
* 在此对这段代码进行解释:b是一个8位的二进制串,以十进制显示。比如说b是1010 1011
* 那么将b右移四位之后变为0000 1010 然后和0xf进行与操作,为0000 1010 这是s1
*/
//高四位转变为16进制
String s1 = Integer.toHexString((b >> 4) & 0xf);
//System.out.println("s1:" + s1);
//低四位转变为16进制
String s2 = Integer.toHexString(b & 0xf);
//System.out.println("s2:" + s2);
sb.append(s1).append(s2);
}
return sb.toString();
}
/**
* 对文件进行摘要,将文件读为字节流之后进行摘要
* @param path 文件路径
* @param list 选择摘要算法
* @return
*/
public static String digestOneFile(String path,DigestAlgortihmList list){
File file = new File(path);
FileInputStream fin;
try {
fin = new FileInputStream(file);
FileChannel ch = fin.getChannel(); //文件通道
MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY,0,file.length());//字节缓冲流
MessageDigest md = MessageDigest.getInstance(list.getName());
md.update(byteBuffer);
byte[] result = md.digest();
return parseToHex(result);
}
catch (Exception e){
e.printStackTrace();
return null;
}
}
}
public class TestDigest {
public static void main(String[] args) throws NoSuchAlgorithmException, CloneNotSupportedException {
String temp = "178458976218971Hello world你好啊";
byte[] arr = DigestUtils.encryptByDigest(temp,DigestAlgortihmList.SHA1);
String result = DigestUtils.parseToHex(arr);
System.out.println("一段字符串使用SHA1摘要后的结果:" + result);
//测试线程安全性
// for(int i=0;i < 100;i ++){
// Thread thread = new Thread(new Runnable() {
// @Override
// public void run() {
// String temp = "178458976218971Hello world你好啊";
// byte[] arr = DigestUtils.encryptByDigest(temp,DigestAlgortihmList.SHA1);
// String result = DigestUtils.parseToHex(arr);
// System.out.println(result);
// }
// });
// thread.start();
// }
String path = "D:\\apache-tomcat-7.0.52.zip";
String fileDigestResult = DigestUtils.digestOneFile(path,DigestAlgortihmList.SHA1);
System.out.println("文件使用SHA1摘要后的结果:" + fileDigestResult);
MessageDigest md = MessageDigest.getInstance(DigestAlgortihmList.SHA1.getName());
md.update("178458976218971".getBytes());
MessageDigest md1 = (MessageDigest)md.clone(); //浅克隆md
byte[] arr1 = md1.digest();
md.update("Hello world".getBytes());
MessageDigest md2 = (MessageDigest)md.clone();
byte[] arr2 = md2.digest();
md.update("你好啊".getBytes());
MessageDigest md3 = (MessageDigest)md.clone();
byte[] arr3 = md.digest(); //最后调用md复位
//会发现这样做完之后是对“178458976218971Hello world你好啊”的累加 与直接进行该字符串累加是一样的
System.out.println("累加摘要的测试");
System.out.println(DigestUtils.parseToHex(arr1));
System.out.println(DigestUtils.parseToHex(arr2));
System.out.println(DigestUtils.parseToHex(arr3));
}
}
AES算法测试:
public static byte[] encryptByAES128(String str,String random_str) {
if (str==null || str.length()==0 || random_str==null || random_str.length()==0) {
return null;
}
KeyGenerator kgen;
try {
//构建一个密钥生成器
kgen = KeyGenerator.getInstance("aes");
//初始化,将随机串放入,初始化为128位
kgen.init(128, new SecureRandom(random_str.getBytes()));
SecretKey secretKey = kgen.generateKey();
System.out.println(secretKey.getEncoded().length);
//获取原始密钥字节数组,new出新的安全密钥
SecretKeySpec key = new SecretKeySpec(secretKey.getEncoded(), "AES");
//创建加密器 指定加密算法 默认模式ECB,有填充 , AES/ECB/PKCS5Padding (128)
//也可以选择 AES/ECB/NoPadding 算法是AES 模式是ECB 无填充
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
System.out.println(cipher.getBlockSize());
//使用CBC模式需要构造一个初始向量
String str_iv = "8144660571234567";
IvParameterSpec iv = new IvParameterSpec(str_iv.getBytes("UTF-8"));
//初始化加密器 选定模式,并将之前准备好的密钥放进去,如果选择了CBC模式需要添加进去初始向量
cipher.init(Cipher.ENCRYPT_MODE, key,iv);
// xxxx xxxx xxxx xxxx xxxW -> key --> XX XXXXXXX XX X
// xxxx^IV -key -> ^xxxx -key->^xxxx -key-> WW WW WWWWW WW
// 0000 0001 0002 0003 0004
// c1(xor v) c2 c3 c4
// enc1 enc2 enc3 enc4
// String str_iv = "14725836abcdefgx";
// IvParameterSpec iv = new IvParameterSpec(str_iv.getBytes("UTF-8"));
// cipher.init(Cipher.ENCRYPT_MODE, key,iv);
byte[] byteContent = str.getBytes("utf-8");
return cipher.doFinal(byteContent);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static byte[] dencryptByAES128(byte[] arr,String random_str) {
if (arr==null || arr.length==0 || random_str==null || random_str.length()==0) {
return null;
}
KeyGenerator kgen;
try {
//构建一个密钥生成器
kgen = KeyGenerator.getInstance("AES");
//初始化,将随机串放入
kgen.init(128, new SecureRandom(random_str.getBytes()));
SecretKey secretKey = kgen.generateKey();
//获取原始密钥字节数组,new出新的安全密钥
SecretKeySpec key = new SecretKeySpec(secretKey.getEncoded(), "AES");
//创建加密器 指定加密算法
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//初始化加密器 选定模式,并将之前准备好的密钥放进去
//cipher.init(Cipher.DECRYPT_MODE, key);
//使用CBC模式需要构造一个初始向量
String str_iv = "8144660571234567";
IvParameterSpec iv = new IvParameterSpec(str_iv.getBytes("UTF-8"));
cipher.init(Cipher.DECRYPT_MODE, key,iv);
return cipher.doFinal(arr);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public void testAES() throws UnsupportedEncodingException {
String str = "123456789Hello world你好";
String random_str = "echored112240xqezf";
byte[] result = AESUtils.encryptByAES128(str,random_str);
System.out.println(HexParseUtils.parseToHex(result));
String s1 = HexParseUtils.parseToHex(result);
result = HexParseUtils.parseTobyteArray(s1);
byte[] result2 = AESUtils.dencryptByAES128(result,random_str);
System.out.println(new String(result2,"utf-8"));
}
ECB模式:Electronic Code Book Mode 电子密码本模式,需要对明文进行分组,分组长度可以为128,256或者512bit。优点:操作简单,容易实现,每个分组独立,误差不被传送,易于并行。缺点:暴漏明文信息的结构,相同的明文会得到相同的密文。
CBC模式:Cipher Blocking Chaining Mode 加密链模式。先将明文分成若干小段,然后每一小段与初始块(初始值,初始向量,IV)或者上一段密文进行异或运算之后,再用密钥进行加密。与前面的异或后再加密。优点是:能够掩盖明文的结构,也就是相同明文会得出不同的密文,安全性好于ECB,适合传输长的明文,是SSL和IPSec的标准。缺点是:无法进行并行运算,有填充,不适合流加密,存在传递误差,一步错,步步错。
计算器模式(Counter(CTR)):CTR模式中有一个自增的计数器,将从计数器中得到的数字(参数值)用密钥加密之后与明文做异或操作得出密文,相当于一次一密。优点是:简单快速,安全可靠,可并行加密;误差不会传递。缺点是:计数器不易得到。
输出反馈模式(OFB)
密码反馈模式(Cipher FeedBack(CFB))
GCM(GMAC Counter Mode):对称加密采用Counter模式,并附带有GMAC消息认证码。GMAC中的G就是指GMAC,C就是指CTR,GCM可以同时完成加密和完整性校验,另外还可以提供附加消息的完整性校验。
在实际应用场景中,有些信息是不需要保密的,但是信息的接收者需要确认它的真实性。例如源IP,源端口,目的IP,IV,等等。因此,我们可以将这一部分作为附加消息,加入到MAC值的计算当中。接收方会收到密文,计数器CTR的初始值,MAC值。其优点时效率高,可并行,误差不传播,可同时完成完整性校验,无填充(适合流加密)
那么说了一些有关MAC的东西,什么是MAC呢?MAC(Message Authentication Code 消息验证码),它是用来校验密文完整性的,收发双方共享一个密钥。密文发送者将密文的MAC值随密文一起发送,密文接收者通过密钥解密收到MAC值。这样就可以对收到的密文做完整性检验。
攻击者在篡改密文之后,因为没有密钥,也就相应无法计算出篡改后的密文的MAC值。如果加密模式是CTR,或者是其他有初始IV的加密模式,初始的计时器或吃屎向量的值作为附加消息也与密文一起计算MAC。GMAC(Galois message authentication code,伽罗瓦消息验证码),是利用伽罗瓦域乘法运算来计算消息的MAC值。假设密钥长度为128bits,当密文大于128bits时,需要将密文按128bits进行分组。