Skip to content

Checksum Verifier — C++ source

Drag and drop a file to compute its MD5, SHA-1, SHA-256, and SHA-512 checksums. Paste an expected hash to verify integrity - detect tampered or corrupted downloads instantly. Runs entirely in your browser.

This is the C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.

// checksum-verifier — compute MD5 / SHA-1 / SHA-256 / SHA-512 of a buffer and
//                     verify a pasted hash against it.
//
// Language: C++ (C++17, standard library only)
// Ported from src/lib/checksum-verifier.ts (the canonical TypeScript
// implementation). display source — part of CosmoDev's polyglot tool pages.
//
// The TS reference hand-rolls MD5 (RFC 1321) because the Web Crypto API
// refuses to implement it, and delegates SHA-1/256/512 to crypto.subtle.
// C++ has no standard crypto facility, so all four digests are implemented
// here from their specifications:
//
//   MD5      RFC 1321      little-endian words, 64-byte blocks
//   SHA-1    FIPS 180-4    big-endian words, 64-byte blocks
//   SHA-256  FIPS 180-4    big-endian words, 64-byte blocks
//   SHA-512  FIPS 180-4    big-endian 64-bit words, 128-byte blocks

#include <algorithm>
#include <array>
#include <cctype>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <optional>
#include <string>
#include <vector>

namespace checksum_verifier {

using Bytes = std::vector<uint8_t>;

/** All four checksums of one buffer, lowercase hex. */
struct ChecksumResult {
  std::string md5;
  std::string sha1;
  std::string sha256;
  std::string sha512;
};

enum class HashAlgorithm { Md5, Sha1, Sha256, Sha512 };

// ---------------------------------------------------------------------------
// MD5 (RFC 1321) — incremental hasher, a direct port of createMD5()
// ---------------------------------------------------------------------------

// Per-round shift amounts (RFC 1321, section 3.4).
constexpr int MD5_S[64] = {
  7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
  5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
  4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
  6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
};

// T[i] = floor(2^32 * |sin(i + 1)|) — the RFC 1321 sine table. IEEE-754
// doubles make this deterministic on every platform. Built once at first use
// (std::sin is not constexpr).
static const std::array<uint32_t, 64>& md5K() {
  static const std::array<uint32_t, 64> TABLE = [] {
    std::array<uint32_t, 64> t{};
    for (int i = 0; i < 64; i++) {
      t[i] = uint32_t(std::floor(std::abs(std::sin(double(i + 1))) * 4294967296.0));
    }
    return t;
  }();
  return TABLE;
}

/** Incremental MD5 (RFC 1321). MD5 is only for integrity checks — not security. */
class MD5Hasher {
 public:
  /** Absorb bytes. Call repeatedly for chunked input (no length limit). */
  void update(const uint8_t* data, std::size_t len) {
    total_ += len;
    std::size_t pos = 0;
    if (buffered_ > 0) {
      const std::size_t take = std::min<std::size_t>(64 - buffered_, len);
      std::memcpy(buf_.data() + buffered_, data, take);
      buffered_ += take;
      pos = take;
      if (buffered_ == 64) {
        transform(buf_.data(), 0);
        buffered_ = 0;
      }
    }
    while (pos + 64 <= len) {
      transform(data, pos);
      pos += 64;
    }
    if (pos < len) {
      std::memcpy(buf_.data() + buffered_, data + pos, len - pos);
      buffered_ += len - pos;
    }
  }

  void update(const Bytes& data) { update(data.data(), data.size()); }

  /** Pad, append the 64-bit length, and return the lowercase hex digest. */
  std::string digestHex() {
    // Capture the message length BEFORE padding updates `total`.
    const uint32_t bitLenLo = uint32_t(total_ << 3);
    const uint32_t bitLenHi = uint32_t(total_ >> 29);  // total * 8 / 2^32
    const std::size_t padLen = buffered_ < 56 ? 56 - buffered_ : 120 - buffered_;
    Bytes pad(padLen, 0);
    pad[0] = 0x80;
    update(pad);
    Bytes lenBytes(8);
    for (int i = 0; i < 4; i++) lenBytes[i] = uint8_t(bitLenLo >> (8 * i));
    for (int i = 0; i < 4; i++) lenBytes[4 + i] = uint8_t(bitLenHi >> (8 * i));
    update(lenBytes);  // buffered is exactly 56, so this fills the final block
    auto wordLE = [](uint32_t n) {
      std::string s;
      for (int i = 0; i < 4; i++) {
        s += "0123456789abcdef"[((n >> (8 * i)) & 0xff) >> 4];
        s += "0123456789abcdef"[(n >> (8 * i)) & 0xf];
      }
      return s;
    };
    return wordLE(a0_) + wordLE(b0_) + wordLE(c0_) + wordLE(d0_);
  }

 private:
  static uint32_t rotl32(uint32_t v, int c) { return (v << c) | (v >> (32 - c)); }

  void transform(const uint8_t* block, std::size_t start) {
    uint32_t m[16];
    for (int i = 0; i < 16; i++) {
      const std::size_t o = start + i * 4;
      m[i] = uint32_t(block[o]) | uint32_t(block[o + 1]) << 8 |
             uint32_t(block[o + 2]) << 16 | uint32_t(block[o + 3]) << 24;
    }
    uint32_t a = a0_, b = b0_, c = c0_, d = d0_;
    for (int i = 0; i < 64; i++) {
      uint32_t f;
      int g;
      if (i < 16) {
        f = (b & c) | (~b & d);
        g = i;
      } else if (i < 32) {
        f = (d & b) | (~d & c);
        g = (5 * i + 1) % 16;
      } else if (i < 48) {
        f = b ^ c ^ d;
        g = (3 * i + 5) % 16;
      } else {
        f = c ^ (b | ~d);
        g = (7 * i) % 16;
      }
      const uint32_t sum = f + a + md5K()[i] + m[g];
      const int rot = MD5_S[i];
      const uint32_t tmp = d;
      d = c;
      c = b;
      b = b + rotl32(sum, rot);
      a = tmp;
    }
    a0_ += a;
    b0_ += b;
    c0_ += c;
    d0_ += d;
  }

  uint32_t a0_ = 0x67452301;
  uint32_t b0_ = 0xefcdab89;
  uint32_t c0_ = 0x98badcfe;
  uint32_t d0_ = 0x10325476;
  uint64_t total_ = 0;
  std::array<uint8_t, 64> buf_{};
  std::size_t buffered_ = 0;
};

/** Synchronous one-shot MD5 of a buffer, as lowercase hex. */
std::string md5(const Bytes& data) {
  MD5Hasher hasher;
  hasher.update(data);
  return hasher.digestHex();
}

// ---------------------------------------------------------------------------
// SHA-1 / SHA-256 / SHA-512 (FIPS 180-4)
// ---------------------------------------------------------------------------

static uint32_t rotr32(uint32_t v, int c) { return (v >> c) | (v << (32 - c)); }
static uint64_t rotr64(uint64_t v, int c) { return (v >> c) | (v << (64 - c)); }

static std::string toHex(const uint8_t* bytes, std::size_t len) {
  static const char* DIGITS = "0123456789abcdef";
  std::string s;
  s.reserve(len * 2);
  for (std::size_t i = 0; i < len; i++) {
    s += DIGITS[bytes[i] >> 4];
    s += DIGITS[bytes[i] & 0xf];
  }
  return s;
}

/** Length of the padded message: data + 0x80 + zeros + 8·lenBits length field. */
static std::size_t paddedLength(std::size_t len, std::size_t block, std::size_t lenField) {
  std::size_t total = len + 1;
  while (total % block != block - lenField) total++;
  return total + lenField;
}

/** SHA-1, FIPS 180-4 — 64-byte blocks, big-endian 32-bit words. */
std::string sha1Hex(const Bytes& data) {
  const std::size_t len = data.size();
  const uint64_t bits = uint64_t(len) * 8;
  const std::size_t total = paddedLength(len, 64, 8);
  uint32_t st[5] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0};

  for (std::size_t off = 0; off < total; off += 64) {
    uint8_t block[64];
    for (std::size_t i = 0; i < 64; i++) {
      const std::size_t at = off + i;
      if (at < len) block[i] = data[at];
      else if (at == len) block[i] = 0x80;
      else if (at < total - 8) block[i] = 0x00;
      else block[i] = uint8_t(bits >> (8 * (total - 1 - at)));
    }
    uint32_t w[80];
    for (int i = 0; i < 16; i++) {
      w[i] = uint32_t(block[i * 4]) << 24 | uint32_t(block[i * 4 + 1]) << 16 |
             uint32_t(block[i * 4 + 2]) << 8 | uint32_t(block[i * 4 + 3]);
    }
    for (int i = 16; i < 80; i++) {
      const uint32_t x = w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16];
      w[i] = (x << 1) | (x >> 31);
    }
    uint32_t a = st[0], b = st[1], c = st[2], d = st[3], e = st[4];
    for (int i = 0; i < 80; i++) {
      uint32_t f, k;
      if (i < 20) {
        f = (b & c) | (~b & d);
        k = 0x5a827999;
      } else if (i < 40) {
        f = b ^ c ^ d;
        k = 0x6ed9eba1;
      } else if (i < 60) {
        f = (b & c) | (b & d) | (c & d);
        k = 0x8f1bbcdc;
      } else {
        f = b ^ c ^ d;
        k = 0xca62c1d6;
      }
      const uint32_t temp = ((a << 5) | (a >> 27)) + f + e + k + w[i];
      e = d;
      d = c;
      c = (b << 30) | (b >> 2);
      b = a;
      a = temp;
    }
    st[0] += a;
    st[1] += b;
    st[2] += c;
    st[3] += d;
    st[4] += e;
  }

  uint8_t digest[20];
  for (int i = 0; i < 5; i++) {
    for (int j = 0; j < 4; j++) digest[i * 4 + j] = uint8_t(st[i] >> (24 - 8 * j));
  }
  return toHex(digest, 20);
}

constexpr uint32_t SHA256_K[64] = {
  0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
  0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
  0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
  0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
  0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
  0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
  0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
  0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
  0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
  0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
  0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
};

/** SHA-256, FIPS 180-4 — 64-byte blocks, big-endian 32-bit words. */
std::string sha256Hex(const Bytes& data) {
  const std::size_t len = data.size();
  const uint64_t bits = uint64_t(len) * 8;
  const std::size_t total = paddedLength(len, 64, 8);
  uint32_t st[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
                    0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};

  for (std::size_t off = 0; off < total; off += 64) {
    uint8_t block[64];
    for (std::size_t i = 0; i < 64; i++) {
      const std::size_t at = off + i;
      if (at < len) block[i] = data[at];
      else if (at == len) block[i] = 0x80;
      else if (at < total - 8) block[i] = 0x00;
      else block[i] = uint8_t(bits >> (8 * (total - 1 - at)));
    }
    uint32_t w[64];
    for (int i = 0; i < 16; i++) {
      w[i] = uint32_t(block[i * 4]) << 24 | uint32_t(block[i * 4 + 1]) << 16 |
             uint32_t(block[i * 4 + 2]) << 8 | uint32_t(block[i * 4 + 3]);
    }
    for (int i = 16; i < 64; i++) {
      const uint32_t s0 = rotr32(w[i - 15], 7) ^ rotr32(w[i - 15], 18) ^ (w[i - 15] >> 3);
      const uint32_t s1 = rotr32(w[i - 2], 17) ^ rotr32(w[i - 2], 19) ^ (w[i - 2] >> 10);
      w[i] = w[i - 16] + s0 + w[i - 7] + s1;
    }
    uint32_t a = st[0], b = st[1], c = st[2], d = st[3];
    uint32_t e = st[4], f = st[5], g = st[6], h = st[7];
    for (int i = 0; i < 64; i++) {
      const uint32_t S1 = rotr32(e, 6) ^ rotr32(e, 11) ^ rotr32(e, 25);
      const uint32_t ch = (e & f) ^ (~e & g);
      const uint32_t t1 = h + S1 + ch + SHA256_K[i] + w[i];
      const uint32_t S0 = rotr32(a, 2) ^ rotr32(a, 13) ^ rotr32(a, 22);
      const uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
      const uint32_t t2 = S0 + maj;
      h = g;
      g = f;
      f = e;
      e = d + t1;
      d = c;
      c = b;
      b = a;
      a = t1 + t2;
    }
    st[0] += a; st[1] += b; st[2] += c; st[3] += d;
    st[4] += e; st[5] += f; st[6] += g; st[7] += h;
  }

  uint8_t digest[32];
  for (int i = 0; i < 8; i++) {
    for (int j = 0; j < 4; j++) digest[i * 4 + j] = uint8_t(st[i] >> (24 - 8 * j));
  }
  return toHex(digest, 32);
}

constexpr uint64_t SHA512_K[80] = {
  0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL,
  0xe9b5dba58189dbbcULL, 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL,
  0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, 0xd807aa98a3030242ULL,
  0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL,
  0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL,
  0xc19bf174cf692694ULL, 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL,
  0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, 0x2de92c6f592b0275ULL,
  0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL,
  0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL,
  0xbf597fc7beef0ee4ULL, 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL,
  0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, 0x27b70a8546d22ffcULL,
  0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL,
  0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL,
  0x92722c851482353bULL, 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL,
  0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, 0xd192e819d6ef5218ULL,
  0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL,
  0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL,
  0x34b0bcb5e19b48a8ULL, 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL,
  0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, 0x748f82ee5defb2fcULL,
  0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL,
  0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL,
  0xc67178f2e372532bULL, 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL,
  0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, 0x06f067aa72176fbaULL,
  0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL,
  0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL,
  0x431d67c49c100d4cULL, 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL,
  0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL,
};

/** SHA-512, FIPS 180-4 — 128-byte blocks, big-endian 64-bit words. */
std::string sha512Hex(const Bytes& data) {
  const std::size_t len = data.size();
  const uint64_t bits = uint64_t(len) * 8;
  // SHA-512 blocks are 128 bytes and reserve 16 for a 128-bit length; only
  // the low 64 bits are ever non-zero for buffers this code can address.
  const std::size_t total = paddedLength(len, 128, 16);
  uint64_t st[8] = {0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, 0x3c6ef372fe94f82bULL,
                    0xa54ff53a5f1d36f1ULL, 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL,
                    0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL};

  for (std::size_t off = 0; off < total; off += 128) {
    uint8_t block[128];
    for (std::size_t i = 0; i < 128; i++) {
      const std::size_t at = off + i;
      if (at < len) block[i] = data[at];
      else if (at == len) block[i] = 0x80;
      else if (at < total - 8) block[i] = 0x00;
      else block[i] = uint8_t(bits >> (8 * (total - 1 - at)));
    }
    uint64_t w[80];
    for (int i = 0; i < 16; i++) {
      w[i] = 0;
      for (int j = 0; j < 8; j++) w[i] = (w[i] << 8) | block[i * 8 + j];
    }
    for (int i = 16; i < 80; i++) {
      const uint64_t s0 = rotr64(w[i - 15], 1) ^ rotr64(w[i - 15], 8) ^ (w[i - 15] >> 7);
      const uint64_t s1 = rotr64(w[i - 2], 19) ^ rotr64(w[i - 2], 61) ^ (w[i - 2] >> 6);
      w[i] = w[i - 16] + s0 + w[i - 7] + s1;
    }
    uint64_t a = st[0], b = st[1], c = st[2], d = st[3];
    uint64_t e = st[4], f = st[5], g = st[6], h = st[7];
    for (int i = 0; i < 80; i++) {
      const uint64_t S1 = rotr64(e, 14) ^ rotr64(e, 18) ^ rotr64(e, 41);
      const uint64_t ch = (e & f) ^ (~e & g);
      const uint64_t t1 = h + S1 + ch + SHA512_K[i] + w[i];
      const uint64_t S0 = rotr64(a, 28) ^ rotr64(a, 34) ^ rotr64(a, 39);
      const uint64_t maj = (a & b) ^ (a & c) ^ (b & c);
      const uint64_t t2 = S0 + maj;
      h = g;
      g = f;
      f = e;
      e = d + t1;
      d = c;
      c = b;
      b = a;
      a = t1 + t2;
    }
    st[0] += a; st[1] += b; st[2] += c; st[3] += d;
    st[4] += e; st[5] += f; st[6] += g; st[7] += h;
  }

  uint8_t digest[64];
  for (int i = 0; i < 8; i++) {
    for (int j = 0; j < 8; j++) digest[i * 8 + j] = uint8_t(st[i] >> (56 - 8 * j));
  }
  return toHex(digest, 64);
}

/** Compute all four checksums of a buffer. */
ChecksumResult computeChecksums(const Bytes& data) {
  return {md5(data), sha1Hex(data), sha256Hex(data), sha512Hex(data)};
}

// ---------------------------------------------------------------------------
// Verification helpers
// ---------------------------------------------------------------------------

/** Normalize a pasted hash: drop whitespace and `:` grouping, lowercase. */
static std::string normalizeHash(const std::string& hash) {
  std::string out;
  out.reserve(hash.size());
  for (char c : hash) {
    if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == ':') continue;
    out += char(std::tolower(static_cast<unsigned char>(c)));
  }
  return out;
}

/**
 * Detect the algorithm from the hex length: 32=MD5, 40=SHA-1, 64=SHA-256,
 * 128=SHA-512. std::nullopt when unrecognized (the TS `null` return).
 */
std::optional<HashAlgorithm> detectHashAlgorithm(const std::string& hash) {
  const std::string h = normalizeHash(hash);
  if (h.empty()) return std::nullopt;
  for (char c : h) {
    const bool hex = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
    if (!hex) return std::nullopt;
  }
  switch (h.size()) {
    case 32: return HashAlgorithm::Md5;
    case 40: return HashAlgorithm::Sha1;
    case 64: return HashAlgorithm::Sha256;
    case 128: return HashAlgorithm::Sha512;
    default: return std::nullopt;
  }
}

/** The computed hex digest for one algorithm (helper for verifyChecksum). */
static std::string digestOf(const ChecksumResult& result, HashAlgorithm algorithm) {
  switch (algorithm) {
    case HashAlgorithm::Md5: return result.md5;
    case HashAlgorithm::Sha1: return result.sha1;
    case HashAlgorithm::Sha256: return result.sha256;
    case HashAlgorithm::Sha512: return result.sha512;
  }
  return "";
}

/** Verification outcome: which algorithm, and does the hash match. */
struct VerifyResult {
  HashAlgorithm algorithm;
  bool match;
};

/**
 * Compare an expected hash against a computed result. Returns std::nullopt
 * when the expected string is not a recognizable hex hash of a supported
 * length.
 */
std::optional<VerifyResult> verifyChecksum(const std::string& expected,
                                           const ChecksumResult& result) {
  const auto algorithm = detectHashAlgorithm(expected);
  if (!algorithm) return std::nullopt;
  return VerifyResult{*algorithm, normalizeHash(expected) == digestOf(result, *algorithm)};
}

}  // namespace checksum_verifier

Also available in 8 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →