A clean, portable, single-file SHA-1 implementation in C99 — no dependencies, no bloat.
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 types —
uint32_t/uint64_tthroughout; no assumption thatintorlongis 32 or 64 bits wide. - No undefined behaviour —
ROTL32casts both operands touint32_tbefore 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 oldSHA1()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 insidesha1_final(), so sensitive mid-state data does not linger on the stack or heap. - Clean separation — declarations live in
sha1.h; all definitions live insha1.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-
astress case.
sha1.h — public API (include this)
sha1.c — implementation (compile this)
No build system required. Two files, drop them in, done.
# Add sha1.c to whatever build you already have, e.g.:
gcc -std=c99 -O2 -o my_program main.c sha1.c#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;
}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;
}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;
}#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;
}/* 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 */| Input | Expected digest |
|---|---|
"" (empty) |
da39a3ee5e6b4b0d3255bfef95601890afd80709 |
"abc" |
a9993e364706816aba3e25717850c26c9cd0d89d |
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" |
84983e441c3bd26ebaae4aa1f95129e5e54670f1 |
1,000,000 × 'a' |
34aa973cd4c4daa4f61eeb2bdbad27316534016f |
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.
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.