Whole-device SSD integrity test: writes AES-CTR pseudorandom data, hashes in parallel, reads back and verifies — N runs. Wipes existing data.
WARNING: This tool overwrites everything on the target device — partition tables, filesystems, all of it. There is no recovery. Read this README before running it.
sudo ./ssd-verify /dev/disk/by-id/YOUR_DEVICE_IDRuns 10 verification passes with 1 MiB blocks (defaults). The script prompts for explicit yes/YES confirmation before any writes. See Usage for arguments and Recommended workflow for picking the device path safely.
For each of N iterations:
- Generate a fresh AES-256 key + IV.
- Encrypt a stream of zeros with AES-256-CTR — producing a deterministic but unpredictable pseudorandom byte stream.
- Write the stream to the entire raw block device via
dd, while in parallel computing a hash of the same stream as it flows by (viateeinto a FIFO). - Sync, flush kernel buffers, drop page cache.
- Read the entire device back and hash the result.
- Compare the two hashes. PASS only if identical.
After N runs, report the count and exit non-zero on any failure.
Write path:
/dev/zero
-> openssl AES-256-CTR pseudorandom stream
-> tee
-> FIFO -> dd -> block device
-> hash (expected)
Read-back path:
block device
-> dd
-> hash (actual)
A run passes only if expected == actual.
Why AES-CTR for the payload? Modern flash controllers compress and dedupe internally. Writing zeros or constant patterns lets a fake or defective drive cheat — the controller may store one block and lie about the rest. AES-CTR output is statistically indistinguishable from random, so the controller can't compress or dedupe it; every byte has to be stored faithfully. The script does not need cryptographic secrecy — AES-CTR is just a fast pseudorandom stream generator.
Why hash the source stream, not regenerate it for comparison? The expected hash is computed from the bytes that actually flowed through tee on their way to the disk. If anything goes wrong in the generation pipeline (OOM, signal, partial pipe), the expected hash reflects what was really sent, not what should have been sent.
Why rotating keys per iteration? Marginal bits can fail probabilistically depending on the bit pattern written. Using a different key each run varies the pattern and improves coverage. Fake-capacity drives fail on run 1 with any data; defective cells may need several runs to surface.
Why drop page cache before read-back? Without it, the read pass can hit Linux's cache and return what was intended to be written, not what's actually on NAND. conv=fsync + sync + blockdev --flushbufs + drop_caches ensures the comparison reads from the device, not from memory.
What this is NOT. This is not a power-loss persistence test. A drive with a volatile internal write cache could still pass this script and lose data on sudden power-off. For that, run this script, power-cycle the drive cold, and then do a read-only hash pass.
- Linux (tested on Ubuntu 24.04)
bash,dd,openssl,lsblk,blockdev,awk,cut- Root
- A block device you are willing to overwrite completely
sudo ./ssd-verify /dev/sdX [runs] [blocksize_MiB]Defaults: 10 runs, 1 MiB block size.
Use a stable device path. A suspicious or flaky drive may disconnect and reappear with a different letter (sda → sdb), and you do not want to clobber the wrong disk. Pick the path from /dev/disk/by-id/:
sudo ./ssd-verify /dev/disk/by-id/ata-Vendor_Model_SerialNumber 10 1 2>&1 | tee ssd-verify.logThe log is useful for disputes — every run records its random key and IV, so the test is reproducible and inspectable after the fact.
Device /dev/sda Size 128035676160 bytes (122104 MiB) Runs 10 Block 1024 KiB Hash sha256
=== Run 1/10 key=... iv=... ===
128035676160 bytes (128 GB, 119 GiB) copied, 598 s, 214 MB/s
128035676160 bytes (128 GB, 119 GiB) copied, 306 s, 418 MB/s
expect a3f1...
actual a3f1... PASS
=== Run 2/10 key=... iv=... ===
...
=== 10/10 runs passed ===
A failed run looks like:
expect 8e21...
actual 4c92... FAIL
Exit code is 0 on a full pass, non-zero if any run failed.
Preflight checks run before any byte is written. The script aborts if any of them fail:
- Block-device validation (refuses to write to a regular file or non-existent path).
- Root check.
- Input validation on
runsandblocksize_MiB. - Hash algorithm validation — catches typos in the
HASH=tunable. - Mount detection — lists any mounted partitions on the target and prompts to unmount before proceeding.
- Swap detection — refuses to run if the target (or any of its partitions) is active swap. Comparison is by canonical device path (not substring), so
sdawill not false-matchsda1. - Two separate
yes/YESconfirmation prompts (one for unmounting, one before the writes start). - Trap handler that kills the background writer on
Ctrl+Cor unexpected exit.
After each write phase, before reading back:
sync
blockdev --flushbufs "$D"
echo 3 > /proc/sys/vm/drop_cachesThis prevents a false PASS caused by Linux serving the read from page cache instead of from the device. See Why this design for the rationale.
- 1–2 runs: Catches all fake-capacity drives and most defective ones.
- 5 runs: Solid for typical due-diligence on a used or suspicious drive.
- 10 runs (default): Covers probabilistic bit failures with comfortable margin.
- 15–20 runs: Paranoid / endurance burn-in. Note this is also 15–20× the drive's capacity in writes — a stress test in its own right.
List devices:
lsblk -o NAME,PATH,SIZE,TYPE,FSTYPE,MOUNTPOINTS,MODEL,SERIALFind stable device IDs:
ls -l /dev/disk/by-id/Run a short pass first, logging output:
sudo ./ssd-verify /dev/disk/by-id/YOUR_DEVICE_ID 1 1 2>&1 | tee ssd-verify-run1.logIf it passes and you want more confidence, run a longer pass:
sudo ./ssd-verify /dev/disk/by-id/YOUR_DEVICE_ID 10 1 2>&1 | tee ssd-verify-run2.logAll runs passed. The device returned the same data it was told to store. Good sign, but not a full guarantee of long-term reliability.
One or more runs failed. Possible causes:
- fake-capacity SSD
- defective flash memory
- controller failure
- USB bridge instability
- cable / enclosure problems
- overheating
- power supply instability
- device disconnect during the test
The device disappears mid-test. Also a serious failure. Check the kernel log:
dmesg -TLook for I/O errors, USB resets, disconnects, controller errors, or SATA/NVMe errors.
If you are testing a suspicious SSD for a seller dispute, keep the full terminal log:
sudo ./ssd-verify /dev/disk/by-id/YOUR_DEVICE_ID 2 1 2>&1 | tee ssd-verify.logUseful supporting evidence:
lsblk -o NAME,PATH,SIZE,TYPE,FSTYPE,MOUNTPOINTS,MODEL,SERIAL
dmesg -T
smartctl -a /dev/sdX # if smartmontools is availableThe hash algorithm is a single tunable at the top of the script:
HASH=sha256Measured throughput on SHA-NI-accelerated hardware (16 KiB blocks via openssl speed):
| Algorithm | Throughput | Notes |
|---|---|---|
| sha1 | ≈ 2.31 GB/s | SHA-NI accelerated |
| sha256 | ≈ 2.15 GB/s | SHA-NI; default |
| blake2b512 | ≈ 1.05 GB/s | Software, modern |
| sha512 | ≈ 1.00 GB/s | No SHA-NI for SHA-512 family |
| sha3-256 | ≈ 0.55 GB/s | Software only |
SHA-256 is the default because the small cost over SHA-1 is trivial on accelerated hardware, and SHA-256 carries more weight if the log ends up in a dispute. On CPUs without SHA-NI the trade-off shifts — benchmark your own machine. The bundled helper script profiles every digest your openssl exposes and prints throughput per block size:
./hash_speed_test.sh 3 # 3 seconds per algorithmYou can also benchmark the AES-CTR generation side:
openssl speed -elapsed -seconds 3 -evp aes-256-ctrThe script uses openssl dgst -<alg> -r rather than coreutils sha{1,256}sum because openssl reliably dispatches to SHA-NI; coreutils may or may not, depending on distro build, and can silently become the bottleneck instead of the disk.
ssd-verify tells you whether the device returned the same data after a full write/read cycle. It does not guarantee:
- long-term data retention
- power-loss safety
- wear endurance
- SMART health correctness
- filesystem-level behavior
- performance consistency
- authenticity of the hardware vendor
It is primarily useful for detecting:
- fake-capacity SSDs
- defective flash
- unstable controllers
- drives that disappear during sustained writes
- drives that silently corrupt data
For wear-endurance, use fio or vendor tooling. For SMART health, use smartctl. For bit-level diagnosis of failures, use badblocks -wsv.
f3— fake-flash detector; similar approach but operates on filesystems rather than raw block devices.h2testw— Windows equivalent off3.badblocks— older, simpler four-pattern write-read test.fio— general-purpose I/O benchmark; can be configured for verification.
ssd-verify differs by writing AES-CTR pseudorandom data (uncompressible, undedupable) to the raw device, with parallel source hashing and rotating per-run keys.
MIT — see LICENSE.
Use at your own risk. The author is not responsible for data loss, hardware damage, or accidental overwriting of the wrong device.