SHA-3: Deep Dive
SHA-3 is the odd one out in the SHA family. Unlike SHA-1 and SHA-2 (which share the same NSA-designed Merkle-Damgård structure), SHA-3 was selected through a public competition and uses a completely different design called Keccak. This post explores the entire SHA-3 family, including its unique extendable-output functions (XOFs).
The SHA-3 Family
SHA-3 includes four fixed-output hash functions and two extendable-output functions (XOFs):
| Algorithm | Output Size | Security Level | Type |
|---|---|---|---|
| SHA3-224 | 224 bits | 112 bits | Fixed output |
| SHA3-256 | 256 bits | 128 bits | Fixed output |
| SHA3-384 | 384 bits | 192 bits | Fixed output |
| SHA3-512 | 512 bits | 256 bits | Fixed output |
| SHAKE128 | Variable | 128 bits | XOF |
| SHAKE256 | Variable | 256 bits | XOF |
XOF (Extendable-Output Function): Unlike traditional hash functions that produce fixed-size output, XOFs can produce output of any desired length.
History: The SHA-3 Competition
Unlike SHA-1 and SHA-2 (designed by the NSA), SHA-3 emerged from a public competition:
Timeline
2004 — Wang breaks MD5, weakens SHA-1
2007 — NIST announces SHA-3 competition
2008 — 64 submissions received
2009 — Round 1: 51 candidates selected
2010 — Round 2: 14 candidates selected
2011 — Round 3: 5 finalists
2012 — Keccak selected as SHA-3 winner
2015 — FIPS 202 published (official SHA-3 standard)
The Five Finalists
| Algorithm | Designer | Notes |
|---|---|---|
| Keccak | Bertoni, Daemen, Peeters, Van Assche | Winner — Sponge construction |
| BLAKE | Aumasson, Henzen, Meier, Phan | Very fast, later became BLAKE2/BLAKE3 |
| Grøstl | Knudsen et al. | AES-based design |
| JH | Wu | Novel structure |
| Skein | Schneier, Ferguson et al. | Threefish block cipher based |
Why Keccak won:
- Completely different design from SHA-2 (diversity = backup if SHA-2 breaks)
- Strong security margins
- Elegant mathematical structure
- Efficient in both hardware and software
- Built-in resistance to length extension attacks
The Keccak Sponge Construction
SHA-3's most distinctive feature is its sponge construction, fundamentally different from the Merkle-Damgård design used by SHA-1/SHA-2.
Sponge vs Merkle-Damgård
Merkle-Damgård (SHA-2):
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Block 1 │──►│ Block 2 │──►│ Block 3 │──► Hash
└─────────┘ └─────────┘ └─────────┘
Chain the compression function
Sponge (SHA-3):
┌──────── ABSORB ────────┐ ┌─── SQUEEZE ───┐
│ │ │ │
Input ──┼──►[f]──►[f]──►[f]──►───┼──┼──►[f]──►[f]───┼──► Output
│ ▲ ▲ ▲ │ │ │ │ │
│ │ │ │ │ │ ▼ ▼ │
│ M[0] M[1] M[2] │ │ Out Out │
└────────────────────────┘ └───────────────┘
The sponge works in two phases:
- Absorbing: Input is XORed into the state, then permuted
- Squeezing: Output is extracted from the state, with permutations between extractions
The State and Parameters
The Keccak state is a 1600-bit array organized as a 5×5×64 three-dimensional structure:
y
▲
4 │ □ □ □ □ □
3 │ □ □ □ □ □
2 │ □ □ □ □ □
1 │ □ □ □ □ □
0 │ □ □ □ □ □
└──────────► x
0 1 2 3 4
Each □ = 64 bits (a "lane")
Total = 5 × 5 × 64 = 1600 bits
The state is divided into two parts:
- Rate (r): The portion that absorbs input and produces output
- Capacity (c): The "hidden" portion that provides security
State (1600 bits) = Rate (r) + Capacity (c)
| Algorithm | Rate (r) | Capacity (c) | Security |
|---|---|---|---|
| SHA3-224 | 1152 bits | 448 bits | 112 bits |
| SHA3-256 | 1088 bits | 512 bits | 128 bits |
| SHA3-384 | 832 bits | 768 bits | 192 bits |
| SHA3-512 | 576 bits | 1024 bits | 256 bits |
| SHAKE128 | 1344 bits | 256 bits | 128 bits |
| SHAKE256 | 1088 bits | 512 bits | 256 bits |
Security level = capacity / 2 (for collision resistance)
The Keccak-f Permutation
The heart of SHA-3 is the Keccak-f[1600] permutation, consisting of 24 rounds. Each round applies five operations:
θ (Theta): Column parity mixing
For each column, XOR with the parity of adjacent columns
Provides diffusion across the x-axis
ρ (Rho): Bitwise rotation
Rotate each lane by a different offset (0 to 63 bits)
Provides diffusion along the z-axis (within lanes)
π (Pi): Lane permutation
Rearrange lanes: (x,y) → (y, 2x+3y)
Provides diffusion across x and y axes
χ (Chi): Nonlinear operation
a[x] = a[x] XOR (NOT a[x+1] AND a[x+2])
The only nonlinear step — provides confusion
ι (Iota): Round constant addition
XOR a round constant into lane (0,0)
Breaks symmetry, prevents slide attacks
After 24 rounds of these five operations, the state is thoroughly mixed.
The SHA-3 Algorithms
SHA3-256
Output: 256 bits (32 bytes, 64 hex characters)
Rate: 1088 bits
Capacity: 512 bits
SHA3-256("Hello, World!") = 1af17a664e3fa8e419b8ba05c2a173169df76162a5a286e0c405b460d478f7ef
SHA3-256 is the direct counterpart to SHA-256. Same output size, same security level, completely different internals.
When to use: When you need SHA-256 security but want a backup algorithm with a different design, or when length extension resistance matters.
SHA3-224
Output: 224 bits (28 bytes, 56 hex characters)
Rate: 1152 bits
Capacity: 448 bits
SHA3-224("Hello, World!") = 853048fb8b11462b6100385633c0cc8dcdc6e2b8e376c28102bc84f2
When to use: Rarely. Use when you specifically need 224-bit output with SHA-3's properties.
SHA3-512
Output: 512 bits (64 bytes, 128 hex characters)
Rate: 576 bits
Capacity: 1024 bits
SHA3-512("Hello, World!") = 38e05c33d7b067127f217d8c856e554fcff09c9320b8a5979ce2ff5d95dd27ba35d1fba50c562dfd1d6cc48bc9c5baa4390894418cc942d968f97bcb659419ed
When to use: Maximum security requirements with SHA-3.
SHA3-384
Output: 384 bits (48 bytes, 96 hex characters)
Rate: 832 bits
Capacity: 768 bits
SHA3-384("Hello, World!") = aa9ad8a49f31d2ddcabbb7010a1566417cff803fef50eba239558826f872e468c5743e7f026b0a8e5b2d7a1cc465cdbe
When to use: When 192-bit security is required with SHA-3.
SHAKE: Extendable-Output Functions
SHAKE128 and SHAKE256 are XOFs (Extendable-Output Functions)—they can produce output of any length you specify.
SHAKE128
Security: 128 bits
Rate: 1344 bits
Output: Variable (you choose)
# Python example: Get 32 bytes from SHAKE128
import hashlib
shake = hashlib.shake_128(b"Hello, World!")
output = shake.hexdigest(32) # 32 bytes = 64 hex chars
SHAKE256
Security: 256 bits
Rate: 1088 bits
Output: Variable (you choose)
# Python example: Get 64 bytes from SHAKE256
import hashlib
shake = hashlib.shake_256(b"Hello, World!")
output = shake.hexdigest(64) # 64 bytes = 128 hex chars
Why XOFs Matter
Traditional hash functions produce fixed output:
- Need 128 bits? Use SHA-256 and truncate (wastes computation)
- Need 1024 bits? Hash multiple times with counters (awkward)
XOFs solve this elegantly:
# Need exactly 100 bytes of output? Easy.
output = hashlib.shake_256(b"seed").digest(100)
# Need 1000 bytes? Also easy.
long_output = hashlib.shake_256(b"seed").digest(1000)
Use cases for XOFs:
- Key derivation: Generate keys of exact required length
- Mask generation: For OAEP padding in RSA
- Stream cipher: XOF output XORed with plaintext
- Deterministic random: Reproducible random bytes from a seed
SHA-3 vs SHA-2: When to Use Which
Key Differences
| Property | SHA-2 | SHA-3 |
|---|---|---|
| Design | Merkle-Damgård | Sponge |
| Origin | NSA | Public competition |
| Length extension | Vulnerable | Resistant |
| Parallelizable | No | No |
| Hardware efficiency | Good | Excellent |
| Software speed | Fast | Moderate |
| XOF variants | No | Yes (SHAKE) |
| Age | 24+ years | 10+ years |
When to Use SHA-3
-
Length extension attacks are a concern
- SHA-3 is inherently immune
- No need for HMAC construction in some cases
-
Algorithm diversity required
- Backup if SHA-2 is ever broken
- Defense in depth
-
Variable output needed
- SHAKE128/SHAKE256 provide any output length
-
Hardware implementation
- SHA-3 is very efficient in hardware
- Good for FPGAs and ASICs
When to Use SHA-2
-
Compatibility
- SHA-256 is everywhere
- Better library/tool support
-
Software performance
- SHA-256 is faster in pure software
- Especially with CPU extensions (SHA-NI)
-
Proven track record
- 24+ years of cryptanalysis
- No significant weaknesses found
-
Standards compliance
- Many standards specify SHA-256
- Changing requires compliance updates
The "Paranoid" Approach
Some high-security applications use both:
final_hash = SHA3-256(SHA-256(data))
If either algorithm breaks, the combination remains secure. This is overkill for most applications but demonstrates SHA-3's role as a backup.
Security Analysis
Theoretical Security
| Algorithm | Collision | Preimage | Second Preimage |
|---|---|---|---|
| SHA3-224 | 2^112 | 2^224 | 2^224 |
| SHA3-256 | 2^128 | 2^256 | 2^256 |
| SHA3-384 | 2^192 | 2^384 | 2^384 |
| SHA3-512 | 2^256 | 2^512 | 2^512 |
| SHAKE128 | 2^min(d/2, 128) | 2^min(d, 128) | 2^min(d, 128) |
| SHAKE256 | 2^min(d/2, 256) | 2^min(d, 256) | 2^min(d, 256) |
(For SHAKE, d = output length in bits)
Known Attacks
As of 2025, no practical attacks exist against SHA-3. The best known results:
- Reduced-round attacks (up to 8 of 24 rounds for some properties)
- Theoretical improvements that don't threaten the full algorithm
The 24 rounds provide a substantial security margin.
Quantum Resistance
Like SHA-2, SHA-3 is affected by quantum computers:
- Preimage: Grover's algorithm provides quadratic speedup (2^n → 2^(n/2))
- Collision: Less affected by quantum algorithms
SHA3-256 with 128-bit collision resistance becomes ~64-bit against quantum collision attacks—still requiring significant quantum resources.
Code Examples
Node.js
Requirements: Node.js 10+ for SHA3, Node.js 12.8+ for SHAKE with outputLength option (PR #28805)
const crypto = require('crypto');
const message = 'Hello, World!';
// ============ Fixed-Output Hash Functions ============
// SHA3-224
const sha3_224 = crypto.createHash('sha3-224')
.update(message)
.digest('hex');
console.log(`SHA3-224: ${sha3_224}`);
// SHA3-256
const sha3_256 = crypto.createHash('sha3-256')
.update(message)
.digest('hex');
console.log(`SHA3-256: ${sha3_256}`);
// SHA3-384
const sha3_384 = crypto.createHash('sha3-384')
.update(message)
.digest('hex');
console.log(`SHA3-384: ${sha3_384}`);
// SHA3-512
const sha3_512 = crypto.createHash('sha3-512')
.update(message)
.digest('hex');
console.log(`SHA3-512: ${sha3_512}`);
// ============ Extendable-Output Functions (XOFs) ============
// SHAKE128 - 32 bytes output
const shake128_32 = crypto.createHash('shake128', { outputLength: 32 })
.update(message)
.digest('hex');
console.log(`SHAKE128 (32 bytes): ${shake128_32}`);
// SHAKE128 - 64 bytes output
const shake128_64 = crypto.createHash('shake128', { outputLength: 64 })
.update(message)
.digest('hex');
console.log(`SHAKE128 (64 bytes): ${shake128_64}`);
// SHAKE256 - 32 bytes output
const shake256_32 = crypto.createHash('shake256', { outputLength: 32 })
.update(message)
.digest('hex');
console.log(`SHAKE256 (32 bytes): ${shake256_32}`);
// SHAKE256 - 64 bytes output
const shake256_64 = crypto.createHash('shake256', { outputLength: 64 })
.update(message)
.digest('hex');
console.log(`SHAKE256 (64 bytes): ${shake256_64}`);
// ============ Practical Example: Key Derivation ============
function deriveKey(password, salt, keyLength) {
return crypto.createHash('shake256', { outputLength: keyLength })
.update(password + salt)
.digest();
}
const derivedKey = deriveKey('my_password', 'random_salt', 32);
console.log(`Derived key: ${derivedKey.toString('hex')}`);
C# (.NET)
Requirements: .NET 8+ for native SHA3 support (SHA3_256, Shake128, Shake256). For earlier .NET versions, use BouncyCastle.
using System;
using System.Security.Cryptography;
using System.Text;
class SHA3Examples
{
static void Main()
{
string message = "Hello, World!";
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
// ============ Fixed-Output Hash Functions ============
// SHA3-256 (.NET 8+)
using (var sha3_256 = SHA3_256.Create())
{
byte[] hash = sha3_256.ComputeHash(messageBytes);
Console.WriteLine($"SHA3-256: {Convert.ToHexString(hash).ToLower()}");
}
// SHA3-384 (.NET 8+)
using (var sha3_384 = SHA3_384.Create())
{
byte[] hash = sha3_384.ComputeHash(messageBytes);
Console.WriteLine($"SHA3-384: {Convert.ToHexString(hash).ToLower()}");
}
// SHA3-512 (.NET 8+)
using (var sha3_512 = SHA3_512.Create())
{
byte[] hash = sha3_512.ComputeHash(messageBytes);
Console.WriteLine($"SHA3-512: {Convert.ToHexString(hash).ToLower()}");
}
// ============ Extendable-Output Functions (XOFs) ============
// SHAKE128 (.NET 8+)
byte[] shake128Output = new byte[32];
Shake128.HashData(messageBytes, shake128Output);
Console.WriteLine($"SHAKE128 (32 bytes): {Convert.ToHexString(shake128Output).ToLower()}");
// SHAKE128 - 64 bytes
byte[] shake128Output64 = new byte[64];
Shake128.HashData(messageBytes, shake128Output64);
Console.WriteLine($"SHAKE128 (64 bytes): {Convert.ToHexString(shake128Output64).ToLower()}");
// SHAKE256
byte[] shake256Output = new byte[32];
Shake256.HashData(messageBytes, shake256Output);
Console.WriteLine($"SHAKE256 (32 bytes): {Convert.ToHexString(shake256Output).ToLower()}");
// SHAKE256 - 64 bytes
byte[] shake256Output64 = new byte[64];
Shake256.HashData(messageBytes, shake256Output64);
Console.WriteLine($"SHAKE256 (64 bytes): {Convert.ToHexString(shake256Output64).ToLower()}");
}
// ============ Practical Example: Key Derivation ============
static byte[] DeriveKey(string password, string salt, int keyLength)
{
byte[] input = Encoding.UTF8.GetBytes(password + salt);
byte[] key = new byte[keyLength];
Shake256.HashData(input, key);
return key;
}
}
Note: SHA-3 support in .NET requires .NET 8 or later. For earlier versions, use a library like BouncyCastle:
// Using BouncyCastle for .NET Framework / .NET Core < 8
using Org.BouncyCastle.Crypto.Digests;
var sha3 = new Sha3Digest(256); // SHA3-256
sha3.BlockUpdate(messageBytes, 0, messageBytes.Length);
byte[] hash = new byte[sha3.GetDigestSize()];
sha3.DoFinal(hash, 0);
Go (Golang)
Requirements: Go 1.4+ with the golang.org/x/crypto/sha3 package (pkg.go.dev/golang.org/x/crypto/sha3). Install with go get golang.org/x/crypto/sha3.
package main
import (
"encoding/hex"
"fmt"
"golang.org/x/crypto/sha3"
)
func main() {
message := []byte("Hello, World!")
// ============ Fixed-Output Hash Functions ============
// SHA3-224
hash224 := sha3.Sum224(message)
fmt.Printf("SHA3-224: %s\n", hex.EncodeToString(hash224[:]))
// SHA3-256
hash256 := sha3.Sum256(message)
fmt.Printf("SHA3-256: %s\n", hex.EncodeToString(hash256[:]))
// SHA3-384
hash384 := sha3.Sum384(message)
fmt.Printf("SHA3-384: %s\n", hex.EncodeToString(hash384[:]))
// SHA3-512
hash512 := sha3.Sum512(message)
fmt.Printf("SHA3-512: %s\n", hex.EncodeToString(hash512[:]))
// ============ Using the Hash Interface ============
// Useful for streaming/incremental hashing
hasher := sha3.New256()
hasher.Write(message)
result := hasher.Sum(nil)
fmt.Printf("SHA3-256 (incremental): %s\n", hex.EncodeToString(result))
// ============ Extendable-Output Functions (XOFs) ============
// SHAKE128 - 32 bytes output
shake128_32 := make([]byte, 32)
sha3.ShakeSum128(shake128_32, message)
fmt.Printf("SHAKE128 (32 bytes): %s\n", hex.EncodeToString(shake128_32))
// SHAKE128 - 64 bytes output
shake128_64 := make([]byte, 64)
sha3.ShakeSum128(shake128_64, message)
fmt.Printf("SHAKE128 (64 bytes): %s\n", hex.EncodeToString(shake128_64))
// SHAKE256 - 32 bytes output
shake256_32 := make([]byte, 32)
sha3.ShakeSum256(shake256_32, message)
fmt.Printf("SHAKE256 (32 bytes): %s\n", hex.EncodeToString(shake256_32))
// SHAKE256 - 64 bytes output
shake256_64 := make([]byte, 64)
sha3.ShakeSum256(shake256_64, message)
fmt.Printf("SHAKE256 (64 bytes): %s\n", hex.EncodeToString(shake256_64))
// ============ Using SHAKE with io.Reader Interface ============
// Useful for generating arbitrary amounts of output
shakeReader := sha3.NewShake256()
shakeReader.Write(message)
// Read any amount of bytes
output := make([]byte, 100)
shakeReader.Read(output)
fmt.Printf("SHAKE256 (100 bytes): %s\n", hex.EncodeToString(output))
// ============ Practical Example: Key Derivation ============
key := deriveKey("my_password", "random_salt", 32)
fmt.Printf("Derived key: %s\n", hex.EncodeToString(key))
}
func deriveKey(password, salt string, keyLength int) []byte {
input := []byte(password + salt)
key := make([]byte, keyLength)
sha3.ShakeSum256(key, input)
return key
}
Python
Requirements: Python 3.6+ (hashlib SHA-3 support added in 3.6)
import hashlib
message = b"Hello, World!"
# ============ Fixed-Output Hash Functions ============
print(f"SHA3-224: {hashlib.sha3_224(message).hexdigest()}")
print(f"SHA3-256: {hashlib.sha3_256(message).hexdigest()}")
print(f"SHA3-384: {hashlib.sha3_384(message).hexdigest()}")
print(f"SHA3-512: {hashlib.sha3_512(message).hexdigest()}")
# ============ Extendable-Output Functions (XOFs) ============
print(f"SHAKE128 (32 bytes): {hashlib.shake_128(message).hexdigest(32)}")
print(f"SHAKE128 (64 bytes): {hashlib.shake_128(message).hexdigest(64)}")
print(f"SHAKE256 (32 bytes): {hashlib.shake_256(message).hexdigest(32)}")
print(f"SHAKE256 (64 bytes): {hashlib.shake_256(message).hexdigest(64)}")
# ============ Practical Example: Key Derivation ============
def derive_key(password: str, salt: bytes, key_length: int) -> bytes:
"""Derive a key of exact length using SHAKE256"""
data = password.encode() + salt
return hashlib.shake_256(data).digest(key_length)
key = derive_key("my_password", b"random_salt", 32)
print(f"Derived key: {key.hex()}")
Command Line (OpenSSL)
Requirements: OpenSSL 1.1.1+ for SHA3 (release notes), OpenSSL 3.0+ for SHAKE with -xoflen option (openssl-dgst manpage)
# Using OpenSSL (1.1.1+ for SHA3, 3.0+ for SHAKE)
echo -n "Hello, World!" | openssl dgst -sha3-224
echo -n "Hello, World!" | openssl dgst -sha3-256
echo -n "Hello, World!" | openssl dgst -sha3-384
echo -n "Hello, World!" | openssl dgst -sha3-512
# SHAKE with variable output length
echo -n "Hello, World!" | openssl dgst -shake128 -xoflen 32
echo -n "Hello, World!" | openssl dgst -shake256 -xoflen 64
# Using rhash
echo -n "Hello, World!" | rhash --sha3-256 -
Real-World Usage
Current Adoption
SHA-3 adoption is growing but still trails SHA-2:
| Application | SHA-2 | SHA-3 |
|---|---|---|
| TLS/SSL | Default | Supported |
| Code signing | Standard | Rare |
| Cryptocurrencies | Bitcoin, Ethereum | Some newer chains |
| Password hashing | Not recommended | Not recommended |
| File integrity | Common | Growing |
Where SHA-3 Shines
-
EdDSA (Ed448)
- Uses SHAKE256 internally
- High-security digital signatures
-
Post-quantum cryptography
- Several NIST PQC algorithms use SHA-3/SHAKE
- CRYSTALS-Kyber, CRYSTALS-Dilithium use SHAKE
-
Hardware security modules
- SHA-3's efficient hardware implementation
- Smart cards, secure elements
-
Ethereum 2.0
- Uses SHA-256 and SHA3-256 (Keccak-256 variant)
Keccak vs SHA-3: A Note on Ethereum
Ethereum uses "Keccak-256," which is not identical to SHA3-256:
Keccak-256("") = c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
SHA3-256("") = a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a
The difference: NIST added domain separation padding when standardizing SHA-3. Ethereum adopted Keccak before this standardization, so it uses the original padding.
Bottom line: When working with Ethereum, use Keccak-256, not SHA3-256.
Conclusion
SHA-3 represents a fundamental shift in hash function design. Its sponge construction, XOF capability, and inherent length extension resistance make it a powerful tool—and a crucial backup if SHA-2 ever falls.
Quick recommendations:
- Default choice: SHA-256 (compatibility and speed)
- Length extension concerns: SHA3-256 or HMAC-SHA256
- Variable output needed: SHAKE128 or SHAKE256
- Maximum security: SHA3-512 or SHA-512
- Post-quantum preparation: SHAKE256 (used in PQC algorithms)
SHA-3 may not have replaced SHA-2 as the default, but it has earned its place as the essential backup and the foundation for next-generation cryptography.
References
Standards and Theory
- NIST FIPS 202. SHA-3 Standard: Permutation-Based Hash and Extendable-Output Functions
- Bertoni, G., et al. The Keccak Reference
- Bertoni, G., et al. Sponge Functions
- NIST. SHA-3 Competition
- Ethereum Yellow Paper. Keccak-256 specification
Implementation Documentation
- Node.js. Crypto module - Hash class
- Node.js. PR #28805 - Add outputLength option for XOF hash functions
- Microsoft .NET. SHA3_256 Class
- Microsoft .NET. Shake128 Class
- Microsoft .NET. Shake256 Class
- Go. golang.org/x/crypto/sha3 package
- Python. hashlib — Secure hashes and message digests
- OpenSSL. OpenSSL 1.1.1 Release Notes
- OpenSSL. openssl-dgst Manual Page
- BouncyCastle. C# Cryptography APIs