SHA1 algorithm details

Posted by neo0506 on Sat, 15 Jan 2022 14:12:24 +0100

SHA1 algorithm details

SHA1 algorithm, as a digest algorithm, is used in various signature, digest and other scenarios. In this chapter, we analyze the details of SHA1 algorithm in detail;

Definition of terms

  • word: 32bit String, which can be expressed as 8 hexadecimal sequences, such as A103FE23;

  • integer: number between 0-2 ^ 32-1;

  • Block: a String representing 512bit. A block can be represented as a sequence (array) of 16 word s;

Message fill rule

For the message M to be summarized, first fill in a ^ bit 1, then fill in N ^ bit 0, and finally fill in the length information of ^ 64bit ^ message M (unit bit). Finally, the following conditions need to be met:

  • (length of message M + 1 + N + 64)% 512 = 0; PS: the length unit of message M is bit;

Function definition:

  • f(t;B,C,D) = (B AND C) OR ((NOT B) AND D) ( 0 <= t <= 19)

  • f(t;B,C,D) = B XOR C XOR D (20 <= t <= 39)

  • f(t;B,C,D) = (B AND C) OR (B AND D) OR (C AND D) (40 <= t <= 59)

  • f(t;B,C,D) = B XOR C XOR D (60 <= t <= 79).

  • K(t) = 5A827999 ( 0 <= t <= 19)

  • K(t) = 6ED9EBA1 (20 <= t <= 39)

  • K(t) = 8F1BBCDC (40 <= t <= 59)

  • K(t) = CA62C1D6 (60 <= t <= 79).

  • S^n(X) = (X << n) OR (X >> 32-n)

Abstract algorithm

PS: Abstract There are two kinds of algorithms. One uses more memory, the algorithm is relatively simple (equivalent to exchanging space for time), and the other uses less memory, and the algorithm is relatively complex (equivalent to exchanging time for space). Here we use the first algorithm, that is, we use more memory, but the algorithm is simple;

1. Declare four buffer s:

  • Two buffers with A length of 5, in word. The data in one buffer is marked as A, B, C, D and E, and the data in the other buffer is marked as H0, H1, H2, H3 and H4;

  • A buffer with a length of 80, in word, where the data is marked as W(0), W(1), w (2) W(79);

  • A buffer of length 1, in word, marked as TEMP;

2. Fill the buffer marked H with the following values:

  • H0: 0x67452301

  • H1: 0xEFCDAB89

  • H2: 0x98BADCFE

  • H3: 0x10325476

  • H4: 0xC3D2E1F0

3. Fill the message with summary according to the filling rules mentioned above;

4. Group the filled data into M0, M1, M2 according to 512bit Mn to cycle processing until all packet processing is completed. The processing rules of Mi are as follows:

  • Split the data in Mi from left to right into W(0), w (1) In w (15); PS: note that the data in Mi is 512bit in total, which is exactly divided into 16 word s and put into W(0)-W(15);

  • Cyclic processing (t from 16 to 79): W(t) = S^1(W(t-3) XOR W(t-8) XOR W(t-14) XOR W(t-16));

  • Make A = H0, B = H1, C = H2, D = H3, E = H4;

  • Cyclic processing (t from 0 to 79): TEMP = S^5(A) + f(t;B,C,D) + E + W(t) + K(t);E = D; D = C; C = S^30(B); B = A; A = TEMP;

  • Finally, update the values in Hbuffer: H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E;

After Mn processing, H0 H1 H2 H3 H4 is the message summary we need;

Reference documents:

  • SHA1 specification: RFC3174

Refer to Java implementation:

import java.util.Arrays;

/**
 * RFC3174 Reference implementation of specification
 * 
 * @author JoeKerouac
 * @since 1.0.0
 */
public class SHA1 {

    /**
     * block Size in byte s
     */
    private static final int BLOCK_SIZE = 64;

    private final int[] H = new int[5];
    private final int[] W = new int[80];

    /**
     * Calculation summary information
     *
     * @param data
     *            Data to calculate summary information
     * @return Calculation results
     */
    public byte[] digest(byte[] data) {
        reset();

        byte[] padding = padding(data);
        int blockCount = padding.length / BLOCK_SIZE;
        for (int i = 0; i < blockCount; i++) {
            process(padding, i * BLOCK_SIZE);
        }

        byte[] result = new byte[20];
        for (int i = 0; i < 5; i++) {
            writeInt(H[i], result, i * 4);
        }

        return result;
    }

    /**
     * Reset H status value
     */
    private void reset() {
        H[0] = 0x67452301;
        H[1] = 0xEFCDAB89;
        H[2] = 0x98BADCFE;
        H[3] = 0x10325476;
        H[4] = 0xC3D2E1F0;

    }

    /**
     * fill
     * 
     * @param data
     *            Data to populate
     * @return Fill results
     */
    private byte[] padding(byte[] data) {
        // Calculate the total length to fill
        int paddingSize = BLOCK_SIZE - (data.length % BLOCK_SIZE);
        paddingSize = paddingSize < 9 ? paddingSize + BLOCK_SIZE : paddingSize;

        // Start generating populated data
        byte[] padding = new byte[data.length + paddingSize];
        System.arraycopy(data, 0, padding, 0, data.length);
        // First fill in a bit1 and 7 bit0, which exactly corresponds to - 128 of the signed byte value
        padding[data.length] = -128;

        // Fill 0
        for (int i = data.length + 1; i < padding.length - 8; i++) {
            padding[i] = 0;
        }

        // Fill in the length of the original message
        writeLong(data.length * 8L, padding, padding.length - 8);
        return padding;
    }

    /**
     * Write long value to byte array
     * 
     * @param value
     *            long value
     * @param data
     *            array
     * @param offset
     *            Write start position
     */
    private void writeLong(long value, byte[] data, int offset) {
        for (int i = 0; i < 8; i++) {
            data[offset + i] = (byte)(value >>> ((7 - i) * 8) & 0xff);
        }
    }

    /**
     * Write int value to byte array
     * 
     * @param value
     *            int value
     * @param data
     *            Array to write
     * @param offset
     *            Write start position
     */
    private void writeInt(int value, byte[] data, int offset) {
        for (int i = 0; i < 4; i++) {
            data[offset + i] = (byte)(value >>> ((3 - i) * 8) & 0xff);
        }
    }

    /**
     * Starting from the starting position, merge the four byte s in the original data into an int
     * 
     * @param data
     *            raw data
     * @param offset
     *            Starting position
     * @return int
     */
    private int mergeToInt(byte[] data, int offset) {
        return Byte.toUnsignedInt(data[offset]) << 24 | Byte.toUnsignedInt(data[1 + offset]) << 16
            | Byte.toUnsignedInt(data[2 + offset]) << 8 | Byte.toUnsignedInt(data[3 + offset]);
    }

    /**
     * A block that starts at the specified starting position for processing data
     * 
     * @param data
     *            Data to be processed
     * @param offset
     *            Starting position
     */
    private void process(byte[] data, int offset) {
        for (int j = 0; j < 16; j++) {
            W[j] = mergeToInt(data, offset + j * 4);
        }

        for (int t = 16; t < 80; t++) {
            // W(t-3) XOR W(t-8) XOR W(t-14) XOR W(t-16)
            int value = W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16];
            W[t] = value << 1 | value >>> 31;
        }

        int temp;
        int A = H[0];
        int B = H[1];
        int C = H[2];
        int D = H[3];
        int E = H[4];

        for (int t = 0; t < 80; t++) {
            // TEMP = S^5(A) + f(t;B,C,D) + E + W(t) + K(t);
            temp = (A << 5 | A >>> 27) + f(t, B, C, D) + E + W[t] + k(t);
            E = D;
            D = C;
            C = B << 30 | B >>> 2;
            B = A;
            A = temp;
        }

        H[0] += A;
        H[1] += B;
        H[2] += C;
        H[3] += D;
        H[4] += E;
    }

    /**
     * Corresponding to function K in RFC3174 specification
     * 
     * @param t
     *            t
     * @return result
     */
    private int k(int t) {
        // K(t) = 5A827999 ( 0 <= t <= 19)
        // K(t) = 6ED9EBA1 (20 <= t <= 39)
        // K(t) = 8F1BBCDC (40 <= t <= 59)
        // K(t) = CA62C1D6 (60 <= t <= 79).
        if (t >= 0 && t <= 19) {
            return 0x5A827999;
        } else if (t <= 39) {
            return 0x6ED9EBA1;
        } else if (t <= 59) {
            return 0x8F1BBCDC;
        } else {
            return 0xCA62C1D6;
        }
    }

    /**
     * Corresponding to function f in RFC3174 specification
     * 
     * @param t
     *            t
     * @param B
     *            B
     * @param C
     *            C
     * @param D
     *            D
     * @return result
     */
    private int f(int t, int B, int C, int D) {
        // f(t;B,C,D) = (B AND C) OR ((NOT B) AND D) ( 0 <= t <= 19)
        // f(t;B,C,D) = B XOR C XOR D (20 <= t <= 39)
        // f(t;B,C,D) = (B AND C) OR (B AND D) OR (C AND D) (40 <= t <= 59)
        // f(t;B,C,D) = B XOR C XOR D (60 <= t <= 79).
        if (t >= 0 && t <= 19) {
            return (B & C) | (~B & D);
        } else if (t <= 39) {
            return B ^ C ^ D;
        } else if (t <= 59) {
            return B & C | B & D | C & D;
        } else {
            return B ^ C ^ D;
        }
    }

    public static void main(String[] args) {
        // Assuming that this is our data, we can replace the data with the actual data in actual use
        byte[] data = new byte[63];
        SHA1 sha1 = new SHA1();
        // Calculate message summary
        byte[] digest = sha1.digest(data);
        System.out.println(Arrays.toString(digest));
    }

Contact me

  • Author wechat: Joe Kerouac

  • WeChat official account (first time to official account): Code depth Research Institute

  • GitHub: https://github.com/JoeKerouac

Topics: Algorithm security