加密算法(SHA-1篇)

概述

SHA-1是一种数据加密算法,该算法的思想是接收一段明文,然后以一种不可逆的方式将它转换成一段(通常更小)密文,也可以简单的理解为取一串输入码(称为预映射或信息),并把它们转化为长度较短、位数固定的输出序列即散列值(也称为信息摘要或信息认证代码)的过程。

可以生成一个被称为信息摘要的160位(20字节)散列值,散列值通常的呈现形式位40个十六进制数。

该算法输入报文的长度不限,产生的输出是一个160位的报文摘要。输入是按512 位的分组进行处理的。SHA-1是不可逆的、防冲突,并具有良好的雪崩效应。

数字签名的原理是将要传送的明文通过一种函数运算(Hash)转换成报文摘要(不同的明文对应不同的报文摘要),报文摘要加密后与明文一起传送给接受方,接受方将接受的明文产生新的报文摘要与发送方的发来报文摘要解密比较,比较结果一致表示明文未被改动,如果不一致表示明文已被篡改。

简单文字描述:

客户端:

在APP 与 后端服务器接口调用的时候,将需要传输的参数进行关键参数(如:String A,String B)进行SHA1加密,获取加密之后的摘要(C);然后在接口调用的时候将参数原文(A,B) 和 加密的摘要(C) 一起传输给后台服务器;

服务器:

后台接口接受相关参数,然后将(A,B) 在后台进行SHA1加密,获取加密摘要D,最后将D与C进行比较,如果C == D ,则 A和B 在传输过程中参数没有被窃取改变;如果 C != D,则说明A和B已经在传输过程中发生了改变,最好不要使用!

:需要在前后端共同定义一个加密额外秘钥,在进行SHA1加密的过程中添加进去,这样即使在客户端拦截到我们需要传输的参数,进行SHA1 加密,但是由于其不知道 秘钥,所以进行SHA1加密出来的摘要肯定和后端用相关参数、秘钥计算出来的结果不同。

关键SHA-1加密代码简单举例:

 public static String createSignature(String... arr) {
        try {
            Arrays.sort(arr);            
            StringBuilder content = new StringBuilder();
            for (int i = 0; i < arr.length; i++) {
                content.append(arr[i]);
            }
            MessageDigest md = MessageDigest.getInstance("SHA-1");           
            byte[] digest = md.digest(content.toString().getBytes());
            return byteToStr(digest);
        } catch (Exception e) {  
        }
        return null;
}

算法原理:

1、将消息摘要转换成位字符串

因为在SHA-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

如果大家对MD-5不陌生的话,会发现一个重要的现象,其前四个与MD-5一样,但不同之处为存储为big-endien format.

5、计算消息摘要

在计算报文之前我们还要做一些基本的工作,就是在我们计算过程中要用到的方法,或定义。

(1)循环左移操作符Sn(x),x是一个字,也就是32bit大小的变量,n是一个整数且0<=n<=32。Sn(X) = (X<<n)OR(X>>32-n)

(2)在程序中所要用到的常量,这一系列常量字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)

(3)所要用到的一系列函数

 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)

(4)计算

计算需要一个缓冲区,由5个32位的字组成,还需要一个80个32位字的缓冲区。第一个5个字的缓冲区被标识为A,B,C,D,E。80个字的缓冲区被标识为W0, W1,…, W79

另外还需要一个一个字的TEMP缓冲区。

为了产生消息摘要,在第4部分中定义的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.

对于SHA256,SHA384,SHA512。你也可以用相似的办法来计算消息摘要。对消息进行补位的算法完全是一样的。

加密:

C语言:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/sha.h>
 
int main()
{
  int i;
  unsigned char result[SHA_DIGEST_LENGTH];
  const char *string = "Encryption Code";
  SHA1(string, strlen(string), result);
  for(i = 0; i < SHA_DIGEST_LENGTH; i++)
    printf("%02x%c", result[i], i < (SHA_DIGEST_LENGTH-1) ? ' ' : '\n');
  return EXIT_SUCCESS;
}

Java:

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
 
public class Digester {
 
    public static void main(String[] args) {
        System.out.println(hexDigest("Encryption code", "SHA-1"));
    }
    static String hexDigest(String str, String digestName) {
        try {
            MessageDigest md = MessageDigest.getInstance(digestName);
            byte[] digest = md.digest(str.getBytes(StandardCharsets.UTF_8));
            char[] hex = new char[digest.length * 2];
            for (int i = 0; i < digest.length; i++) {
                hex[2 * i] = "0123456789abcdef".charAt((digest[i] & 0xf0) >> 4);
                hex[2 * i + 1] = "0123456789abcdef".charAt(digest[i] & 0x0f);
            }
            return new String(hex);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException(e);
        }
    }
}
    public static String sha1(String data) throws NoSuchAlgorithmException {
        data += "lyz";
        MessageDigest md = MessageDigest.getInstance("SHA1");
       byte[] b = data.getBytes();
       md.update(b);
        //获取密文  (完成摘要计算)
        byte[] b2 = md.digest();
        //获取计算的长度
        int len = b2.length;
        //16进制字符串
        String str = "0123456789abcdef";
        //把字符串转为字符串数组
        char[] ch = str.toCharArray();
        //创建一个40位长度的字节数组
        char[] chs = new char[len*2];
        //循环20次
        for(int i=0,k=0;i<len;i++) {
            byte b3 = b2[i];//获取摘要计算后的字节数组中的每个字节
            // >>>:无符号右移  
            // &:按位与
            //0xf:0-15的数字
            chs[k++] = ch[b3 >>> 4 & 0xf];
            chs[k++] = ch[b3 & 0xf];
        }
        //字符数组转为字符串
        return new String(chs);
    }
主函数:
    public static void main(String[] args) throws NoSuchAlgorithmException {
        
        String data = "跳梁小豆tlxd666";
        String result = sha1(data);
        System.out.println("加密后:"+result);
}  

python:

import hashlib
h = hashlib.sha1()
h.update(bytes("Ars longa, vita brevis", encoding="ASCII"))
h.hexdigest()
# "e640d285242886eb96ab80cbf858389b3df52f43"

解密:暴力破解

package sha1;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.Callable;
import java.lang.Math;

public  class Sha1 implements Callable {

    private int threadNumber;
    Sha1(int threadNumber){
        this.threadNumber = threadNumber ;
    }
    @Override
    public Object call() throws Exception {
        /*可能的字符*/
        char [] encryptedText = ("QWINqwin%(*=2468").toCharArray();
        //char [] encryptedText = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890~!@#$%^&*()[]|;',<>.?".toCharArray();
        /*长度为5的密文字符串*/
        String data = null;
        /*根据输入的线程编号threadNumber,分配相应的任务*/
        int [] temp = new int[8];
        for (int i = 0; i<temp.length ; i++) {
            int n = (int) Math.pow(2,i);
            if((threadNumber & n) > 0){
                temp[i] = 1;
            }
            else {
                temp[i] = 0;
            }
        }
        int length = encryptedText.length / 2;
        /*8个字符的组合*/
        for(int a = (length * temp[0]) ; a < (length * (temp[0]+1)); a++){
            for(int b = (length * temp[1]); b < (length * (temp[1]+1)); b++){
                for(int c = (length * temp[2]); c < (length * (temp[2]+1)); c++){
                    for(int d = (length * temp[3]); d < (length * (temp[3]+1)); d++){
                        for(int e = (length * temp[4]); e < (length * (temp[4]+1)); e++){
                            for(int f = (length * temp[5]); f < (length * (temp[5]+1)); f++){
                                for(int g = (length * temp[6]); g < (length * (temp[6]+1)); g++){
                                    for(int h = (length * temp[7]); h < (length * (temp[7]+1)); h++){
                                        /*获取不同的键盘字符组合*/
                                        data = ""+ encryptedText[a] + encryptedText[b] + encryptedText[c] + encryptedText[d] + encryptedText[e] + encryptedText[f]+ encryptedText[g]+ encryptedText[h];
                                        /*将字符加密*/
                                        String result = sha1(data);
                                        String decrypted = "67ae1a64661ac8b4494666f58c4822408dd0a3e4";
                                        //String decrypted = "4b58475789e60dbf1a28d638b556a938134644c8";
                                        /*如果字符加密后的sha1哈希值和所给的相等,即为找到了答案*/
                                        if(decrypted.equals(result)){
                                            //System.out.println("4b58475789e60dbf1a28d638b556a938134644c8的sha1算法解密结果为: "+data);
                                            System.out.println("67ae1a64661ac8b4494666f58c4822408dd0a3e4的sha1算法解密结果为: "+data);
                                            return 10;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return 0;
    }
    
    public static String sha1(String data) throws NoSuchAlgorithmException {
        //信息摘要器                                算法名称
        MessageDigest md = MessageDigest.getInstance("SHA1");
        //把字符串转为字节数组
        byte[] b = data.getBytes();
        //使用指定的字节来更新我们的摘要
        md.update(b);
        //获取密文  (完成摘要计算)
        byte[] b2 = md.digest();
        //获取计算的长度
        int len = b2.length;
        //16进制字符串
        String str = "0123456789abcdef";
        //把字符串转为字符串数组
        char[] ch = str.toCharArray();

        //创建一个40位长度的字节数组
        char[] chs = new char[len*2];
        //循环20次
        for(int i=0,k=0;i<len;i++) {
            //获取摘要计算后的字节数组中的每个字节
            byte b3 = b2[i];
            // >>>:无符号右移
            // &:按位与
            //0xf:0-15的数字
            chs[k++] = ch[b3 >>> 4 & 0xf];
            chs[k++] = ch[b3 & 0xf];
        }
        //字符数组转为字符串
        return new String(chs);
    }
}

SHA加密和解密算法详解&代码示例:

import java.security.MessageDigest;
public class SHAUtil {
    /***
     * SHA加密 生成40位SHA码
     */
    public static String shaEncode(String data) throws Exception {
        MessageDigest sha = MessageDigest.getInstance("SHA");

        byte[] byteArray = data.getBytes("UTF-8");

        // md5Bytes的长度为20
        byte[] md5Bytes = sha.digest(byteArray);

        // 转换成16进制字符串
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }

    public static void main(String args[]) throws Exception {
        String str = new String("1A2B3C4D5E");
        System.out.println("原始:" + str);
        System.out.println("SHA后:" + shaEncode(str));
    }
}