使用java实现基于CS模式SHA-256算法文件完整性程序

目录

0x01 程序基本要求

0x02 实现代码

0x03 效果展示

0x04 总结


0x01 程序基本要求

软件包括客户端和服务器端,客户端实现文件 Hash 值计算,服务器端接收
的文件和 Hash 值,进行文件完整性验证。 软件具体功能如下:
1 ) 客户端文件 Hash 的计算
a )输入任意文件格式的文件
b )采用 Hash 算法计算 Hash 值,并十六进制显示 hash 值,然后把文件和
hash 值给服务器端
(2 ) 服务器端验证文件的完整性
a) 接收端客户端文件和 Hash 值,然后分离得到文件和收到 Hash 值,然后
采用跟客户端一样 Hash 函数计算文件 Hash 值,计算 Hash 值与收到 Hash
较,若一致,则文件完整性认证通过,若不一致,则文件完整性验证不通过;
其它要求:
1 )用函数实现 Hash 算法
(2 )可选择 C C++ Java Python 等任意一种编程语言实现;

0x02 实现代码

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();
        }
    }

}

0x03 效果展示

首先运行Server.java

在运行 Client.java

在输入框中输入字符串

客户端显示如下

服务端显示如下

0x04 总结

 鉴于本人实力有限,有更好的实现方法可以一起讨论改正。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值