Checksums and integrity
Verify Lockwell object integrity end to end with CRC32, CRC32C, CRC64NVME, SHA1, and SHA256 digests on single, multipart, and edge-runtime writes.
Lockwell verifies object integrity end to end with five algorithms: CRC32, CRC32C, CRC64NVME, SHA1, and
SHA256. The flow is the same for all of them:
- The SDK computes the digest over the exact bytes you upload and sends it on the request.
- The server recomputes it over the bytes it received and rejects a mismatch before anything is committed.
- The verified digest is stored and echoed back, so later reads return it for a downstream consumer to re-check.
Pick an algorithm by need. CRC64NVME is the modern default: fast and covering the full object. Use SHA256 when you
need a cryptographic digest, for an external attestation, a content-addressed lookup, or a regulatory requirement. The
CRC variants are cheaper on the CPU and are the right pick for throughput.
Put an object with a verified checksum
The native client takes checksums as a map of algorithm name to a precomputed base64 digest. Compute the digest yourself (or with the helpers below) and pass it. The server verifies it before any bytes are committed, then echoes it back on the result.
import { NativeClient, computeChecksumBase64 } from "@kelphect/sdk";
const native = new NativeClient({
endpoint: "https://objects.example.com",
accessKeyId: process.env.LOCKWELL_ACCESS_KEY_ID,
secretKey: process.env.LOCKWELL_SECRET_KEY,
});
const body = "hello world";
const res = await native.putObject("reports", "q1/summary.txt", body, {
checksums: { crc64nvme: computeChecksumBase64("CRC64NVME", body) },
});
console.log(res.checksums.crc64nvme); // the server-verified base64 digestThe S3 client takes a checksum algorithm as a put option and hashes the buffered body for you, sending
x-amz-checksum-<alg>:
const res = await s3.putObject("reports", "q1/summary.txt", "hello world", {
checksumAlgorithm: "CRC64NVME",
});
console.log(res.checksums.crc64nvme); // the server-verified base64 digestIf the body is corrupted in transit, the server's recomputed digest will not match what was sent and the write fails
with a BadDigest error (status 400). The object is never written.
Compute a digest without uploading
The SDKs export the hashing helpers, so you can precompute a digest (for a manifest, a dedup probe, or to compare
against a returned value) without a network call. The output is byte-for-byte identical to what the server stores, and
it feeds the native checksums map directly.
import { computeChecksumBase64, checksumHeaderName, CHECKSUM_ALGORITHMS } from "@kelphect/sdk";
const digest = computeChecksumBase64("SHA256", "hello world");
const header = checksumHeaderName("SHA256"); // "x-amz-checksum-sha256"
console.log(CHECKSUM_ALGORITHMS); // ['CRC32','CRC32C','CRC64NVME','SHA1','SHA256']The Go SDK computes and sends the digest internally when you pass WithChecksumAlgorithm on the S3 client, so there is
no exported helper to call by hand on that path. On the native path, compute the digest with the standard library (as in
the Go example above) and pass it in the Checksums map.
The Checksums response shape
Native object reads surface the recorded digests as X-Lockwell-Checksum-<alg> headers, parsed into a Checksums map
keyed by lowercase algorithm. Read the entry for the algorithm the object was written with.
const got = await native.getObject("reports", "q1/summary.txt");
if (got.checksums.crc64nvme) {
// re-verify downstream if you want to: hash got.body yourself and compare
}The S3 client returns a Checksums value with one field per algorithm. Only the field for the algorithm the object was
written with is populated; the rest are empty strings. The value is base64-encoded, matching the x-amz-checksum-<alg>
wire value.
| Field (Go / Java) | Field (Node) | Wire header |
|---|---|---|
CRC32 | crc32 | x-amz-checksum-crc32 |
CRC32C | crc32c | x-amz-checksum-crc32c |
CRC64NVME | crc64nvme | x-amz-checksum-crc64nvme |
SHA1 | sha1 | x-amz-checksum-sha1 |
SHA256 | sha256 | x-amz-checksum-sha256 |
PutObject, GetObject, HeadObject, UploadPart, and CompleteMultipartUpload all carry a Checksums value on the
S3 client.
const got = await s3.getObject("reports", "q1/summary.txt");
if (got.checksums.crc64nvme) {
// re-verify downstream if you want to
}Per-part checksums on multipart uploads
A multipart upload checksums each part on the way up, then folds the parts into one composite checksum on the assembled
object. On the native path, supply the per-part digest in the checksums map on every uploadPart, and read the
composite off the object after completion.
const mpu = await native.createMultipartUpload("reports", "big.bin", {
contentType: "application/octet-stream",
});
const parts = [];
for (let n = 1; n <= chunks.length; n++) {
const p = await native.uploadPart("reports", "big.bin", mpu.uploadId, n, chunks[n - 1], {
checksums: { crc32c: computeChecksumBase64("CRC32C", chunks[n - 1]) },
});
parts.push({ partNumber: n, etag: p.etag });
}
const done = await native.completeMultipartUpload("reports", "big.bin", mpu.uploadId, parts);
const head = await native.headObject("reports", "big.bin");
console.log(head.checksums.crc32c); // the composite checksum of the whole objectThe S3 client declares the algorithm at CreateMultipartUpload, echoes it back, and you pass the echoed algorithm to
every UploadPart. The composite lands on CompleteMultipartUpload:
const mpu = await s3.createMultipartUpload("reports", "big.bin", {
checksumAlgorithm: "CRC32C",
});
const parts = [];
for (let n = 1; n <= chunks.length; n++) {
const p = await s3.uploadPart("reports", "big.bin", mpu.uploadId, n, chunks[n - 1], {
checksumAlgorithm: mpu.checksumAlgorithm, // each part carries a verified digest
});
parts.push({ partNumber: n, etag: p.etag });
}
const done = await s3.completeMultipartUpload("reports", "big.bin", mpu.uploadId, parts);
console.log(done.checksums.crc32c); // the composite checksum of the whole objectThe per-part digest is sent as a direct x-amz-checksum-<alg> header (S3) or X-Lockwell-Checksum-<alg> header
(native) on each part, so each part is validated end to end and folded into the composite. See
multipart uploads for the full lifecycle.
The WebCrypto SHA-256 helper for edge runtimes
The Node S3 client and the hashing helpers in checksum.js import node:crypto, so they do not bundle for Cloudflare
Workers, Vercel Edge, Bun, or Deno. The edge-safe entry point exports a single helper, sha256ChecksumBase64, built on
the web-standard crypto.subtle so it runs with no Node dependency. It pairs with the native client, which is
edge-compatible.
On the edge, reach for `@kelphect/sdk/edge` and the native client. The S3 client and the full hashing helpers
depend on node:crypto and will not bundle there.
import { NativeClient, sha256ChecksumBase64 } from "@kelphect/sdk/edge";
const native = new NativeClient({
endpoint: env.LOCKWELL_ENDPOINT,
accessKeyId: env.LOCKWELL_ACCESS_KEY_ID,
secretKey: env.LOCKWELL_SECRET_KEY,
});
const body = "hello from the edge";
await native.putObject("reports", "edge.txt", body, {
checksums: { sha256: await sha256ChecksumBase64(body) },
});sha256ChecksumBase64 accepts a string, Uint8Array, or ArrayBuffer and returns the base64 digest as a promise. It
covers the common SHA-256 case on the web path. For CRC32C or CRC64NVME on the edge, precompute the digest and pass
it as a string. See edge runtimes for what else is edge-safe.
When to use which
| Need | Algorithm |
|---|---|
| Default, fast, full-object integrity | CRC64NVME |
| Cheapest CRC, broad tooling parity | CRC32 |
| CRC with hardware acceleration on most CPUs | CRC32C |
| Cryptographic digest (attestation, content addressing, regulated data) | SHA256 |
Legacy SHA1 parity with an external system | SHA1 |
Checksums pair naturally with idempotency on the native path: a streaming PUT is never buffered, so the server uses a supplied checksum to confirm a retried write is the same payload. See conditional writes and idempotency.
Related
- Multipart uploads for per-part and composite checksums.
- Errors and retries for the
BadDigestfailure path. - S3 operations reference for the full operation matrix.
The app kit
LockwellKit composes the admin and native clients so an app can provision tenants, get per-tenant clients, configure browser CORS, sign browser URLs, and verify webhooks with almost no glue.
Client configuration
Configure Lockwell SDK endpoints, regions, TLS trust, timeouts, retries, cancellation, concurrency, progress, and response correlation in Go, Node, and Java.