SHA-256

Header: #include <cryptopp/sha.h> | Namespace: CryptoPP Since: Crypto++ 4.0
Thread Safety: Not thread-safe per instance; use separate instances per thread

SHA-256 (Secure Hash Algorithm 256-bit) is a widely-used cryptographic hash function from the SHA-2 family. It’s a NIST/FIPS standard used in TLS, Bitcoin, digital signatures, and countless security applications.

Quick Example

#include <cryptopp/sha.h>
#include <cryptopp/hex.h>
#include <cryptopp/filters.h>
#include <iostream>

int main() {
    using namespace CryptoPP;

    SHA256 hash;
    std::string message = "Hello, World!";
    std::string digest, hexOutput;

    StringSource(message, true,
        new HashFilter(hash,
            new StringSink(digest)
        )
    );

    // Convert to hex
    StringSource(digest, true,
        new HexEncoder(new StringSink(hexOutput))
    );

    std::cout << "SHA-256: " << hexOutput << std::endl;
    // Expected: dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f

    return 0;
}

Usage Guidelines

ℹ️

Do:

  • Use SHA-256 for general-purpose hashing (file integrity, checksums)
  • Use for digital signatures and certificates
  • Use with HMAC for message authentication
  • Use as the underlying hash in PBKDF2 (PBKDF2-HMAC-SHA256)
  • Use as a fast follow-on KDF after Argon2 (once you already have a high-entropy key)
  • Generate test vectors for validation

Avoid:

  • Hashing passwords directly with plain SHA-256 (use Argon2 for password hashing)
  • Using SHA-1 for new applications (broken, use SHA-256)
  • Assuming collision resistance means preimage resistance

Class: SHA256

Cryptographic hash function producing 256-bit (32-byte) digests.

Constants

static const int DIGESTSIZE = 32;   // 32 bytes (256 bits)
static const int BLOCKSIZE = 64;    // 64 bytes (512 bits)

Constructors

Default Constructor

SHA256();

Create SHA-256 hash object in initial state.

Example:

SHA256 hash;

Methods

Update()

void Update(const byte* input, size_t length);

Add data to the hash computation.

Parameters:

  • input - Data to hash
  • length - Length of data in bytes

Can be called multiple times to hash data incrementally.

Example:

SHA256 hash;
hash.Update((const byte*)"Hello, ", 7);
hash.Update((const byte*)"World!", 6);
// Equivalent to hashing "Hello, World!"

Final()

void Final(byte* digest);

Finalize hash and get result.

Parameters:

  • digest - Output buffer (must be 32 bytes)

Note: Automatically calls Restart() after completion.

Example:

SHA256 hash;
std::string message = "Hello, World!";
hash.Update((const byte*)message.data(), message.size());

byte digest[SHA256::DIGESTSIZE];
hash.Final(digest);

TruncatedFinal()

void TruncatedFinal(byte* digest, size_t digestSize);

Get truncated hash (first digestSize bytes).

Parameters:

  • digest - Output buffer
  • digestSize - Number of bytes to output (≤ 32)

Use case: Some protocols require shorter hashes (e.g., 128-bit).

Example:

SHA256 hash;
hash.Update((const byte*)data, dataLen);

byte shortHash[16];  // 128-bit hash
hash.TruncatedFinal(shortHash, 16);

Restart()

void Restart();

Reset hash to initial state. Keeps object for reuse.

Example:

SHA256 hash;

// Hash first message
hash.Update((const byte*)msg1.data(), msg1.size());
byte digest1[32];
hash.Final(digest1);

// Reuse for second message
hash.Update((const byte*)msg2.data(), msg2.size());
byte digest2[32];
hash.Final(digest2);

CalculateDigest() - Static

static void CalculateDigest(byte* digest,
                            const byte* input,
                            size_t length);

One-shot hash computation.

Parameters:

  • digest - Output buffer (32 bytes)
  • input - Data to hash
  • length - Length of data

Example:

std::string message = "Hello, World!";
byte digest[SHA256::DIGESTSIZE];

SHA256::CalculateDigest(digest,
    (const byte*)message.data(),
    message.size()
);

VerifyDigest() - Static

static bool VerifyDigest(const byte* digest,
                         const byte* input,
                         size_t length);

Verify a hash digest (constant-time comparison).

Parameters:

  • digest - Expected hash (32 bytes)
  • input - Data to verify
  • length - Length of data

Returns: true if hash matches, false otherwise

Important: Uses constant-time comparison to prevent timing attacks.

Example:

byte expectedHash[32] = { /* ... */ };
std::string data = "Test data";

bool valid = SHA256::VerifyDigest(
    expectedHash,
    (const byte*)data.data(),
    data.size()
);

if (valid) {
    std::cout << "Hash verified!" << std::endl;
}

DigestSize()

unsigned int DigestSize() const;

Get hash output size.

Returns: 32 (bytes)

AlgorithmName()

std::string AlgorithmName() const;

Get algorithm name.

Returns: “SHA-256”

AlgorithmProvider()

std::string AlgorithmProvider() const;

Get implementation provider (hardware acceleration info).

Returns:

  • “SHA-NI” - Intel/AMD SHA Extensions
  • “ARMv8” - ARM SHA Extensions
  • “POWER8” - IBM POWER8+ SHA
  • “C++” - Software implementation

Example:

SHA256 hash;
std::cout << "Provider: " << hash.AlgorithmProvider() << std::endl;
// Output: "SHA-NI" on modern Intel/AMD CPUs

Complete Example: File Hashing

#include <cryptopp/sha.h>
#include <cryptopp/files.h>
#include <cryptopp/hex.h>
#include <iostream>

using namespace CryptoPP;

std::string hashFile(const std::string& filename) {
    SHA256 hash;
    std::string digest, hexDigest;

    FileSource(filename.c_str(), true,
        new HashFilter(hash,
            new StringSink(digest)
        )
    );

    StringSource(digest, true,
        new HexEncoder(new StringSink(hexDigest))
    );

    return hexDigest;
}

int main() {
    try {
        std::string hash = hashFile("document.pdf");
        std::cout << "SHA-256: " << hash << std::endl;

        // Save hash for later verification
        std::ofstream out("document.pdf.sha256");
        out << hash;

    } catch (const Exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}

Complete Example: Incremental Hashing

#include <cryptopp/sha.h>
#include <cryptopp/hex.h>
#include <fstream>
#include <iostream>

using namespace CryptoPP;

std::string hashLargeFile(const std::string& filename) {
    SHA256 hash;
    std::ifstream file(filename, std::ios::binary);

    if (!file) {
        throw std::runtime_error("Cannot open file");
    }

    // Process in 64KB chunks
    const size_t BUFFER_SIZE = 65536;
    byte buffer[BUFFER_SIZE];

    while (file.read((char*)buffer, BUFFER_SIZE) || file.gcount() > 0) {
        hash.Update(buffer, file.gcount());
    }

    byte digest[SHA256::DIGESTSIZE];
    hash.Final(digest);

    // Convert to hex
    std::string hexDigest;
    StringSource(digest, sizeof(digest), true,
        new HexEncoder(new StringSink(hexDigest))
    );

    return hexDigest;
}

int main() {
    std::string hash = hashLargeFile("large_video.mp4");
    std::cout << "SHA-256: " << hash << std::endl;
    return 0;
}

Complete Example: HMAC-SHA256

#include <cryptopp/sha.h>
#include <cryptopp/hmac.h>
#include <cryptopp/filters.h>
#include <cryptopp/hex.h>
#include <iostream>

using namespace CryptoPP;

std::string hmacSHA256(const std::string& key, const std::string& message) {
    HMAC<SHA256> hmac((const byte*)key.data(), key.size());

    std::string mac, hexMAC;
    StringSource(message, true,
        new HashFilter(hmac,
            new StringSink(mac)
        )
    );

    StringSource(mac, true,
        new HexEncoder(new StringSink(hexMAC))
    );

    return hexMAC;
}

int main() {
    std::string key = "secret-key-2025";
    std::string message = "Important message";

    std::string mac = hmacSHA256(key, message);
    std::cout << "HMAC-SHA256: " << mac << std::endl;

    return 0;
}

Performance

Hardware Acceleration

SHA-256 benefits from hardware acceleration on modern processors:

PlatformInstructionsSpeedup
Intel/AMD x86-64SHA Extensions (SHA-NI)3-5x
ARM v8+SHA Extensions3-5x
IBM POWER8+SHA instructions3-5x
SoftwarePure C++Baseline

Benchmarks (Approximate)

OperationSpeed (MB/s)Notes
With SHA-NI800-1500Hardware accelerated
Without200-400Software implementation
IncrementalSameNo overhead

Note: BLAKE3 is 2-4x faster than SHA-256, even with SHA-NI.

Checking Hardware Support

SHA256 hash;
std::string provider = hash.AlgorithmProvider();

if (provider == "SHA-NI" || provider == "ARMv8") {
    std::cout << "Hardware acceleration available!" << std::endl;
} else {
    std::cout << "Using software implementation" << std::endl;
}

Security

Security Properties

  • Collision resistance: 128-bit security (2^128 operations)
  • Preimage resistance: 256-bit security (2^256 operations)
  • Second preimage: 256-bit security
  • Output size: 256 bits (32 bytes)
  • Block size: 512 bits (64 bytes)
  • Standard: FIPS 180-4, NIST approved

Security Level

SHA-256 provides 128-bit collision resistance (birthday bound) and 256-bit preimage resistance.

Comparison:

  • SHA-256: ~128-bit collision / ~256-bit preimage
  • SHA-512: ~256-bit collision / ~512-bit preimage
  • BLAKE3: ~128-bit collision / ~128-bit preimage (but faster in software)

Important Security Notes

  1. Not for Password Hashing:

    // WRONG - SHA-256 is too fast for passwords
    std::string passwordHash = sha256(password);
    
    // CORRECT - Use Argon2
    Argon2 argon2;
    argon2.DeriveKey(hash, ...);
  2. Collision Resistance vs Preimage:

    • Collision: Finding two messages with same hash (~128-bit security)
    • Preimage: Finding message for given hash (~256-bit security)
    • Most applications are comfortable with ~128-bit overall security; SHA-256 exceeds that for both collision and preimage resistance
  3. SHA-1 is Broken:

    // NEVER use SHA-1 for new applications
    SHA1 broken;  // Collision attacks exist!
    
    // Use SHA-256 instead
    SHA256 secure;

Test Vectors (NIST)

// Test Vector 1: Empty string
// Input: ""
// SHA-256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

SHA256 hash;
byte digest[32];
hash.Final(digest);

byte expected[] = {
    0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14,
    0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
    0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c,
    0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55
};

assert(std::memcmp(digest, expected, 32) == 0);
// Test Vector 2: "abc"
// Input: "abc"
// SHA-256: ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad

std::string message = "abc";
SHA256 hash;
hash.Update((const byte*)message.data(), message.size());

byte digest[32];
hash.Final(digest);

byte expected[] = {
    0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea,
    0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23,
    0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c,
    0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad
};

assert(std::memcmp(digest, expected, 32) == 0);

Common Use Cases

1. File Integrity Verification

// Generate checksum
std::string originalHash = hashFile("software.zip");

// Later: verify file hasn't been modified
std::string currentHash = hashFile("software.zip");

if (originalHash == currentHash) {
    std::cout << "File integrity verified" << std::endl;
} else {
    std::cout << "WARNING: File has been modified!" << std::endl;
}

2. Content-Addressed Storage

// Store files by their SHA-256 hash
std::string content = readFile("photo.jpg");
std::string hash = sha256(content);

// Save as: storage/ba/7816bf.../content
std::string path = "storage/" + hash.substr(0, 2) + "/" +
                   hash.substr(2, 6) + "/content";
writeFile(path, content);

3. Digital Signatures (with RSA/ECDSA)

// Sign document
std::string document = readFile("contract.pdf");

SHA256 hash;
hash.Update((const byte*)document.data(), document.size());

byte digest[32];
hash.Final(digest);

// Sign the hash (not the whole document)
RSA::Signer signer(privateKey);
std::string signature = sign(digest, 32, signer);

4. Merkle Trees (Blockchain)

// Build Merkle tree from transaction hashes
std::vector<std::string> transactions;
std::vector<byte[32]> leafHashes;

for (const auto& tx : transactions) {
    byte hash[32];
    SHA256::CalculateDigest(hash,
        (const byte*)tx.data(), tx.size());
    leafHashes.push_back(hash);
}

// Combine hashes pairwise
// ...

Thread Safety

Not thread-safe. Use separate instances per thread.

// WRONG - sharing between threads
SHA256 global_hash;
void thread1() { global_hash.Update(...); }  // RACE CONDITION
void thread2() { global_hash.Update(...); }

// CORRECT - per-thread instances
void threadFunc(const std::string& data) {
    SHA256 hash;  // Thread-local
    hash.Update((const byte*)data.data(), data.size());
    byte digest[32];
    hash.Final(digest);
}

When to Use SHA-256

✅ Use SHA-256 for:

  1. File Integrity - Checksums, downloads verification
  2. Digital Signatures - Hash before signing
  3. Certificates - TLS/SSL certificates
  4. Blockchain - Bitcoin, Ethereum (though moving to SHA-3)
  5. HMAC - Message authentication (HMAC-SHA256)
  6. General Hashing - When you need a standard hash

❌ Don’t use SHA-256 for:

  1. Password Hashing - Use Argon2 (SHA-256 is too fast)
  2. High Performance - Use BLAKE3 (2-4x faster)
  3. New Protocols - Consider BLAKE3 or SHA-3 for future-proofing

SHA-256 vs Alternatives

AlgorithmSpeedSecurityStandardUse Case
SHA-256Fast128-bit collisionNIST/FIPSGeneral purpose ⭐
BLAKE3Very Fast128-bit collisionModernHigh performance
SHA-512Fast (64-bit)256-bit collisionNIST/FIPSHigh security
SHA-3Medium128-bit collisionNIST/FIPSDiversity
SHA-1FastBROKENDeprecatedNEVER use

Exceptions

None thrown under normal operation. Memory allocation failures may throw std::bad_alloc.

See Also