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[]数组的一个转化,没有实际差别!

 

posted @ 2023-01-25 12:45  港城大白鲨  阅读(270)  评论(0编辑  收藏  举报