Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

sha1.c

A clean, portable, single-file SHA-1 implementation in C99 — no dependencies, no bloat.


Why this implementation?

Most SHA-1 code you find online carries subtle bugs inherited from copy-paste chains: unguarded shift macros that trigger undefined behaviour, unsigned int used where a fixed-width uint32_t is required, bit-length accounting that silently breaks on long messages, and everything crammed into a single header that violates the one-definition rule the moment it is included in two translation units.

This implementation fixes all of that:

  • Correct fixed-width typesuint32_t / uint64_t throughout; no assumption that int or long is 32 or 64 bits wide.
  • No undefined behaviourROTL32 casts both operands to uint32_t before shifting, sidestepping sign-extension and shift-past-width UB.
  • Accurate bit-length accounting — total bits are captured before padding mutates the buffer, so hashes of messages near block boundaries are always correct.
  • No symbol collisions — the public API uses the sha1_ prefix; the old SHA1() name clashes with OpenSSL and several system headers.
  • Memory hygiene — the message schedule (W[80]) is zeroed after each block, and the entire context is zeroed inside sha1_final(), so sensitive mid-state data does not linger on the stack or heap.
  • Clean separation — declarations live in sha1.h; all definitions live in sha1.c. Drop both files into any project and compile.
  • Verified — tested against every official FIPS 180-4 / RFC 3174 test vector, including the empty string, the 448-bit FIPS message, and the one-million-a stress case.

Files

sha1.h   — public API (include this)
sha1.c   — implementation (compile this)

No build system required. Two files, drop them in, done.


Quick start

Compile

# Add sha1.c to whatever build you already have, e.g.:
gcc -std=c99 -O2 -o my_program main.c sha1.c

One-shot hash (entire message in memory)

#include <stdio.h>
#include "sha1.h"

int main(void)
{
    const char *msg = "Hello, world!";
    uint8_t hash[SHA1_DIGEST_SIZE];

    sha1_digest(msg, 13, hash);

    for (int i = 0; i < SHA1_DIGEST_SIZE; i++)
        printf("%02x", hash[i]);
    puts("");   /* 943a702d06f34599aee1f8da8ef9f7296031d699 */

    return 0;
}

Streaming hash (data arrives in chunks)

Use this when hashing files, network streams, or any data too large to hold in memory all at once.

#include <stdio.h>
#include "sha1.h"

int main(void)
{
    SHA1_CTX ctx;
    uint8_t  hash[SHA1_DIGEST_SIZE];
    uint8_t  buf[4096];
    size_t   n;
    FILE    *f = fopen("large_file.bin", "rb");

    sha1_init(&ctx);

    while ((n = fread(buf, 1, sizeof(buf), f)) > 0)
        sha1_update(&ctx, buf, n);

    sha1_final(&ctx, hash);
    fclose(f);

    for (int i = 0; i < SHA1_DIGEST_SIZE; i++)
        printf("%02x", hash[i]);
    puts("");

    return 0;
}

Hash multiple separate buffers as one logical message

sha1_update() can be called any number of times — the result is identical to concatenating all the inputs and hashing them together.

#include <stdio.h>
#include "sha1.h"

int main(void)
{
    SHA1_CTX ctx;
    uint8_t  hash[SHA1_DIGEST_SIZE];

    sha1_init(&ctx);
    sha1_update(&ctx, "foo",  3);
    sha1_update(&ctx, "fighters",  8);
    sha1_update(&ctx, "everlong",  8);
    sha1_final(&ctx, hash);

    /* Identical to sha1_digest("foofighterseverlong", 19, hash) */
    for (int i = 0; i < SHA1_DIGEST_SIZE; i++)
        printf("%02x", hash[i]);
    puts("");   /* c83b46a77f1e27bf371cb240528ddd7933148179 */

    return 0;
}

Convert digest bytes to a hex string

#include <stdio.h>
#include <string.h>
#include "sha1.h"

/* hex must be at least SHA1_DIGEST_SIZE * 2 + 1 bytes (41 bytes) */
void sha1_hex(const void *data, size_t len, char hex[41])
{
    uint8_t hash[SHA1_DIGEST_SIZE];
    sha1_digest(data, len, hash);
    for (int i = 0; i < SHA1_DIGEST_SIZE; i++)
        sprintf(hex + i * 2, "%02x", hash[i]);
    hex[40] = '\0';
}

int main(void)
{
    char hex[41];
    sha1_hex("yowaimo", 7, hex);
    printf("%s\n", hex);   /* e70f41572efbe9b52171049de269deeb09f3f2ed */
    return 0;
}

API reference

/* Initialise a context before first use. */
void sha1_init(SHA1_CTX *ctx);

/* Feed len bytes of data into the running hash.
   May be called any number of times. */
void sha1_update(SHA1_CTX *ctx, const void *data, size_t len);

/* Finalise and write the 20-byte digest into hash[].
   Zeroes the context — reinitialise with sha1_init() to reuse. */
void sha1_final(SHA1_CTX *ctx, uint8_t hash[SHA1_DIGEST_SIZE]);

/* One-shot convenience: init + update + final in one call. */
void sha1_digest(const void *data, size_t len, uint8_t hash[SHA1_DIGEST_SIZE]);

Constants:

#define SHA1_DIGEST_SIZE  20   /* output length in bytes */
#define SHA1_BLOCK_SIZE   64   /* internal compression block size */

Test vectors (FIPS 180-4 / RFC 3174)

Input Expected digest
"" (empty) da39a3ee5e6b4b0d3255bfef95601890afd80709
"abc" a9993e364706816aba3e25717850c26c9cd0d89d
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" 84983e441c3bd26ebaae4aa1f95129e5e54670f1
1,000,000 × 'a' 34aa973cd4c4daa4f61eeb2bdbad27316534016f

A note on SHA-1 security

SHA-1 is no longer considered secure for digital signatures or certificate chains — practical collision attacks have been demonstrated (SHAttered, 2017). It remains appropriate for non-security uses such as Git object IDs, checksums, cache keys, and HMAC-SHA1 in legacy protocols. If you need collision resistance, use SHA-256 or SHA-3.


License

I neither own this algorithm, not this implementation (It's open source lol), use it any way you like. A star would be appreciated tho.

About

A clean, portable, two-file SHA-1 implementation in C99 — correct types, no Undefined Behavior, no dependencies.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages