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 →