implementation of the sha1 hash function and gives utilities to find hash of string or hash of text from a file also contains a test class to verify that the generated hash matches what is returned by the hashlib library usage python sha1 py string hello world python sha1 py file helloworld txt when run without any arguments it prints the hash of the string hello world welcome to cryptography sha1 hash or sha1 sum of a string is a cryptographic function which means it is easy to calculate forwards but extremely difficult to calculate backwards what this means is you can easily calculate the hash of a string but it is extremely difficult to know the original string if you have its hash this property is useful for communicating securely send encrypted messages and is very useful in payment systems blockchain and cryptocurrency etc the algorithm as described in the reference first we start with a message the message is padded and the length of the message is added to the end it is then split into blocks of 512 bits or 64 bytes the blocks are then processed one at a time each block must be expanded and compressed the value after each compression is added to a 160bit buffer called the current hash state after the last block is processed the current hash state is returned as the final hash reference https deadhacker com20060221sha1illustrated class to contain the entire pipeline for sha1 hashing algorithm sha1hashbytes allan utf8 finalhash 872af2d8ac3d8695387e7c804bf0e02c18df9e6e initiates the variables data and h h is a list of 5 8digit hexadecimal numbers corresponding to 1732584193 4023233417 2562383102 271733878 3285377520 respectively we will start with this as a message digest 0x is how you write hexadecimal numbers in python static method to be used inside other methods left rotates n by b sha1hash rotate12 2 48 pads the input message with zeros so that paddeddata has 64 bytes or 512 bits returns a list of bytestrings each of length 64 staticmethod takes a bytestringblock of length 64 unpacks it to a list of integers and returns a list of 80 integers after some bit operations calls all the other methods to process the input pads the data then splits into blocks and then does a series of operations for each block including expansion for each block the variable h that was initialized is copied to a b c d e and these 5 variables a b c d e undergo several changes after all the blocks are processed these 5 variables are pairwise added to h ie a to h0 b to h1 and so on this h becomes our final hash which is returned provides option string or file to take input and prints the calculated sha1 hash unittest main has been commented out because we probably don t want to run the test each time unittest main in any case hash input should be a bytestring hashlib is only used inside the test class class to contain the entire pipeline for sha1 hashing algorithm sha1hash bytes allan utf 8 final_hash 872af2d8ac3d8695387e7c804bf0e02c18df9e6e initiates the variables data and h h is a list of 5 8 digit hexadecimal numbers corresponding to 1732584193 4023233417 2562383102 271733878 3285377520 respectively we will start with this as a message digest 0x is how you write hexadecimal numbers in python static method to be used inside other methods left rotates n by b sha1hash rotate 12 2 48 pads the input message with zeros so that padded_data has 64 bytes or 512 bits returns a list of bytestrings each of length 64 staticmethod takes a bytestring block of length 64 unpacks it to a list of integers and returns a list of 80 integers after some bit operations calls all the other methods to process the input pads the data then splits into blocks and then does a series of operations for each block including expansion for each block the variable h that was initialized is copied to a b c d e and these 5 variables a b c d e undergo several changes after all the blocks are processed these 5 variables are pairwise added to h ie a to h 0 b to h 1 and so on this h becomes our final hash which is returned noqa s324 provides option string or file to take input and prints the calculated sha1 hash unittest main has been commented out because we probably don t want to run the test each time unittest main in any case hash input should be a bytestring
import argparse
import hashlib  
import struct


class SHA1Hash:
    

    def __init__(self, data):
        
        self.data = data
        self.h = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0]

    @staticmethod
    def rotate(n, b):
        
        return ((n << b) | (n >> (32 - b))) & 0xFFFFFFFF

    def padding(self):
        
        padding = b"\x80" + b"\x00" * (63 - (len(self.data) + 8) % 64)
        padded_data = self.data + padding + struct.pack(">Q", 8 * len(self.data))
        return padded_data

    def split_blocks(self):
        
        return [
            self.padded_data[i : i + 64] for i in range(0, len(self.padded_data), 64)
        ]

    
    def expand_block(self, block):
        
        w = list(struct.unpack(">16L", block)) + [0] * 64
        for i in range(16, 80):
            w[i] = self.rotate((w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]), 1)
        return w

    def final_hash(self):
        
        self.padded_data = self.padding()
        self.blocks = self.split_blocks()
        for block in self.blocks:
            expanded_block = self.expand_block(block)
            a, b, c, d, e = self.h
            for i in range(80):
                if 0 <= i < 20:
                    f = (b & c) | ((~b) & d)
                    k = 0x5A827999
                elif 20 <= i < 40:
                    f = b ^ c ^ d
                    k = 0x6ED9EBA1
                elif 40 <= i < 60:
                    f = (b & c) | (b & d) | (c & d)
                    k = 0x8F1BBCDC
                elif 60 <= i < 80:
                    f = b ^ c ^ d
                    k = 0xCA62C1D6
                a, b, c, d, e = (
                    self.rotate(a, 5) + f + e + k + expanded_block[i] & 0xFFFFFFFF,
                    a,
                    self.rotate(b, 30),
                    c,
                    d,
                )
            self.h = (
                self.h[0] + a & 0xFFFFFFFF,
                self.h[1] + b & 0xFFFFFFFF,
                self.h[2] + c & 0xFFFFFFFF,
                self.h[3] + d & 0xFFFFFFFF,
                self.h[4] + e & 0xFFFFFFFF,
            )
        return ("{:08x}" * 5).format(*self.h)


def test_sha1_hash():
    msg = b"Test String"
    assert SHA1Hash(msg).final_hash() == hashlib.sha1(msg).hexdigest()  


def main():
    
    
    parser = argparse.ArgumentParser(description="Process some strings or files")
    parser.add_argument(
        "--string",
        dest="input_string",
        default="Hello World!! Welcome to Cryptography",
        help="Hash the string",
    )
    parser.add_argument("--file", dest="input_file", help="Hash contents of a file")
    args = parser.parse_args()
    input_string = args.input_string
    
    if args.input_file:
        with open(args.input_file, "rb") as f:
            hash_input = f.read()
    else:
        hash_input = bytes(input_string, "utf-8")
    print(SHA1Hash(hash_input).final_hash())


if __name__ == "__main__":
    main()
    import doctest

    doctest.testmod()