目录
0x01 程序基本要求
0x02 实现代码
0x03 效果展示
0x04 总结
SHA256.java
package com;
import java.nio.ByteBuffer;
public class SHA256
{
//SHA-256计算使用的常量数组
private static final int[] K = { 0x428a2f98, 0x71374491, 0xb5c0fbcf,
0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74,
0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc,
0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85,
0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb,
0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70,
0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3,
0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f,
0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
0xc67178f2 };
// SHA-256 算法初始化向量,散列计算中使用的中间状态值。
private static final int[] H0 = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372,
0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 };
// 数组 W、H 和 TEMP 用于 SHA-256 散列计算过程中存储中间计算结果。
private static final int[] W = new int[64];
private static final int[] H = new int[8];
private static final int[] TEMP = new int[8];
//计算SHA-256哈希值
public static byte[] hash(byte[] message)
{
// 将初始哈希值 H0 复制到哈希数组 H 中,作为哈希的起始值。
System.arraycopy(H0, 0, H, 0, H0.length);
// 在输入消息前先进行填充(pad),使得消息长度满足对应规则(512 位)。然后将填充后的消息转化为整型数组。
int[] words = toIntArray(pad(message));
// 列举所有的块(每个块包含16个字母)
for (int i = 0, n = words.length / 16; i < n; ++i) {
// 前 16 个字(即 512 位二进制数的前 16 个 32 位整数)与 W 数组对应。
System.arraycopy(words, i * 16, W, 0, 16);
for (int t = 16; t < W.length; ++t) {
W[t] = smallSig1(W[t - 2]) + W[t - 7] + smallSig0(W[t - 15])
+ W[t - 16];
}
// 让 TEMP = H
System.arraycopy(H, 0, TEMP, 0, H.length);
// 对 TEMP 数组进行修改,计算出新的 TEMP 数组。
for (int t = 0; t < W.length; ++t) {
int t1 = TEMP[7] + bigSig1(TEMP[4])
+ ch(TEMP[4], TEMP[5], TEMP[6]) + K[t] + W[t];
int t2 = bigSig0(TEMP[0]) + maj(TEMP[0], TEMP[1], TEMP[2]);
System.arraycopy(TEMP, 0, TEMP, 1, TEMP.length - 1);
TEMP[4] += t1;
TEMP[0] = t1 + t2;
}
// 将temp中的值赋予给h
for (int t = 0; t < H.length; ++t) {
H[t] += TEMP[t];
}
}
return toByteArray(H);
}
//对数组 message 进行填充
public static byte[] pad(byte[] message)
{
//blockBits 表示 Hash 函数的分块大小
final int blockBits = 512;
//blockBytes 是 blockBits 以字节为单位的表示
final int blockBytes = blockBits / 8;
// 扩展后的消息长度:原始消息长度 + 1位填充 + 8字节长度 + 填充字节数(padBytes)
int newMessageLength = message.length + 1 + 8;
//计算需要填充的字节数,使新的消息长度是块(blockBytes)的倍数。
int padBytes = blockBytes - (newMessageLength % blockBytes);
newMessageLength += padBytes;
// 将消息复制到扩展数组
final byte[] paddedMessage = new byte[newMessageLength];
System.arraycopy(message, 0, paddedMessage, 0, message.length);
// 在扩展数组的末尾添加 1 位表示填充位0b10000000 。
paddedMessage[message.length] = (byte) 0b10000000;
// 获取填充后消息的长度
int lenPos = message.length + 1 + padBytes;
//将消息长度存储在对应的paddedMessage数组中,message.length * 8表示消息长度,按bit计算所以乘8。
ByteBuffer.wrap(paddedMessage, lenPos, 8).putLong(message.length * 8);
//从 lenPos 位置开始的连续 8 个字节包装成一个 ByteBuffer 对象
return paddedMessage;
}
//将字节数组转换为整数数组,因为SHA-256算法要求输入为32位二进制数字
public static int[] toIntArray(byte[] bytes)
{
//判断输入的字节数组的长度是否是整数字节的倍数 ,如果不是,则抛出 IllegalArgumentException 异常,int 类型使用的字节数是 4。
if (bytes.length % Integer.BYTES != 0) {
throw new IllegalArgumentException("byte array length");
}
//创建 ByteBuffer 对象,使用 static 方法 wrap 来包装传入的字节数组。
ByteBuffer buf = ByteBuffer.wrap(bytes);
int[] result = new int[bytes.length / Integer.BYTES];
//将 ByteBuffer 中的整数读取到结果数组中。
for (int i = 0; i < result.length; ++i) {
result[i] = buf.getInt();
}
return result;
}
//将整数型数组转换成字节数组
public static byte[] toByteArray(int[] ints)
{
//创建了一个 ByteBuffer 对象 buf,该对象的容量为整数型数组中元素个数乘以整数型变量的大小
ByteBuffer buf = ByteBuffer.allocate(ints.length * Integer.BYTES);
for (int i = 0; i < ints.length; ++i) {
//使用 buf.putInt(ints[i]) 将每个元素写入 ByteBuffer 对象中。
buf.putInt(ints[i]);
}
return buf.array();
}
//处理消息块 M
private static int ch(int x, int y, int z)
{
return (x & y) | ((~x) & z);
}
private static int maj(int x, int y, int z)
{
return (x & y) | (x & z) | (y & z);
}
//计算x的哈希值
private static int bigSig0(int x)
{
//Integer.rotateRight(x, 2):这个函数用于将输入值 x 向右旋转 2 位
return Integer.rotateRight(x, 2) ^ Integer.rotateRight(x, 13)
^ Integer.rotateRight(x, 22);
}
private static int bigSig1(int x)
{
return Integer.rotateRight(x, 6) ^ Integer.rotateRight(x, 11)
^ Integer.rotateRight(x, 25);
}
private static int smallSig0(int x)
{
//(x >>> 3):将 x 向右移动 3 位,即将 x 的二进制位右移 3 位,然后用移位后的结果替换原来的 x,返回结果。
return Integer.rotateRight(x, 7) ^ Integer.rotateRight(x, 18)
^ (x >>> 3);
}
private static int smallSig1(int x)
{
return Integer.rotateRight(x, 17) ^ Integer.rotateRight(x, 19)
^ (x >>> 10);
}
}
Client.java
package com;
import javax.xml.bind.DatatypeConverter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.security.NoSuchAlgorithmException;
import javax.swing.JOptionPane;
public class Client {
private static final String SERVER_IP = "localhost";
private static final int SERVER_PORT = 6666;
public static void main(String[] args) {
try {
// 弹出输入对话框,获取用户需要加密的字符串
String input = JOptionPane.showInputDialog("请输入需 hash 加密的数据:");
FileOutputStream fos=new FileOutputStream("D:xxx\\com\\example.txt");
//getBytes() 方法将 input 字符串转换为字节数组写入到文件
fos.write(input.getBytes());
fos.close();
// 计算文件的 SHA-256 Hash 值,并将结果存储到字节数组中
byte[] bytes = input.getBytes();
byte[] hash = SHA256.hash(bytes);
System.out.println("SHA-256 哈希值为:" + hash);
// 将字节数组中的哈希值转换为十六进制字符串
String fileHashHex = DatatypeConverter.printHexBinary(hash);
System.out.println("十六进制 hash 值: " + fileHashHex);
// 创建一个 Socket 实例,连接服务器并发送文件名和哈希值
Socket socket = new Socket(SERVER_IP, SERVER_PORT);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeUTF("D:xxx\\com\\example.txt");
objectOutputStream.writeObject(fileHashHex);
objectOutputStream.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Server.java
package com;
import com.SHA256;
import javax.xml.bind.DatatypeConverter;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
public class Server {
private static final int PORT = 6666;
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(PORT);
//创建一个服务器端套接字,监听指定的端口号 PORT,以等待客户端的连接请求。
System.out.println("Server started.");
System.out.println("Waiting for a client ...");
while (true) {
//创建一个侦听客户端连接请求的 ServerSocket 对象,并等待客户端连接。
Socket socket = serverSocket.accept();
//accept()方法是 ServerSocket 类的一个方法,其作用是等待一个客户端的连接请求,并返回一个与该客户端连接的套接字,即 Socket 对象。
System.out.println("Client connected");
//从客户端读取输入
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
//读取指定文件路径和名称
String fileName = objectInputStream.readUTF();
//读取客户端传过来的hash值
String hashBytes= (String) objectInputStream.readObject();
System.out.println("File hash: " + fileName);
File file = new File(fileName);
FileInputStream inputStream = new FileInputStream(file);
//inputStream.available() 方法返回字节流中可供读取的字节数。
int length = inputStream.available();
//inputStream.read(bytes) 从输入流中读取全部可用的字节,将它们存储在 bytes 数组中。
byte bytes[] = new byte[length];
inputStream.read(bytes);
inputStream.close();
//将 bytes 数组中的字节转换成字符串,使用 StandardCharsets.UTF_8 将字节数组解码为字符串。
String str =new String(bytes, StandardCharsets.UTF_8);
byte[] hash = SHA256.hash(bytes);
String fileHashHex = DatatypeConverter.printHexBinary(hash);
System.out.println("客户端输入字符串为:"+str);
System.out.println("客户端接收到的哈希值为:"+hashBytes);
System.out.println("服务端计算得到的哈希值为:"+fileHashHex);
// 比较从客户端接收到的哈希值与计算出的哈希值
if (MessageDigest.isEqual(hashBytes.getBytes(), fileHashHex.getBytes())) {
System.out.println("文件完整性验证通过");
} else {
System.out.println("文件完整性认证不通过");
}
objectInputStream.close();
socket.close();
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
首先运行Server.java
在运行 Client.java
在输入框中输入字符串
客户端显示如下
服务端显示如下
鉴于本人实力有限,有更好的实现方法可以一起讨论改正。