Quick Info

Category: Cryptography
Output Size: 160-512 bits
Block Size: 512/1024 bits
Variants: SHA-1, SHA-2, SHA-3

SHA (Secure Hash Algorithm)

Description

The Secure Hash Algorithm (SHA) is a family of cryptographic hash functions designed by the National Security Agency (NSA). These functions take an input of arbitrary length and produce a fixed-size output, making them ideal for digital signatures, message authentication, and data integrity verification.

  • Produces fixed-length output regardless of input size
  • One-way function (computationally infeasible to reverse)
  • Small changes in input produce large changes in output
  • Multiple variants with different security levels

History

NSA Logo

The SHA family began with SHA-0, published by the NSA in 1993. It was quickly replaced by SHA-1 in 1995 due to a significant flaw in the original algorithm. As computing power increased and cryptographic attacks evolved, the need for stronger hash functions led to the development of SHA-2 in 2001.

In 2015, SHA-3 was standardized, becoming the latest member of the SHA family. Unlike its predecessors, SHA-3 uses a completely different internal structure (Keccak) and was selected through a public competition run by NIST.

Today, while SHA-1 is considered cryptographically broken, SHA-2 and SHA-3 remain secure and are widely used in various security applications and protocols.

How It Works

  1. Message Preprocessing
    • Padding the message to a multiple of block size
    • Adding length information
    • Breaking into fixed-size blocks
  2. Initialize Hash Values
    • Set initial hash values (constants)
    • Different for each SHA variant
  3. Process Message Blocks
    • Message schedule generation
    • Compression function application
    • State update after each block
  4. Final Output
    • Concatenate final state values
    • Truncate to desired output size

Visualization

Click Start to begin SHA hashing

Implementation

def sha256(message: bytes) -> bytes:
    """Implementation of SHA-256 hash function"""
    # Initial hash values (first 32 bits of the fractional parts of the square roots of the first 8 primes)
    h0 = 0x6a09e667
    h1 = 0xbb67ae85
    h2 = 0x3c6ef372
    h3 = 0xa54ff53a
    h4 = 0x510e527f
    h5 = 0x9b05688c
    h6 = 0x1f83d9ab
    h7 = 0x5be0cd19

    # Round constants (first 32 bits of the fractional parts of the cube roots of the first 64 primes)
    k = [
        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
        # ... (remaining constants omitted for brevity)
    ]

    def right_rotate(n: int, d: int) -> int:
        """Rotate a 32-bit integer n right by d bits"""
        return (n >> d) | (n << (32 - d)) & 0xffffffff

    def process_block(block: bytes) -> None:
        """Process a 512-bit block"""
        nonlocal h0, h1, h2, h3, h4, h5, h6, h7
        
        # Create message schedule
        w = [0] * 64
        for i in range(16):
            w[i] = int.from_bytes(block[i*4:(i+1)*4], 'big')
        
        for i in range(16, 64):
            s0 = right_rotate(w[i-15], 7) ^ right_rotate(w[i-15], 18) ^ (w[i-15] >> 3)
            s1 = right_rotate(w[i-2], 17) ^ right_rotate(w[i-2], 19) ^ (w[i-2] >> 10)
            w[i] = (w[i-16] + s0 + w[i-7] + s1) & 0xffffffff

        # Initialize working variables
        a, b, c, d, e, f, g, h = h0, h1, h2, h3, h4, h5, h6, h7

        # Main loop
        for i in range(64):
            S1 = right_rotate(e, 6) ^ right_rotate(e, 11) ^ right_rotate(e, 25)
            ch = (e & f) ^ ((~e) & g)
            temp1 = (h + S1 + ch + k[i] + w[i]) & 0xffffffff
            S0 = right_rotate(a, 2) ^ right_rotate(a, 13) ^ right_rotate(a, 22)
            maj = (a & b) ^ (a & c) ^ (b & c)
            temp2 = (S0 + maj) & 0xffffffff

            h = g
            g = f
            f = e
            e = (d + temp1) & 0xffffffff
            d = c
            c = b
            b = a
            a = (temp1 + temp2) & 0xffffffff

        # Update hash values
        h0 = (h0 + a) & 0xffffffff
        h1 = (h1 + b) & 0xffffffff
        h2 = (h2 + c) & 0xffffffff
        h3 = (h3 + d) & 0xffffffff
        h4 = (h4 + e) & 0xffffffff
        h5 = (h5 + f) & 0xffffffff
        h6 = (h6 + g) & 0xffffffff
        h7 = (h7 + h) & 0xffffffff

    # Preprocess message
    message_len = len(message)
    message = bytearray(message)
    message.append(0x80)
    while (len(message) + 8) % 64 != 0:
        message.append(0x00)
    message.extend((message_len * 8).to_bytes(8, 'big'))

    # Process message in 512-bit blocks
    for i in range(0, len(message), 64):
        process_block(message[i:i+64])

    # Produce final hash value
    return b''.join(x.to_bytes(4, 'big') for x in [h0, h1, h2, h3, h4, h5, h6, h7])
#include <vector>
#include <string>
#include <cstring>
#include <openssl/sha.h>

class SHA256 {
private:
    SHA256_CTX ctx;
    unsigned char hash[SHA256_DIGEST_LENGTH];

public:
    SHA256() {
        SHA256_Init(&ctx);
    }

    void update(const void* data, size_t len) {
        SHA256_Update(&ctx, data, len);
    }

    void update(const std::string& data) {
        update(data.c_str(), data.length());
    }

    std::vector<unsigned char> final() {
        SHA256_Final(hash, &ctx);
        return std::vector<unsigned char>(hash, hash + SHA256_DIGEST_LENGTH);
    }

    static std::vector<unsigned char> hash(const std::string& data) {
        SHA256 sha;
        sha.update(data);
        return sha.final();
    }
};
using System;
using System.Security.Cryptography;
using System.Text;

public class SHA256Hash
{
    private SHA256 sha256;

    public SHA256Hash()
    {
        sha256 = SHA256.Create();
    }

    public byte[] ComputeHash(string input)
    {
        return ComputeHash(Encoding.UTF8.GetBytes(input));
    }

    public byte[] ComputeHash(byte[] input)
    {
        return sha256.ComputeHash(input);
    }

    public string ComputeHashString(string input)
    {
        byte[] hashBytes = ComputeHash(input);
        StringBuilder builder = new StringBuilder();
        
        for (int i = 0; i < hashBytes.Length; i++)
        {
            builder.Append(hashBytes[i].ToString("x2"));
        }
        
        return builder.ToString();
    }

    public void Dispose()
    {
        sha256.Dispose();
    }
}

Complexity Analysis

Operation Time Complexity Space Complexity
Hash Computation O(n) O(1)
Block Processing O(1) O(1)

The time complexity is linear in the size of the input because each byte must be processed exactly once. The space complexity is constant because SHA uses a fixed amount of memory regardless of input size.

Advantages and Disadvantages

Advantages

  • Strong collision resistance
  • Widely adopted and standardized
  • Multiple variants for different security needs
  • Fast hardware implementations available
  • Deterministic output

Disadvantages

  • SHA-1 is no longer secure
  • Slower than non-cryptographic hashes
  • Complex implementation
  • Fixed output size may be too large for some uses