当前位置:   article > 正文

Java加密总结:常见哈希算法总结、对称加密算法与非对称加密算法的对比_哈希算法各个网站加密结果不同

哈希算法各个网站加密结果不同

哈希算法(Hash)又称摘要算法(Digest),它的作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要。哈希算法的目的:为了验证原始数据是否被篡改。

哈希算法最重要的特点就是:

相同的输入一定得到相同的输出;

不同的输入大概率得到不同的输出。

哈希碰撞:哈希碰撞是指,两个不同的输入得到了相同的输出。如:

  1. "AaAaAa".hashCode(); // 0x7460e8c0
  2. "BBAaBB".hashCode(); // 0x7460e8c0
  3. "通话".hashCode(); // 0x11ff03
  4. "重地".hashCode(); // 0x11ff03

哈希碰撞是不可避免的,因为输出的字节长度是固定的,String的hashCode()输出是4字节整数,最多只有4294967296种输出,但输入的数据长度是不固定的,有无数种输入。所以,哈希算法是把一个无限的输入集合映射到一个有限的输出集合,必然会产生碰撞。既然哈希碰撞不可避免,那我们就得想办法去降低这种碰撞的概率,因为碰撞概率的高低关系到哈希算法的安全性。一个安全的哈希算法必须满足:
●碰撞概率低;
●不能猜测输出。
不能猜测输出是指:输入的任意一个bit的变化会造成输出完全不同,这样就很难从输出反推输入(只能依靠暴力穷举)。

常见的哈希算法有:

常见的哈希算法
算法输出长度(位)输出长度(字节)
MD5128 bits16 bytes
SHA-1160 bits20 bytes
RipeMD-160160 bits20 bytes
SHA-256256 bits32 bytes
SHA-512512 bits64 bytes

首先我们来看用MD5算法来计算哈希,代码实现如下:

  1. package com.hpc.wyj02;
  2. import java.security.MessageDigest;
  3. import java.security.NoSuchAlgorithmException;
  4. import java.util.Arrays;
  5. public class Demo01 {
  6. public static void main(String[] args) {
  7. try {
  8. //获取基于MD5加密算法的工具对象
  9. MessageDigest md5=MessageDigest.getInstance("MD5");
  10. //更新原始数据
  11. md5.update("Hello".getBytes());
  12. md5.update("World".getBytes());
  13. //获取加密后的结果
  14. byte[] result=md5.digest();
  15. System.out.println(Arrays.toString(result));
  16. StringBuilder sb = new StringBuilder();
  17. for(byte bite : result) {
  18. sb.append(String.format("%02x", bite));
  19. }
  20. System.out.println(sb.toString());
  21. } catch (NoSuchAlgorithmException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. }

运行结果如下:

哈希算法的用途:

1、校验下载文件

因为相同的输入永远会得到相同的输出,因此,如果输入被修改了,得到的输出就会不同。我们在网站上下载软件的时候,经常看到下载页显示的MD5哈希值:

 我们只需要自己计算一下本地文件的哈希值,再与官网公开的哈希值对比,如果相同,说明文件下载正确,否则,说明文件已被篡改。

2、存储用户密码

如果直接将用户的原始口令存放到数据库中,会产生极大的安全风险:
●数据库管理员能够看到用户明文口令;
●数据库数据一旦泄漏,黑客即可获取用户明文口令。

为了防止这些安全问题,我们可以存储用户口令的哈希,例如,MD5。在用户输入原始口令后,系统计算用户输入的原始口令的MD5并与数据库存储的MD5对比,如果一致,说明口令正确,否则,口令错误。这样,即使数据库泄漏,黑客也无法拿到用户的原始口令。

使用哈希口令时,还要注意防止彩虹表攻击。什么是彩虹表呢?上面讲到了,如果只拿到MD5,从MD5反推明文口令,只能使用暴力穷举的方法。然而黑客并不笨,暴力穷举会消耗大量的算力和时间。但是,如果有一个预先计算好的常用口令和它们的MD5的对照表,这个表就是彩虹表。如果用户使用了常用口令,黑客从MD5一下就能反查到原始口令:

 这就是为什么不要使用常用密码,以及不要使用生日作为密码的原因。

当然,我们也可以采取特殊措施来抵御彩虹表攻击:对每个口令额外添加随机数,这个方法称之为加盐(salt):
digest = md5(salt + inputPassword)
经过加盐处理的数据库表,内容如下:

 SHA-1也是一种哈希算法,它的输出是160 bits,即20字节。SHA-1是由美国国家安全局开发的,SHA算法实际上是一个系列,包括SHA-0(已废弃)、SHA-1、SHA-256、SHA-512等。
在Java中使用SHA-1,和MD5完全一样,只需要把算法名称改为"SHA-1":

  1. package com.hpc.wyj02;
  2. import java.security.MessageDigest;
  3. import java.security.NoSuchAlgorithmException;
  4. import java.util.Arrays;
  5. public class Demo01 {
  6. public static void main(String[] args) {
  7. try {
  8. //获取基于SHA-1加密算法的工具对象
  9. MessageDigest md5=MessageDigest.getInstance("SHA-1");
  10. //更新原始数据
  11. md5.update("Hello".getBytes());
  12. md5.update("World".getBytes());
  13. //获取加密后的结果
  14. //20 bytes
  15. byte[] result=md5.digest();
  16. System.out.println(Arrays.toString(result));
  17. StringBuilder sb = new StringBuilder();
  18. for(byte bite : result) {
  19. sb.append(String.format("%02x", bite));
  20. }
  21. System.out.println(sb.toString());
  22. } catch (NoSuchAlgorithmException e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. }

运行结果如下:

当我们想用某种算法,Java标准库没有提供怎么办?这时,我们就得找一个现成的第三方库,直接使用。

BouncyCastle就是一个提供了很多哈希算法和加密算法的第三方开源库。它提供了Java标准库没有的一些算法,例如,RipeMD160哈希算法。RIPEMD160是一种基于Merkle-Damgård结构的加密哈希函数,它是比特币标准之一。RIPEMD-160是RIPEMD算法的增强版本,RIPEMD-160算法可以产生出160位的的哈希摘要。

当我们要用这种算法时,我们必须把BouncyCastle提供的bcprov-jdk15on-1.70.jar添加至classpath。这个jar包可以从官方网站下载。

Java标准库的java.security包提供了一种标准机制,允许第三方提供商无缝接入。我们要使用BouncyCastle提供的RipeMD160算法,需要先把BouncyCastle注册一下:

  1. package com.hpc.wyj03;
  2. import java.math.BigInteger;
  3. import java.security.MessageDigest;
  4. import java.security.NoSuchAlgorithmException;
  5. import java.security.Security;
  6. import java.util.Arrays;
  7. import org.bouncycastle.jce.provider.BouncyCastleProvider;
  8. public class Demo01 {
  9. public static void main(String[] args) {
  10. try {
  11. //注册BouncyCastle提供的通知类对象BouncyCastleProvider
  12. Security.addProvider(new BouncyCastleProvider()) ;
  13. //获取RipeMD160算法的”消息摘要对象“(加密对象)
  14. MessageDigest digest=MessageDigest.getInstance("RipeMD160");
  15. //更新原始数据
  16. digest.update("HelloWord".getBytes());
  17. //获取消息摘要
  18. byte[] result=digest.digest();
  19. //输出消息摘要的内容和字节长度
  20. System.out.println(Arrays.toString(result));
  21. System.out.println(result.length);
  22. //16进制字符串内容
  23. String s=new BigInteger(1,result).toString(16);
  24. System.out.println(s); //20字节=40字符
  25. System.out.println(s.length());
  26. } catch (NoSuchAlgorithmException e) {
  27. e.printStackTrace();
  28. }
  29. }
  30. }

运行结果如下:

 对称加密算法

对称加密算法就是传统的用一个密码进行加密和解密。在软件开发中,常用的对称加密算法有:

注意:DES算法由于密钥过短,可以在短时间内被暴力破解,所以现在已经不安全了。

使用AES加密:

 AES算法是目前应用最广泛的加密算法。比较常见的工作模式是ECB和CBC。


用ECB模式加密和解密结果如下:

  1. package com.hpc.wyj03;
  2. import java.security.GeneralSecurityException;
  3. import java.security.NoSuchAlgorithmException;
  4. import java.util.Arrays;
  5. import java.util.Base64;
  6. import javax.crypto.Cipher;
  7. import javax.crypto.NoSuchPaddingException;
  8. import javax.crypto.SecretKey;
  9. import javax.crypto.spec.SecretKeySpec;
  10. /**
  11. * AES
  12. * EBC
  13. * @author 我
  14. *
  15. */
  16. public class Demo02 {
  17. public static void main(String[] args) throws
  18. NoSuchAlgorithmException, GeneralSecurityException {
  19. //原文
  20. String s="lovejj";
  21. //秘钥数组
  22. //128=16 bytes
  23. byte[] key="qwertyuiop123456".getBytes();
  24. System.out.println("秘钥数组:"+Arrays.toString(key));
  25. System.out.println("秘钥数组长度:"+key.length+"字节");
  26. //加密
  27. byte[] data=s.getBytes();
  28. byte[] encrypted=encrypt(key, data);
  29. System.out.println("Encrypted(加密):"
  30. +Base64.getEncoder().encodeToString(encrypted));
  31. //解密
  32. byte[] decrypted=decrypt(key, encrypted);
  33. System.out.println("Decrypted(解密):"+new String(decrypted));
  34. }
  35. //加密方法
  36. public static byte[] encrypt(byte[] key,byte[] input) throws
  37. NoSuchAlgorithmException, GeneralSecurityException {
  38. //创建密码对象,需要传入算法/工作模式/填充模式
  39. Cipher cipher=Cipher.getInstance("AES/ECB/PKCS5Padding");
  40. //根据key的字节内容,”恢复“秘钥对象
  41. SecretKey keySpec=new SecretKeySpec(key, "AES");
  42. //初始化秘钥:设置加密模式ENCRYPT_MODE
  43. cipher.init(Cipher.ENCRYPT_MODE,keySpec);
  44. //根据原始内容(字节),进行加密
  45. return cipher.doFinal(input);
  46. }
  47. //解密方法
  48. public static byte[] decrypt(byte[] key,byte[] input) throws
  49. NoSuchAlgorithmException, GeneralSecurityException {
  50. //创建密码对象,需要传入算法/工作模式/填充模式
  51. Cipher cipher=Cipher.getInstance("AES/ECB/PKCS5Padding");
  52. //根据key的字节内容,”恢复“秘钥对象
  53. SecretKey keySpec=new SecretKeySpec(key, "AES");
  54. //初始化秘钥:设置解密模式DECRYPT_MODE
  55. cipher.init(Cipher.DECRYPT_MODE,keySpec);
  56. //根据原始内容(字节),进行解密
  57. return cipher.doFinal(input);
  58. }
  59. }

运行结果如下:

 Java标准库提供的对称加密接口非常简单,使用时按以下步骤编写代码:


1根据算法名称/工作模式/填充模式获取Cipher实例;
2根据算法名称初始化一个SecretKey实例,密钥必须是指定长度;
3使用SerectKey初始化Cipher实例,并设置加密或解密模式;
4传入明文或密文,获得密文或明文。

ECB模式是最简单的AES加密模式,它只需要一个固定长度的密钥,固定的明文会生成固定的密文,这种一对一的加密方式会导致安全性降低,更好的方式是通过CBC模式,它需要一个随机数作为IV参数,这样对于同一份明文,每次生成的密文都不同,具体实现代码如下:

  1. package com.hpc.wyj03;
  2. import java.security.GeneralSecurityException;
  3. import java.security.NoSuchAlgorithmException;
  4. import java.security.SecureRandom;
  5. import java.util.Arrays;
  6. import java.util.Base64;
  7. import javax.crypto.BadPaddingException;
  8. import javax.crypto.Cipher;
  9. import javax.crypto.IllegalBlockSizeException;
  10. import javax.crypto.NoSuchPaddingException;
  11. import javax.crypto.SecretKey;
  12. import javax.crypto.spec.IvParameterSpec;
  13. import javax.crypto.spec.SecretKeySpec;
  14. public class Demo03 {
  15. public static void main(String[] args) throws
  16. NoSuchPaddingException, GeneralSecurityException {
  17. //原文
  18. String s="lovejj";
  19. //秘钥数组
  20. //256=32 bytes
  21. byte[] key="qwertyuiopasdfghjklzxcvbnm123456".getBytes();
  22. System.out.println("秘钥数组:"+Arrays.toString(key));
  23. System.out.println("秘钥数组长度:"+key.length+"字节");
  24. //加密
  25. byte[] data=s.getBytes();
  26. byte[] encrypted=encrypt(key, data);
  27. System.out.println("Encrypted(加密):"
  28. +Base64.getEncoder().encodeToString(encrypted));
  29. //解密
  30. byte[] decrypted=decrypt(key, encrypted);
  31. System.out.println("Dncrypted(解密):"+new String(decrypted));
  32. }
  33. //加密方法
  34. public static byte[] encrypt(byte[] key,byte[] input) throws
  35. GeneralSecurityException, NoSuchPaddingException {
  36. //创建密码对象,需要传入算法/工作模式CBC/填充模式
  37. Cipher cipher=Cipher.getInstance("AES/CBC/PKCS5Padding");
  38. //根据key的字节内容,”恢复“秘钥对象
  39. SecretKey keySpec=new SecretKeySpec(key,"AES");
  40. //CBC模式需要生成一个16bytes的initialization vector
  41. SecureRandom sr=SecureRandom.getInstanceStrong();
  42. //生成16个字节的随机数
  43. byte[] iv=sr.generateSeed(16);
  44. System.out.println("IV:"+Arrays.toString(iv));
  45. //随机数封装成IvParameterSpec参数
  46. IvParameterSpec ivps=new IvParameterSpec(iv);
  47. //初始化秘钥:设置加密模式ENCRYPT_MODE、秘钥、IV
  48. cipher.init(Cipher.ENCRYPT_MODE, keySpec,ivps);
  49. //根据原始内容(字节),进行加密
  50. byte[] data=cipher.doFinal(input);
  51. //拼接,IV不需要保密
  52. return jion(iv,data);
  53. }
  54. //解密方法
  55. public static byte[] decrypt(byte[] key,byte[] input) throws
  56. BadPaddingException, NoSuchAlgorithmException, GeneralSecurityException {
  57. //分割数组
  58. byte[] iv=new byte[16];
  59. byte[] data=new byte[input.length-16];
  60. System.arraycopy(input, 0, iv, 0, 16); //IV
  61. System.arraycopy(input, 16, data, 0, data.length); //密文
  62. System.out.println("IV:"+Arrays.toString(iv));
  63. //创建密码对象,需要传入算法/工作模式/填充模式
  64. Cipher cipher=Cipher.getInstance("AES/CBC/PKCS5Padding");
  65. //根据key的字节内容,”恢复“秘钥对象
  66. SecretKeySpec keySpec=new SecretKeySpec(key, "AES");
  67. IvParameterSpec ivps=new IvParameterSpec(iv); //恢复IV
  68. //初始化秘钥:设置解密模式ENCRYPT_MODE、秘钥、IV
  69. cipher.init(Cipher.DECRYPT_MODE, keySpec,ivps);
  70. //根据原始内容(字节),进行解密
  71. return cipher.doFinal(data);
  72. }
  73. //拼接方法
  74. public static byte[] jion(byte[] b1,byte[] b2) {
  75. byte[] b=new byte[b1.length+b2.length];
  76. System.arraycopy(b1, 0, b, 0, b1.length);
  77. System.arraycopy(b2, 0, b, b1.length, b2.length);
  78. return b;
  79. }
  80. }

运行结果如下:

 在CBC模式下,需要一个随机生成的16字节IV参数,观察输出,可以发现每次生成的IV不同,密文也不同。

非对称加密算法

非对称加密:加密和解密使用的不是相同的密钥,只有同一个公钥-私钥对才能正常加解密。

非对称加密的典型算法就是RSA算法,它是由Ron Rivest,Adi Shamir,Leonard Adleman这三个人一起发明的,所以用他们三个人的姓氏首字母缩写表示。

非对称加密的优点:对称加密需要协商密钥,而非对称加密可以安全地公开各自的公钥,在N个人之间通信的时候:使用非对称加密只需要N个密钥对,每个人只管理自己的密钥对。而使用对称加密需要则需要N*(N-1)/2个密钥,因此每个人需要管理N-1个密钥,密钥管理难度大,而且非常容易泄漏。

非对称加密的缺点:运算速度非常慢,比对称加密要慢很多。

RSA算法的代码实现如下:

  1. package com.hpc.wyj05;
  2. import java.math.BigInteger;
  3. import java.security.GeneralSecurityException;
  4. import java.security.KeyPair;
  5. import java.security.KeyPairGenerator;
  6. import java.security.PrivateKey;
  7. import java.security.PublicKey;
  8. import javax.crypto.Cipher;
  9. public class RSA01 {
  10. public static void main(String[] args) throws Exception {
  11. // 明文:
  12. byte[] plain = "Hello, encrypt use RSA".getBytes("UTF-8");
  13. // 创建公钥/私钥对:
  14. Human alice = new Human("Alice");
  15. // 用Alice的公钥加密:
  16. // 获取Alice的公钥,并输出
  17. byte[] pk = alice.getPublicKey();
  18. System.out.println(String.format("public key(公钥): %x", new BigInteger(1, pk)));
  19. // 使用公钥加密
  20. byte[] encrypted = alice.encrypt(plain);
  21. System.out.println(String.format(
  22. "encrypted(加密): %x", new BigInteger(1, encrypted)));
  23. // 用Alice的私钥解密:
  24. // 获取Alice的私钥,并输出
  25. byte[] sk = alice.getPrivateKey();
  26. System.out.println(String.format(
  27. "private key(秘钥): %x", new BigInteger(1, sk)));
  28. // 使用私钥解密
  29. byte[] decrypted = alice.decrypt(encrypted);
  30. System.out.println("decrypted(解密)"+new String(decrypted, "UTF-8"));
  31. }
  32. }
  33. // 用户类
  34. class Human {
  35. // 姓名
  36. String name;
  37. // 私钥:
  38. PrivateKey sk;
  39. // 公钥:
  40. PublicKey pk;
  41. // 构造方法
  42. public Human(String name) throws GeneralSecurityException {
  43. // 初始化姓名
  44. this.name = name;
  45. // 生成公钥/私钥对:
  46. KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
  47. kpGen.initialize(1024);
  48. KeyPair kp = kpGen.generateKeyPair();
  49. this.sk = kp.getPrivate();
  50. this.pk = kp.getPublic();
  51. }
  52. // 把私钥导出为字节
  53. public byte[] getPrivateKey() {
  54. return this.sk.getEncoded();
  55. }
  56. // 把公钥导出为字节
  57. public byte[] getPublicKey() {
  58. return this.pk.getEncoded();
  59. }
  60. // 用公钥加密:
  61. public byte[] encrypt(byte[] message) throws GeneralSecurityException {
  62. Cipher cipher = Cipher.getInstance("RSA");
  63. cipher.init(Cipher.ENCRYPT_MODE, this.pk); // 使用公钥进行初始化
  64. return cipher.doFinal(message);
  65. }
  66. // 用私钥解密:
  67. public byte[] decrypt(byte[] input) throws GeneralSecurityException {
  68. Cipher cipher = Cipher.getInstance("RSA");
  69. cipher.init(Cipher.DECRYPT_MODE, this.sk); // 使用私钥进行初始化
  70. return cipher.doFinal(input);
  71. }
  72. }

运行结果如下:

 总的来说对称加密算法使用同一个密钥进行加密和解密,非对称加密算法就是加密(公钥)和解密(私钥)使用的不是相同的密钥,只有同一个公钥-私钥对才能正常加解密。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小桥流水78/article/detail/775714
推荐阅读
相关标签
  

闽ICP备14008679号