Java 实现MD5、SHA,对称加密AES
MD5,SHA都是哈希值,并不能称为加密,因为无法解密
首先看下官方构造类说明
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/security/MessageDigest.html
MessageDigest md = MessageDigest.getInstance("SHA-256"); try { md.update(toChapter1); MessageDigest tc1 = md.clone(); byte[] toChapter1Digest = tc1.digest(); md.update(toChapter2); ...etc. } catch (CloneNotSupportedException cnse) { throw new DigestException("couldn't make digest of partial content"); }
网上看了下最大的差异就是把byte数据转换为16进制的过程
版本1:利用byte的二级制存储结构 转换16进制
补充:byte的取值范围为 [-128,127],二进制的范围为 [0-127] 00000000-01111111 [-128,-1] 10000000-11111111 数据存储均为补码,对应的二进制为8位,16进制的数为4位,所以采用了无符号右移4位以及原有的低位4位与0xf的与操作
public static String MD5(String key){ char hexDigests[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; try { byte[] in = key.getBytes(); MessageDigest messageDigest = MessageDigest.getInstance("md5"); messageDigest.update(in); // 获得密文 byte[] md = messageDigest.digest(); // 将密文转换成16进制字符串形式 int j = md.length; char[] str = new char[j*2]; int k = 0; for (int i = 0; i < j; i++) { byte b = md[i]; str[k++] = hexDigests[b >>> 4 & 0xf]; // >>> 无符号右移。这里将字节b右移4位,低位抛弃,就等于是高4位于0xf做与运算。4位最多表示15。 str[k++] = hexDigests[b & 0xf]; //用 1字节=8位,与0xf与运算,高4位必为0,就得到了低四位的数。 } return new String(str); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); throw new RuntimeException("md5加密失败",e); } }
版本2:用String.format形式转16进制,这种形式对大多数人更为友好
public static String Nd5(String key){ try { byte[] in = key.getBytes(); MessageDigest messageDigest = MessageDigest.getInstance("md5"); messageDigest.update(in); // 获得密文 byte[] md = messageDigest.digest(); StringBuilder enText = new StringBuilder(); for(byte b:md){ enText.append(String.format("%02x",b));//02x 表示不足2位前面补1个0,04x表示长度为4最多可以补3个0 } return new String(enText); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); throw new RuntimeException("md5加密失败",e); } }
同理,如果需要SHA-1,SHA-256 只需要改造参数即可 MessageDigest.getInstance("SHA-1");
另附上获取值的在计算机中存储的二进制(获取都为补码,不是原码,便于计算机计算)
System.out.println(Integer.toBinaryString(((byte)-38 & 0xFF) + 0x100).substring(1)); //11011010
System.out.println(Integer.toBinaryString((-128 & 0xFF) + 0x100).substring(1)); //10000000
对称加密AES
AES的一些说明可以参考:https://blog.csdn.net/qq_39126560/article/details/126955368
DES 全称为Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法,1977年被美国联邦政府的国家标准局确定为联邦资料处理标准(FIPS)
AES 密码学中的高级加密标准(Advanced Encryption Standard,AES),又称Rijndael加密法(是这个人的算法最终赢得了认可),是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES(Data Encryption Standard),已经被多方分析且广为全世界所使用。
为什么 DES 被废弃?
我们知道数据加密标准(Data Encryption Standard: DES)的密钥长度是56比特,因此算法的理论安全强度是2的56次方。但二十世纪中后期正是计算机飞速发展的阶段,元器件制造工艺的进步使得计算机的处理能力越来越强,DES将不能提供足够的安全性。
为什么AES算法被称为 Rijndael 算法?
1997年1月2号,美国国家标准技术研究所(National Institute of Standards and Technology: NIST)宣布希望征集高级加密标准(Advanced Encryption Standard: AES)[3],用以取代DES。AES得到了全世界很多密码工作者的响应,先后有很多人提交了自己设计的算法。最终有5个候选算法进入最后一轮:Rijndael,Serpent,Twofish,RC6和MARS,下图分别为其中的5位作者。最终经过安全性分析、软硬件性能评估等严格的步骤,Rijndael算法获胜。
为什么AES算法安全性高?
AES的区块长度固定为128位,密钥长度则可以是128 bit,192 bit 或256位 bit 。换算成字节长度,就是密码必须是 16个字节,24个字节,32个字节。AES密码的长度更长了,破解难度就增大了,所以就更安全。
用到的概念,密钥,偏移量,填充模式,加密模式
以下演示 AES CBC PKCS7(在AES加密当中严格来说是不能使用pkcs5的,因为AES的块大小是16bytes而pkcs5只能用于8bytes,通常我们在AES加密中所说的pkcs5指的就是pkcs7!)
package com.hengrui.utility; import javax.crypto.*; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.*; import java.util.Base64; public class AESUtil { //CBC模式 //加密 public static byte[] encryt(byte[] data, byte[] key, byte[] iv) throws GeneralSecurityException { //在除ECB以外的所有加密方式中,都需要用到IV对加密结果进行随机化 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv)); return cipher.doFinal(data); } //加密 public static String encrytString(byte[] data, byte[] key, byte[] iv) throws GeneralSecurityException { return Base64.getEncoder().encodeToString(encryt(data,key,iv)); } //解密 public static byte[] decrypt(byte[] data, byte[] key, byte[] iv) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv)); return cipher.doFinal(data); } }
调用的地方
这个地方 编码是 StandardCharsets.UTF_8,建议写,可以使用 System.out.println(Charset.defaultCharset());查看系统默认的编码,
这个加密最终操作的是字节,所以从字符转到字节 大家得有一定的编码约定
String key="pT2YDLak+U^J%g#4"; String iv="t_YaxP~+@@k%&~8o"; String result = AESUtil.encrytString("Hellworld你好世界".getBytes(StandardCharsets.UTF_8),key.getBytes(StandardCharsets.UTF_8),iv.getBytes(StandardCharsets.UTF_8)); System.out.println(result); byte[] resultde = AESUtil.decrypt(Base64.getDecoder().decode(result),key.getBytes(StandardCharsets.UTF_8),iv.getBytes(StandardCharsets.UTF_8)); System.out.println(new String(resultde)); new String(resultde,StandardCharsets.UTF_8);
或者自行生成一个key,都是byte数组
KeyGenerator kgen = KeyGenerator.getInstance("AES");// 创建AES的Key生产者 kgen.init(128, new SecureRandom());// 利用用户密码作为随机数初始化出 SecretKey secretKey = kgen.generateKey(); byte[] enCodeFormat = secretKey.getEncoded();
最后 探索一点
有没有一种疑问,为什么MD5,SHA1这类哈希最后都是用16进制,AES,RSA这类加密却采用base64?
这个网上查询了一些解释,结合自身理解,我理解一个是一个约定俗成的原因(次因),另一个就是MD5和SHA1都是固定长度的,为了可读性使用了16进制。
而AES,RSA这类加密的长度不定,且base64相比16进制可以稍微短一些,所以这么设计了。
但一点是肯定的,不论base64,16进制,这些都是可以互转的,都是byte[]数组的一个转化,没有实际差别!