cybersecurity · beginner · ~15 min · safe pentest lab

Validate a Canonical SHA-256 Hex Digest

Practice deny-by-default input validation on attacker-controlled text: enforce an exact length and a strict character alphabet while bounds-checking every index before dereferencing, and reject anything that is not the canonical form.

Challenge

The problem

A SHA-256 digest is 256 bits. When systems store or compare hashes as text, the canonical form is exactly 64 lowercase hexadecimal characters ([0-9a-f]). Attacker-controlled strings routinely flow into hash-comparison code: session tokens, integrity checksums, API signatures, cached lookup keys.

If you accept a malformed "hash" without validating it, downstream code may treat it as trusted, index a table with it, log it unescaped, or compare it in ways that leak information. Uppercase, wrong length, embedded spaces, or non-hex bytes must all be rejected. Deny by default.

The task

Implement int valid_sha256_hex(const char *s) that returns 1 only if s is exactly 64 characters long and every character is a lowercase hex digit. Otherwise return 0. A NULL pointer returns 0.

You operate on a fixed string the caller hands you. Never read past the terminating NUL: check each byte in bounds and stop at the first \0. Do not call strlen and then loop assuming the length — walk the buffer defensively.

Edge cases

  • NULL input.
  • Empty string and strings shorter than 64 chars (early NUL).
  • Strings longer than 64 chars (65th byte is not NUL).
  • Uppercase hex, trailing/embedded spaces, and out-of-alphabet bytes like g.

Input format

A single C string pointer s (possibly NULL). Only the bytes up to the terminating NUL may be read.

Output format

int: 1 if s is a canonical lowercase 64-char hex SHA-256 digest, otherwise 0.

Constraints

C11, freestanding-friendly: no I/O, no allocation, no network or filesystem access. Must not read beyond the NUL terminator. Must run in O(n) with no undefined behavior. NULL must return 0 without dereferencing.

Starter code

int valid_sha256_hex(const char *s){
    /* TODO: validate that s is a 64-char lowercase hex SHA-256 digest.
       This insecure stub trusts the input and always accepts. Replace it. */
    (void)s;
    return -1;
}

Common mistakes

Using strlen() and trusting its result instead of walking the buffer, which reads the whole string even when it is far too long; accepting uppercase hex because you used a case-insensitive check; forgetting to confirm the string ends exactly at index 64 so an over-long string with a valid 64-char prefix is wrongly accepted; dereferencing s before the NULL check; using isxdigit() from <ctype.h> which also accepts uppercase and is locale-dependent.

Edge cases to handle

NULL pointer returns 0 with no dereference; empty string returns 0; a 62- or 63-char string returns 0 (early NUL detected in the loop); a 65+ char string returns 0 (byte at index 64 is not NUL); uppercase A-F returns 0; a trailing or embedded space returns 0; a byte like 'g' or punctuation returns 0; all-zeros and all-f 64-char strings return 1.

Background lessons

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.