SHA1算法以及Java代码实现(不使用MessageDigest类)

来源:互联网 发布:网络弊大于利三辩提问 编辑:程序博客网 时间:2024/06/06 17:35

SHA1是常见的哈希函数之一,关于哈希函数的内容,可以看一看这篇文章

http://blog.csdn.net/shaoqunliu/article/details/52078869


最近课程上了点密码学,稍微了解了下哈希函数,并且试着用Java实现了一下sha1算法。为了理解其原理,我没有使用Java带的MessageDigest类。


SHA1算法过程


        SHA1算法大概有以下几个流程

                1、比特位的填充,使得信息长度为512位的倍数,其中要预留64位以记录原文的长度

                2、对信息分组

                3、对于每个分组,进行80轮的计算,得到160位的摘要值

                4、将每个分组计算的结果“相加”,得到最终的160位摘要值,就是sha1值。

        详细介绍下每个步骤的过程

        各类形式的数据都可以计算出一个SHA1值,因为任意长度二进制数据经过SHA1计算,其结果都为定长(160位)的二进制消息摘要值,当然,很多网站计算出的SHA1摘要是一段40位0~9,A~F的字符串,这是用十六进制来表示SHA1值的一种形式。1个十六进制位可以表示4个二进制位,因此正好160/4=40位。


比特位填充:

        首先参与计算的信息是以二进制表示的,如果是文件通常是计算字节流,字符串则取每个字符ASCII码值组成二进制序列。以字符串为例子。例如"abc",对应的ASCII码序列{0x61, 0x62, 0x63},另外为表示方便,这里也使用16进制表示。"abc"就要表示为

61 62 63。

        接下来是比特填充,为了方便分组,要将二进制序列填充至长度 Len mod 512 = 448,因为最后要预留64来记录数据源的长度(512-448=64)。"abc"的长度为24位,一个字母占1字节(8位),因此"abc"长度为24位,因此还需要填充424位,其中填充的424位中,第一位填充1,其余423位均为0,填充十六进制结果如下

        61626380 00000000 00000000 00000000

        00000000 00000000 00000000 00000000

        00000000 00000000 00000000 00000000

        00000000 00000000 

        上面十六进制 80 就是 1000 0000,即在填充 1000..... 00 这样的二进制序列。

        最后将原始信息的长度填充至最后64个二进制位,"abc"长度为24,故填充 00000000 00000018,结果如下

        61626380 00000000 00000000 00000000

        00000000 00000000 00000000 00000000

        00000000 00000000 00000000 00000000

        00000000 00000000 00000000 00000018


        注意:10...00 这个填充步骤是必须进行的。考虑到有原文长度Len恰好满足 Len mod 512 = 448,那么就要在449位开始填充1,450~960位填充0,预留961~1024这64位来记录原文长度。


消息分组:

        经过上一步骤的处理,会得到一个长度为512整数倍位的二进制序列M,将M分组,每组512个二进制位,每个分组记为Mi。"abc"经过填充长度为512位,因此只需要计算一个分组。


计算分组的摘要值:

        每个分组进行80轮的计算,在计算每个分组Mi前,需要准备几个初始值与常数。

        其一是Kt,在不同的轮中的值不一样,t表示轮数。

        Kt = 0x5A827999 , 0≤t<20

        Kt = 0x6ED9EBA1 , 20t<40

        Kt = 0x8F1BBCDC , 40t<60

        Kt = 0xCA62C1D6 , 60≤t<80


        其次是H,包含五个二进制序列,每个序列有32位,共160位。

        H0 = 0x67452301

        H1 = 0xEFCDAB89

        H2 = 0x98BADCFE

        H3 = 0x10325476

        H4 = 0xC3D2E1F0

        此外还要要准备一个w数组,长度为80,每个元素有32位,即一个长度为80的int数组。

        对于一个分组Mi,再次将其切成16小片m0, m1, m2, ... m14, m15,每小片32位。对于w数组,前16元素为Mi的分片,即

                w[0] = m0,

                w[1] = m1,

                ...

                w[i] = mi

                ...

                w[15] = m15,

        对于 i≥ 16,w[i] 的计算公式如下

        w[i] = S1( w[i-16] XOR w[i-14]XOR w[i-8]XORw[i-3])

        其中XOR表示按位异或,Sn 表示循环左移n位,那么S1就表示循环左移1位。


        另外准备5个32位的分片a, b, c, d, e,赋予初始值

        a=H0, b=H1, c=H2, d=H3, e=H4

        

        对a, b, c, d, e进行80轮计算,每轮的计算如下,用t表示轮数。

                temp=S5(a) + Ft(b, c, d) + e + w[t] + Kt

                e = d
                d = c
                c = S30(b)
                b = a
                a = temp

        其中temp只是用来暂存计算结果的中间变量,Sn同表示循环左移n位,w数组上面已经定义,Kt常数上面也提过。

        Ft也是一个计算的函数,规定 Ft(b, c, d) 如下

        0≤t<20, Ft = b AND c OR (NOT b) AND d

        20≤t<40, Ft = b AND c OR b AND d OR c AND d

        40≤t<60, Ft = b XOR c XOR d

        60≤t<80, Ft = b AND c OR b AND d OR c AND d

        其中,AND, OR, XOR, NOT 对应按位与,按位或,按位异或,按位非


注意:Java实现循环左移函数S的写法如下,注意>>>与>>的区别,两者均为右移,>>高位补符号位,>>>高位补0

int s(int lmov, int num) {        return num << lmov | num >>> (32 - lmov);}


得到摘要值:

        对于每个Mi计算出的摘要值hi,只需要将它们全部“相加“,最终结果就是SHA1值。

        “相加”规则如下

        1、满足普通的加法规则;

        2、在加的过程中通常会产生溢出,如 0xFFFF FFFF + 0x0000 0002,溢出的位直接舍弃掉,左式子结果就为0x0000 0001


Java代码实现

package Cryptology;/** * Created by YotWei on 2017/10/28. * sha1 */public class SHA1Demo {    public static void main(String[] args) throws Exception {        String s = "abc";        System.out.println(s);        System.out.println("sha1: " + SHA1.generate(s.getBytes()));//可以使用MessageDigest类来检验自己的算法是否正确//        MessageDigest sha1 = MessageDigest.getInstance("sha1");//        sha1.update(s.getBytes());//        BigInteger b = new BigInteger(1, sha1.digest());//        System.out.println("sha1 (Generated by Java built-in algorithm): " + b.toString(16));    }}class SHA1 {    static String generate(byte[] dataBytes) throws Exception {        byte[] fillBytes = new byte[64 * ((dataBytes.length + 8) / 64 + 1)];        int i;        for (i = 0; i < dataBytes.length; i++) {            fillBytes[i] = dataBytes[i];        }        //fill 100000.....00        fillBytes[i] = (byte) 0x80;        //fill length        long len = dataBytes.length * 8L;        for (int j = fillBytes.length - 8, k = 0; j < fillBytes.length; j++, k++) {            fillBytes[j] = (byte) (len >> ((7 - k) * 8));        }        //cast bytes to ints        int[] bytes2Ints = byteArrToIntArr(fillBytes);        int[] k = {0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6};        int[] h = {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0};        for (int j = 0; j < bytes2Ints.length; j += 16) {            int[] w = new int[80];            System.arraycopy(bytes2Ints, 0, w, 0, 16);            int a = h[0], b = h[1], c = h[2], d = h[3], e = h[4];            for (int t = 0; t < 80; t++) {                if (t >= 16) {                    w[t] = s(1, w[t - 16] ^ w[t - 14] ^ w[t - 8] ^ w[t - 3]);                }                int temp = s(5, a) + f(t, b, c, d) + e + w[t] + k[t / 20];                e = d;                d = c;                c = s(30, b);                b = a;                a = temp;            }            h[0] += a;            h[1] += b;            h[2] += c;            h[3] += d;            h[4] += e;        }        return String.format("%08x%08x%08x%08x%08x", h[0], h[1], h[2], h[3], h[4]);    }    private static int f(int t, int b, int c, int d) {        switch (t / 20) {            case 0:                return (b & c) | (~b & d);            case 2:                return (b & c) | (b & d) | (c & d);            default:                return b ^ c ^ d;        }    }    private static int s(int lmov, int num) {//        return num << lmov | num >> (32 - lmov);        return num << lmov | num >>> (32 - lmov);    }    private static int[] byteArrToIntArr(byte[] bytes) throws Exception {        if (bytes.length % 4 != 0) {            throw new Exception("Parse Error");        }        int[] intArr = new int[bytes.length / 4];        for (int i = 0; i < intArr.length; i++) {            intArr[i] = bytes[i * 4 + 3] & 0x000000ff |                    bytes[i * 4 + 2] << 8 & 0x0000ff00 |                    bytes[i * 4 + 1] << 16 & 0x00ff0000 |                    bytes[i * 4] << 24 & 0xff000000;        }        return intArr;    }}


阅读全文
1 0
原创粉丝点击