1、首先说明SHA-1算法虽然可以实现加密,但是我们也只是按照官方提供的文档来实现的,SHA-1算法内部具体的一些算法推论(比如一些计算是怎么的出来的,这些官方并没有给出),所以我们也只是按照套路实现自己的一套SHA-1加密算法,所以码友们不要纠结一些计算是如何得出来的,因为没几个人知道,所以这就保证了这种加密方式被破解出来的可能性非常低,如果有了解的码友们应该知道这些吧!
2、下面开始简单介绍SHA-1加密的实现过程:
1)、比如:我们这里以 "abc" 字符串来说明问题,,因为‘a’=97,'b'=98,'c'=99,所以将其转换为位串后为:
01100001 01100010 01100011
2)、对转换后的位字符串进行补位操作:
SHA-1算法标准规定,必须对消息摘要进行补位操作,即将输入的数据进行填充,使得数据长度对512求余的结果为448 ,填充比特位的最高位补一个1,其余的位补0,如果在补位之前已经满足对512取模余数为448,也要进行补位,在其后补一位1即可。 总之,补位是至少补一位,最多补512位,我们依然以“abc”为例,其补位过程如下:
初始的信息摘要:01100001 01100010 01100011
第一步补位: 01100001 01100010 01100011 1
..... ......
补位最后一位: 01100001 01100010 01100011 10.......0(后面补了423个0)
而后我们将补位操作后的信息摘要转换为十六进制,如下所示:
61626380 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000
3)、附加长度值:
在信息摘要后面附加64bit的信息,用来表示原始信息摘要的长度,在这步操作之后,信息报文便是512bit的倍数。通常来说用一个64位的数据表示原始消息的长度,如果消息长度不大于2^64,那么前32bit就为0,在进行附加长度值操作后,其“abc”数据报文即变成如下形式:
61626380 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000018
因为“abc”占3个字节,即24位 ,换算为十六进制即为0x18
4)、初始化缓存:
一个160位MD缓冲区用以保存中间和最终散列函数的结果。它可以表示为5个32位的寄存器(H0,H1,H2,H3,H4)。初始化为:
H0 = 0x67452301
H1 = 0xEFCDAB89
H2 = 0x98BADCFE
H3 = 0x10325476
H4 = 0xC3D2E1F0
大端存储模式:高位数据放在低地址,低位数据放在高地址
5)计算消息摘要:
S函数
循环左移操作符Sn(x),x是一个字,也就是32bit大小的变量,n是一个整数且0<=n<=32。Sn(X) = (X<<n)OR(X>>32-n)
常量字k(0)、k(1)、...k(79)
Kt = 0x5A827999 (0 <= t <= 19)
Kt = 0x6ED9EBA1 (20 <= t <= 39)
Kt = 0x8F1BBCDC (40 <= t <= 59)
Kt = 0xCA62C1D6 (60 <= t <= 79)
非线性函数
所要用到的一系列函数
Ft(b,c,d) ((b&c)|((~b)&d)) (0 <= t <= 19)
Ft(b,c,d) (b^c^d) (20 <= t <= 39)
Ft(b,c,d) ((b&c)|(b&d)|(c&d)) (40 <= t <= 59)
Ft(b,c,d) (b^c^d) (60 <= t <= 79)
根据结果无法反推
开始计算摘要
计算需要一个缓冲区,由5个32位的字组成,还需要一个80个32位字的缓冲区。第一个5个字的缓冲区被标识为A,B,C,D,E。80个字的缓冲区被标识为W0, W1,..., W79
另外还需要一个一个字的TEMP缓冲区。
为了产生消息摘要,在补好位的数据中前16个字的数据块M1, M2,..., Mn
会依次进行处理,处理每个数据块Mi 包含80个步骤。
现在开始处理M1, M2, ... , Mn。为了处理 Mi,需要进行下面的步骤
(1). 将 Mi 分成 16 个字 W0, W1, ... , W15, W0 是最左边的字
(2). 对于 t = 16 到 79 令 Wt = S1(Wt-3 XOR Wt-8 XOR Wt- 14 XOR Wt-16).
(3). 令 A = H0, B = H1, C = H2, D = H3, E = H4.
(4) 对于 t = 0 到 79,执行下面的循环
TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt;
E = D; D = C; C = S30(B); B = A; A = TEMP;
(5). 令 H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E.
在处理完所有的 Mn, 后,消息摘要是一个160位的字符串,以下面的顺序标识
H0 H1 H2 H3 H4.
到此,实现过程完毕,然后我们可以打印输入你要的结果了,
6)、代码实现如下:
import org.junit.Test;
public class SHA1算法 {
//准备工作
public static final int[] abcde = {
0x67452301,
0xEFCDAB89,
0x98BADCFE,
0x10325476,
0xC3D2E1F0
};
//摘要数据存储用的数组 (存放密文的) 20个字节 * 8 = 160
public static int[] h = new int[5];
//计算过程中需要用到的临时数据存储数组
public static int[] m = new int[80];
//定义辅助方法
//将字节转换为十六进制字节字符串
public static String byteTOHexString(byte b){
char[] digit= {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
char[] ob = new char[2];
ob[0] = digit[(b>>>4)&0x0F];
ob[1] = digit[b&0x0f];
String s = new String(ob);
return s;
}
//将字节数组转换为十六进制字符串
public static String byteArrayToHexString(byte[] byteArray){
String strDigest = "";
for (int i = 0; i < byteArray.length; i++) {
strDigest += byteTOHexString(byteArray[i]);
}
return strDigest;
}
//4字节数组转换成int i个byte合成byteData[] 中
public static int byteArrayToInt(byte[] byteData,int i){
return ((byteData[i]&0xff) << 24) | ((byteData[i+1]&0xff)<<16)|((byteData[i+2]&0xff) << 8) | (byteData[i+3] & 0xff);
}
//整数转成4字节数组,int 分解到byte数组中
public static void inToByteArray(int intValue,byte[] byteData,int i){
byteData[i] = (byte)((intValue >>> 24) & 0xff);
byteData[i+1] = (byte)((intValue >>> 16) & 0xff);
byteData[i+2] = (byte)((intValue >>> 8 ) & 0xff);
byteData[i+3] = (byte)(intValue & 0xff);
}
/**
*/
public static int f1(int x,int y,int z){
return (x&y)|(~x&z);
}
public static int f2(int x,int y,int z){
return x^y^z;
}
public static int f3(int x,int y,int z){
return (x&y) | (x&z) | (y&z);
}
public static int f4(int x,int y,int z){
return x^y^z;
}
//开始逻辑
//进行对原数据的补位
public static byte[] byteArrayFormatData(byte[] byteData){
//补0的个数
int fill = 0;
//补位后的总位数,64的倍数
int size = 0;
//原数据的长度
int srcLength = byteData.length;
//对64求余 n%512 56数据 8长度
int m = srcLength%64;
if (m<56){
fill = 55-m;
size = srcLength-m+64;//数据只有一块
}else if (m==56){
fill = 63;
size = srcLength + 8 + 64;
}else {
fill = 63-m+56; // 58 60+56 116-64=52 55-52=3
size=(srcLength+64) - m + 64;
}
//补位后生成的新的数组的内容
byte[] newnyte = new byte[size];
System.arraycopy(byteData,0,newnyte,0,srcLength);
//补1
int startLocation = srcLength;
newnyte[startLocation++] = (byte)0x80;
//补0
for (int i = 0; i < fill; i++) {
newnyte[startLocation++] = (byte)0x00;
}
//处理长度的位置 字节 *8=?位 512-468=64位 ,用来存放长度
long n = (long)srcLength*8;
byte h8 = (byte)(n&0xff);
byte h7 = (byte)((n>>8)&0xff);
byte h6 = (byte)((n>>16)&0xff);
byte h5 = (byte)((n>>24)&0xff);
byte h4 = (byte)((n>>32)&0xff);
byte h3 = (byte)((n>>40)&0xff);
byte h2 = (byte)((n>>48)&0xff);
byte h1 = (byte)(n>>56);
newnyte[startLocation++] = h1;
newnyte[startLocation++] = h2;
newnyte[startLocation++] = h3;
newnyte[startLocation++] = h4;
newnyte[startLocation++] = h5;
newnyte[startLocation++] = h6;
newnyte[startLocation++] = h7;
newnyte[startLocation++] = h8;
return newnyte;
}
//开始计算密文 算摘要
public static int process_input_bytes(byte[] byteData){
System.arraycopy(abcde,0,h,0,abcde.length);
//格式化数据
byte[] newByte = byteArrayFormatData(byteData);
//计算有多少个大块
int mCount = newByte.length/64;
//循环计算每一块的内容
for (int pos = 0; pos < mCount; pos++) {
//对每一块进行加密计算
//(1)将 Mi 分成 16 个字节 W0,W1。。。。w15 , W0 是最右边的字
for (int i = 0; i < 16; i++) {
m[i] = byteArrayToInt(newByte,(pos*6) + (i*4));
}
//计算
encrpy();
}
return 20;
}
private static void encrpy() {
// (2) c\对于 t= 16 到79 令 Wt = S1(Wt-3 XOR Wt-8 XOR Wt-14 XOR-16)
for (int t = 16; t <= 79; t++) {
m[t] = s(m[t-3]^m[t-8]^m[t-14]^m[t-16],1);
}
//(3) 令 A = H0, b = H1 , c = H2 , D = H3 , E = H4
int[] tempabcde = new int[5];
for (int i = 0; i < tempabcde.length; i++) {
tempabcde[i] = h[i];
}
// 4 对于 t = 0 到 79 ,执行下面的循环
// ....
for (int i = 0; i <= 19; i++) {
int temp = s(tempabcde[0],5)
+ f1(tempabcde[1],tempabcde[2],tempabcde[3])
+ tempabcde[4]
+ m[i] + 0x5A827999;
tempabcde[4] = tempabcde[3];
tempabcde[3] = tempabcde[2];
tempabcde[2] = s(tempabcde[1],30);
tempabcde[1] = tempabcde[0];
tempabcde[0] = temp;
}
for (int i = 20; i <= 39; i++) {
int temp = s(tempabcde[0],5)
+ f2(tempabcde[1],tempabcde[2],tempabcde[3])
+ tempabcde[4]
+ m[i] + 0x6ED9EBA1;
tempabcde[4] = tempabcde[3];
tempabcde[3] = tempabcde[2];
tempabcde[2] = s(tempabcde[1],30);
tempabcde[1] = tempabcde[0];
tempabcde[0] = temp;
}
for (int i = 40; i <= 59; i++) {
int temp = s(tempabcde[0],5)
+ f3(tempabcde[1],tempabcde[2],tempabcde[3])
+ tempabcde[4]
+ m[i] + 0x8F1BBCDC;
tempabcde[4] = tempabcde[3];
tempabcde[3] = tempabcde[2];
tempabcde[2] = s(tempabcde[1],30);
tempabcde[1] = tempabcde[0];
tempabcde[0] = temp;
}
for (int i = 60; i <= 79; i++) {
int temp = s(tempabcde[0],5)
+ f4(tempabcde[1],tempabcde[2],tempabcde[3])
+ tempabcde[4]
+ m[i] + 0xCA62C1D6;
tempabcde[4] = tempabcde[3];
tempabcde[3] = tempabcde[2];
tempabcde[2] = s(tempabcde[1],30);
tempabcde[1] = tempabcde[0];
tempabcde[0] = temp;
}
for (int i = 0; i < tempabcde.length; i++) {
h[i] = h[i] + tempabcde[i];
}
//完成了一次操作
//消除之前的操作,开始下一块的计算
for (int i = 0; i < m.length; i++) {
m[i] = 0;
}
}
//n是一个整数且 0<=n<=32 Sn(X) = (X<<n)OR(X>>32-n)
public static int s(int x,int i){
return (x<<i) | (x>>>(32-i));
}
//把byte数组转16进制显示出来
public static byte[] getDigetOfBytes(byte[] byteData){
process_input_bytes(byteData);
byte[] digest = new byte[20];
for (int i = 0; i < h.length; i++) {
inToByteArray(h[i],digest,i*4);
}
return digest;
}
public static String getDigestOfString(byte[] byteData){
return byteArrayToHexString(getDigetOfBytes(byteData));
}
//这里是测试方法,我们输入一个字符串然后看加密后的结果
@Test
public void test(){
String param = "wwy";
System.out.println("加密前: " + param);
String digest = getDigestOfString(param.getBytes());
System.out.print("加密后:" + digest);
}
/*
*加密前: wwy
加密后:e98e6ed78840d95ea7f3441be4235c2725e42847
*/
注意:代码最开始的abcde数组里面的内容是这里是使用规定、统一的初始化数据,直接使用以上代码实现的加密,和网上在线使用SHA-1加密得到的结果是一样的,但是如果我们自己使用,为了防止别人破解,我们可以适当修改开始的初始工作,也就是abcde数组的初始值,这里的数字任意修改一个字母,加密后得到的加密结果都将是完全不同的,大家可以测试一下。所以公司要使用的话,建议将初始化数组数据稍微修改一点点,只要你前后端这种规定初始数组数据一样的即可。
注意:
只要是哈希函数,就存在碰撞
所谓碰撞的意思是,有两个不同的数据,他们的哈希值相同(SHA1值相同)