哈希算法
什么是哈希算法?
哈希算法(Hash)又称为是摘要算法(Digest),对任意一组数据利用指定算法得到一个固定长度的输出摘要,其目的是为了验证原始数组是否被篡改;
哈希算法的特点:
相同的输入输出一定是相同的
不同的输入输出大概路是不同的(比如下面的例子)这也就是所谓的哈希碰撞
"AaAaAa".hashCode(); // 0x7460e8c0
"BBAaBB".hashCode(); // 0x7460e8c0
"通话".hashCode(); // 0x11ff03
"重地".hashCode(); // 0x11ff03
怎么避免哈希碰撞?
碰撞能不能避免?答案是不能。碰撞是-
一定会出现的,因为输出的字节长度是固定的,string 的hashcode()输出是 4 字节整数,最多只有 4294967296 种输出,但输入的数据长度是不固定的,有无数种输入。所以,哈希算法是把-一个无限的输入集合映射到一个有限的输出集合,必然会产生碰撞。碰撞不可怕,我们担心的不是碰撞,而是碰撞的概率,因为碰撞概率的高低关系到哈希算法的安全性。
一个安全的哈希算法必须满足:
•碰撞概率低;
• 不能猜测输出。
不能猜测输出是指:输入的任意一个 bit 的变化会造成输出完全不同,这样就很难从输出反推输入(只能依拿暴
力穷举)。
我们为了避免这样的事情发生就出现的几种常见的哈希算发
常见的哈希算法:(根据碰撞的概率,哈希算法发输出长度越长就越难发生碰撞,就越安全)
下面我们就浅浅的介绍一些MD5算法
MD5:
首先我们创建一个MessageDidest对象,然后调用的它Update()方法,这时候注意传参一定是这个参数的字节值,最后就是加密的方法digest();方法了拿到时一个字节数组,然后遍历这个字节数组,同StringBuilder类拼接在一起转化成16进制的字符串。具体的代码如下
import java.security.MessageDigest;
public class main {
public static void main(String[] args) {
// 创建一个MessageDigest实例:
MessageDigest md = MessageDigest.getInstance("MD5");
// 反复调用update输入数据:
md.update("Hello".getBytes("UTF-8"));
md.update("World".getBytes("UTF-8"));
// 16 bytes: 68e109f0f40ca72a15e05cc22786f8e6
byte[] results = md.digest();
StringBuilder sb = new StringBuilder();
for(byte bite : results) {
sb.append(String.format("%02x", bite));
}
System.out.println(sb.toString());
}
}
SHA-1;
SHA-1也是一种哈希算法,它输出160bits,就是20个字节,他有一系列的算法比如SHA-1,SHA-256,SHA-512,q其代码的实现也和MD5的算法大同小异,就是把算法名称改成“SHA-1”
import java.security.MessageDigest;
public class main {
public static void main(String[] args) {
// 创建一个MessageDigest实例:
MessageDigest md = MessageDigest.getInstance("SHA-1");
// 反复调用update输入数据:
md.update("Hello".getBytes("UTF-8"));
md.update("World".getBytes("UTF-8"));
// 20 bytes: db8ac1c259eb89d4a131b253bacfca5f319d54f2
byte[] results = md.digest();
StringBuilder sb = new StringBuilder();
for(byte bite : results) {
sb.append(String.format("%02x", bite));
}
System.out.println(sb.toString());
}
}
那么说了这么多究竟这些算法有什么作用呢?
校验下载的文件
什么意思呢,就是利用相同的输入输出也是相同的,我们在下载一些软件时,经常会下载到一些流氓软件,这就是一些人在其中添加了一些你看不见的东西,所以我们可以下载下来后看一下它的哈希值,看是否和官网爆出的哈希值一样,不一样说明就是有问题的举一个例子。
存储用户密码
这就是算法是第二个作用,你想想数据库管理员知道你的密码,万一数据库被黑客入侵,所有用户的信息就被泄露了,这时候就需要一个算法将用户的密码转化成另一种形式,这样就可以有效的解决了。有人就问那么用户在登录时候怎么办?这时候你是知道那时候是按照什么算法计算出来的,在计算一次,和已经计算出来的哈希值对比一下不就知道了,输出的结果就像下面一样
这时候就会出现另一个名词就是彩虹表,意思就是一些人的密码就是生日啊身份证号码呀什么的。这时候黑客就先计算好一些编码,然后对照着反推出一人的密码,这也就是为什么很重要的密码一般不让使用自己的生日什么,那么什么避免彩虹标呢?就是对每个口令密码额外添加一个随机数,这个方法称为 加盐
简答的代码实现如下
package mds.com;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
// 消息摘要算法工具类
public class MessageDigestUtils {
private static MessageDigest mddigest=null;//下面的方法都是静态方法所以创建一个静态成员变量
// 按照MD5算法计算哈希值
public static String digestByMd5(String source) throws NoSuchAlgorithmException {
//加盐
String salt=UUID.randomUUID().toString().substring(0,5);
mddigest=MessageDigest.getInstance("MD5");
byte[] tempsource=(source+salt).getBytes();
byte[] password=digest(tempsource);
String result=toHexString(password);
return result;
}
// 按照SHA-1算法计算哈希值
public static String digestBySHA1(String source) throws NoSuchAlgorithmException {
//加盐
String salt=UUID.randomUUID().toString().substring(0,5);
mddigest=MessageDigest.getInstance("SHA-1");
byte[] tempsource=(source+salt).getBytes();
byte[] password=digest(tempsource);
String result=toHexString(password);
return result;
}
// 加密
private static byte[] digest(byte[] source) {
mddigest.update(source);
return mddigest.digest();
}
// 将字节数组转换成16进制字符串
private static String toHexString(byte[] byteArray) {
StringBuilder sb=new StringBuilder();
for (byte b : byteArray) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}
Hmac算法;
上面我们说的彩虹表是什么,如何避免彩虹表的出现,这个算法有效的解决了这个问题
1.Hmac 算法就是一种基于密钥的消息认证码算法,它的全称是 Hash-based Message Authentication code是-种史安全的消息摘要算法。
2.Himac 算法总是和某种哈希算法配合起来用的。例如,我们使用 MD5 算法,对应的就是Hmac MD5 算法,它相手“加盐”的 MD5
HmacMD5 = md5 (secure random key, input)
因此,HmacMD5 可以看作带有一个安全的key 的MD5。使用HmaCMD5 而不是用 MD5 加salt,有如下好处:
1.HmaCMD5 使用的 key 长度是 64 字节,更安全;
2.Hmac 是标准算法,同样适用于 SHA-1 等其他哈希算法;
3.Hmac 输出和原有的哈希算法长度—致
可见,Himac 本质上就是把 key 混入摘要的算法。验证此哈希时,除了原始的输入数据,还要提供 key。为了保证安全,我们不会自己指定 key,而是通过 Java 标准库的 KeyGenerator 金成-个安全的随机的 ke。
下面是实现代码:
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
public class main {
public static void main(String[] args) throws NoSuchAlgorithmException, IllegalStateException, UnsupportedEncodingException, InvalidKeyException {
// 获取HmacMD5秘钥生成器
KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5");
// 产生秘钥
SecretKey secreKey = keyGen.generateKey();
// 打印随机生成的秘钥:
byte[] keyArray = secreKey.getEncoded();
StringBuilder key = new StringBuilder();
for(byte bite:keyArray) {
key.append(String.format("%02x", bite));
}
System.out.println(key);
// 使用HmacMD5加密
Mac mac = Mac.getInstance("HmacMD5");
mac.init(secreKey); // 初始化秘钥
mac.update("HelloWorld".getBytes("UTF-8"));
byte[] resultArray = mac.doFinal();
StringBuilder result = new StringBuilder();
for(byte bite:resultArray) {
result.append(String.format("%02x", bite));
}
System.out.println(result);
}
}
这和传统的MD5算法有什么区别?
1•通过名称HmacMD5 获取keyGenerator 实例;
2. 通过KevGenerator 创建一个 seeretxey 实例;
3.通过名称 HmaCMD5 获取Mac 实例;
4. 用 secretkey 初始化Mac实例;
5. 对Mac 实例反复调用 update (byte[])输入数据;
6. 调用Mac 实例的 doFinal()获取最终的哈希值。
那又是怎么恢复,或者是怎么验证的呢?上面加密的时候第一部是生成密钥,然后通过方法加密,只要我们知道随机密钥就能恢复,根据byte[]数组,下面是实现的代码
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
String str= "saikudhgsi";
// 通过密钥是字节数组恢复密钥
byte[] keyarray = { -45, 96, -50, 87, 26, -43, -3, -61, 91, -47, -91, -68, -106, -31, -102, -34, -22, -13, 30,
-42, 119, -59, -55, -48, -123, -21, -55, -68, -23, -54, 27, -50, -91, -63, 74, 79, 71, -51, 114, 66,
-42, -18, 5, -81, 111, 0, -11, -72, 40, -85, 41, 32, 11, 93, -32, -24, -22, -117, 102, 44, 58, -41, -31,
-56 };
// 作用是回复密钥将上面密钥的字节数组恢复成密钥
SecretKey key = new SecretKeySpec(keyarray, "HmacMd5");
// 加密操作
Mac mac = Mac.getInstance("HmacMd5");
// 初始化
mac.init(key);
//更新
mac.update(str.getBytes());
//加密操作
byte[] resultbyte=mac.doFinal();
StringBuilder resultsb = new StringBuilder();
for (byte b : resultbyte) {
resultsb.append(String.format("%02x", b));
}
System.out.println("加密的结果为" + resultsb);
System.out.println("加密的结果长度为" + resultsb.length());
}
Bouncycastle
Bouncycastle就是提供的许多加密算法和哈希算法的第三方开源库,它提供的java核心库没有的算法。比如我们上面说的RipeMD5哈希算法
当然首先我们先把这个第三方的架包导进来,然后算法代码过程其实和上面MD5和SHA-1也是大同小异
public class Main {
public static void main(String[] args) throws Exception {
// 注册BouncyCastle提供的通知类对象BouncyCastleProvider
Security.addProvider(new BouncyCastleProvider());
// 获取RipeMD160算法的"消息摘要对象"(加密对象)
MessageDigest md = MessageDigest.getInstance("RipeMD160");
// 更新原始数据
md.update("HelloWorld".getBytes());
// 获取消息摘要(加密)
byte[] result = md.digest();
// 消息摘要的字节长度和内容
System.out.println(result.length); // 160位=20字节
System.out.println(Arrays.toString(result));
// 16进制内容字符串
String hex = new BigInteger(1,result).toString(16);
System.out.println(hex.length()); // 20字节=40个字符
System.out.println(hex);
}
}
对称加密
什么是对称加密?
是指加密和解密时候用同一个密钥,那么在软件开发中常见的对称加密算法有哪些?
因为DES算法密钥太短所以现在已经不安全了,AES已经取代的DES位置,我们就简单介绍一下AES
AES (Advanced Encryption Standard)是取代其前任标准(DES)而成为新标准的一种对称空码算法。全世界的企业和密码学家提交了多个对称密码算法作为AES的候选,最终在2000年从这些候选算法中选出了一种名为 Riindael 的对称密码算法,并将其确定为了AES.Rindael是由比利时密码学家Joan Daemen和Vincent Rimen设汁的分组密码算法.今后会有越来越多的密码软件支持这种算法Riindael的分组长度为128比特,密钥长度可以以32比特为单位在128比特到256比特的范围内进行选择 (不过 在AES的规格中,密钥长度只有128.192和256比特三种)
import java.security.*;
import java.util.Base64;
import javax.crypto.*;
import javax.crypto.spec.*;
public class Main {
public static void main(String[] args) throws Exception {
// 原文:
String message = "Hello, world!";
System.out.println("Message(原始信息): " + message);
// 128位密钥 = 16 bytes Key:
byte[] key = "1234567890abcdef".getBytes();
// 加密:
byte[] data = message.getBytes();
byte[] encrypted = encrypt(key, data);
System.out.println("Encrypted(加密内容): " +
Base64.getEncoder().encodeToString(encrypted));
// 解密:
byte[] decrypted = decrypt(key, encrypted);
System.out.println("Decrypted(解密内容): " + new String(decrypted));
}
// 加密:
public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
// 创建密码对象,需要传入算法/工作模式/填充模式
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
// 根据key的字节内容,"恢复"秘钥对象
SecretKey keySpec = new SecretKeySpec(key, "AES");
// 初始化秘钥:设置加密模式ENCRYPT_MODE
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
// 根据原始内容(字节),进行加密
return cipher.doFinal(input);
}
// 解密:
public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException {
// 创建密码对象,需要传入算法/工作模式/填充模式
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
// 根据key的字节内容,"恢复"秘钥对象
SecretKey keySpec = new SecretKeySpec(key, "AES");
// 初始化秘钥:设置解密模式DECRYPT_MODE
cipher.init(Cipher.DECRYPT_MODE, keySpec);
// 根据原始内容(字节),进行解密
return cipher.doFinal(input);
}
}
上面是加密和解密的全过程
1.根据算法名称/工作模式/填充模式获取Cipher实例
2.根据算法名称初始换一个SecretKey实例,
3.使用SerectKey初始化一个Ciper实例,并且设置加密还是解密模式
4.doFinal()进行加密或者解密
非对称加密
什么是非对称加密?
首先和对称加密做一个对比,对称加密解密只需要一个密钥,而非对称加密需要两个,分别是公钥和私钥,用自己的公钥加密,解密时候用自己的私钥解密。那么对称加密有主要有AES算法非对称算法也有自己的算那就是RSA算法,以及非对称算法是是怎么实现的?
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.Cipher;
// RSA
public class Main {
public static void main(String[] args) throws Exception {
// 明文:
byte[] plain = "Hello, encrypt use RSA".getBytes("UTF-8");
// 创建公钥/私钥对:
Human alice = new Human("Alice");
// 用Alice的公钥加密:
// 获取Alice的公钥,并输出
byte[] pk = alice.getPublicKey();
System.out.println(String.format("public key(公钥): %x", new BigInteger(1, pk)));
// 使用公钥加密
byte[] encrypted = alice.encrypt(plain);
System.out.println(String.format("encrypted(加密): %x", new BigInteger(1, encrypted)));
// 用Alice的私钥解密:
// 获取Alice的私钥,并输出
byte[] sk = alice.getPrivateKey();
System.out.println(String.format("private key(私钥): %x", new BigInteger(1, sk)));
// 使用私钥解密
byte[] decrypted = alice.decrypt(encrypted);
System.out.println("decrypted(解密): " + new String(decrypted, "UTF-8"));
}
}
// 用户类
class Human {
// 姓名
String name;
// 私钥:
PrivateKey sk;
// 公钥:
PublicKey pk;
// 构造方法
public Human(String name) throws GeneralSecurityException {
// 初始化姓名
this.name = name;
// 生成公钥/私钥对:
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
kpGen.initialize(1024);
KeyPair kp = kpGen.generateKeyPair();
this.sk = kp.getPrivate();
this.pk = kp.getPublic();
}
// 把私钥导出为字节
public byte[] getPrivateKey() {
return this.sk.getEncoded();
}
// 把公钥导出为字节
public byte[] getPublicKey() {
return this.pk.getEncoded();
}
// 用公钥加密:
public byte[] encrypt(byte[] message) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, this.pk); // 使用公钥进行初始化
return cipher.doFinal(message);
}
// 用私钥解密:
public byte[] decrypt(byte[] input) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, this.sk); // 使用私钥进行初始化
return cipher.doFinal(input);
}
}
区别
说完对称加密和非对称加密,那么他们的不同或者说有什么区别?
1.对称加密算法加密和解密使用的 密钥的是相同的,而非对称加密算法有两个空钥,也就是加密和解密所使用的密钥是不同的,加密用公钥,解密是私钥。
2.各自算法不同对称算法是(AES)非对称算法是(RSA)
3.对称加密算法运行速度快,非对称算法运行速度慢。
4安全性不司,对称加密算法,在传输的过程容易被窃听,不安全而能非对称加密, 算法就可以很好的避免这个问题
5.数字签名,对称加密不可以用于数字签名和数宁监别,非对称加口密可以用于数字签名和数字鉴别
版权归原作者 小张要秃头 所有, 如有侵权,请联系我们删除。