Use SSE-C

Last updated Apr 21, 2026 The following tutorial shows some snippets for how to use Server-Side Encryption with Customer-Provided Keys (SSE-C) on R2.

Before you begin

  • When using SSE-C, make sure you store your encryption key(s) in a safe place. In the event you misplace them, Cloudflare will be unable to recover the body of any objects encrypted using those keys.
  • While SSE-C does provide MD5 hashes, this hash can be used for identification of keys only. The MD5 hash is not used in the encryption process itself.

Workers

interface Environment {
    R2: R2Bucket
    /**
     * In this example, your SSE-C is stored as a hexadecimal string (preferably a secret).
     * The R2 API also supports providing an ArrayBuffer directly, if you want to generate/
     * store your keys dynamically.
    */
    SSEC_KEY: string
}
export default {
    async fetch(req: Request, env: Env) {
        const { SSEC_KEY, R2 } = env;
        const { pathname: filename } = new URL(req.url);
        switch(req.method) {
            case "GET": {
                const maybeObj = await env.BUCKET.get(filename, {
                    onlyIf: req.headers,
                    ssecKey: SSEC_KEY,
                });
                if(!maybeObj) {
                    return new Response("Not Found", {
                        status: 404
                    });
                }
                const headers = new Headers();
                maybeObj.writeHttpMetadata(headers);
                return new Response(body, {
                    headers
                });
            }
            case 'POST': {
                const multipartUpload = await env.BUCKET.createMultipartUpload(filename, {
                    httpMetadata: req.headers,
                    ssecKey: SSEC_KEY,
                });
                /**
                 * This example only provides a single-part "multipart" upload.
                 * For multiple parts, the process is the same(the key must be provided)
                 * for every part.
                */
                const partOne = await multipartUpload.uploadPart(1, req.body, ssecKey);
                const obj = await multipartUpload.complete([partOne]);
                const headers = new Headers();
                obj.writeHttpMetadata(headers);
                return new Response(null, {
                    headers,
                    status: 201
                });
            }
            case 'PUT': {
                const obj = await env.BUCKET.put(filename, req.body, {
                    httpMetadata: req.headers,
                    ssecKey: SSEC_KEY,
                });
                const headers = new Headers();
                maybeObj.writeHttpMetadata(headers);
                return new Response(null, {
                    headers,
                    status: 201
                });
            }
            default: {
                return new Response("Method not allowed", {
                    status: 405
                });
            }
        }
    }
}
/**
     * In this example, your SSE-C is stored as a hexadecimal string(preferably a secret).
     * The R2 API also supports providing an ArrayBuffer directly, if you want to generate/
     * store your keys dynamically.
*/
export default {
    async fetch(req, env) {
        const { SSEC_KEY, R2 } = env;
        const { pathname: filename } = new URL(req.url);
        switch(req.method) {
            case "GET": {
                const maybeObj = await env.BUCKET.get(filename, {
                    onlyIf: req.headers,
                    ssecKey: SSEC_KEY,
                });
                if(!maybeObj) {
                    return new Response("Not Found", {
                        status: 404
                    });
                }
                const headers = new Headers();
                maybeObj.writeHttpMetadata(headers);
                return new Response(body, {
                    headers
                });
            }
            case 'POST': {
                const multipartUpload = await env.BUCKET.createMultipartUpload(filename, {
                    httpMetadata: req.headers,
                    ssecKey: SSEC_KEY,
                });
                /**
                 * This example only provides a single-part "multipart" upload.
                 * For multiple parts, the process is the same(the key must be provided)
                 * for every part.
                */
                const partOne = await multipartUpload.uploadPart(1, req.body, ssecKey);
                const obj = await multipartUpload.complete([partOne]);
                const headers = new Headers();
                obj.writeHttpMetadata(headers);
                return new Response(null, {
                    headers,
                    status: 201
                });
            }
            case 'PUT': {
                const obj = await env.BUCKET.put(filename, req.body, {
                    httpMetadata: req.headers,
                    ssecKey: SSEC_KEY,
                });
                const headers = new Headers();
                maybeObj.writeHttpMetadata(headers);
                return new Response(null, {
                    headers,
                    status: 201
                });
            }
            default: {
                return new Response("Method not allowed", {
                    status: 405
                });
            }
        }
    }
}

S3-API

import {
    UploadPartCommand,
    PutObjectCommand, S3Client,
    CompleteMultipartUploadCommand,
    CreateMultipartUploadCommand,
    type UploadPartCommandOutput
} from "@aws-sdk/client-s3";

const s3 = new S3Client({
    endpoint: process.env.R2_ENDPOINT,
    credentials: {
        accessKeyId: process.env.R2_ACCESS_KEY_ID,
        secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
    },
});

const SSECustomerAlgorithm = "AES256";
const SSECustomerKey = process.env.R2_SSEC_KEY;
const SSECustomerKeyMD5 = process.env.R2_SSEC_KEY_MD5;

await s3.send(
    new PutObjectCommand({
        Bucket: "your-bucket",
        Key: "single-part",
        Body: "BeepBoop",
        SSECustomerAlgorithm,
        SSECustomerKey,
        SSECustomerKeyMD5,
    }),
);

const multi = await s3.send(
    new CreateMultipartUploadCommand({
        Bucket: "your-bucket",
        Key: "multi-part",
        SSECustomerAlgorithm,
        SSECustomerKey,
        SSECustomerKeyMD5,
    }),
);
const UploadId = multi.UploadId;

const parts: UploadPartCommandOutput[] = [];

parts.push(
    await s3.send(
        new UploadPartCommand({
            Bucket: "your-bucket",
            Key: "multi-part",
            UploadId,
            //  filledBuf()` generates some random data.
            // Replace with a function/body of your choice.
            Body: filledBuf(),
            PartNumber: 1,
            SSECustomerAlgorithm,
            SSECustomerKey,
            SSECustomerKeyMD5,
        }),
    ),
);
parts.push(
    await s3.send(
        new UploadPartCommand({
            Bucket: "your-bucket",
            Key: "multi-part",
            UploadId,
            //  filledBuf()` generates some random data.
            // Replace with a function/body of your choice.
            Body: filledBuf(),
            PartNumber: 2,
            SSECustomerAlgorithm,
            SSECustomerKey,
            SSECustomerKeyMD5,
        }),
    ),
);
await s3.send(
    new CompleteMultipartUploadCommand({
        Bucket: "your-bucket",
        Key: "multi-part",
        UploadId,
        MultipartUpload: {
            Parts: parts.map(({ ETag }, PartNumber) => ({
                ETag,
                PartNumber: PartNumber + 1,
            })),
        },
        SSECustomerAlgorithm,
        SSECustomerKey,
        SSECustomerKeyMD5,
    }),
);

const HeadObjectOutput = await s3.send(
    new HeadObjectCommand({
        Bucket: "your-bucket",
        Key: "multi-part",
        SSECustomerAlgorithm,
        SSECustomerKey,
        SSECustomerKeyMD5,
    }),
);

const GetObjectOutput = await s3.send(
    new GetObjectCommand({
        Bucket: "your-bucket",
        Key: "single-part",
        SSECustomerAlgorithm,
        SSECustomerKey,
        SSECustomerKeyMD5,
    }),
);

Was this helpful?

YesNo

On this page

Use SSE-C - 图1Docs

{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/r2/examples/ssec/#page","headline":"Use SSE-C","description":"The following tutorial shows some snippets for how to use Server-Side Encryption with Customer-Provided Keys (SSE-C) on Cloudflare R2.","url":"https://developers.cloudflare.com/r2/examples/ssec/","inLanguage":"en","image":"https://developers.cloudflare.com/r2/examples/ssec/og.png?v=83c74e2f518a7808","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}


description: Configure R2 buckets with Terraform using the Cloudflare provider. title: Terraform image: https://developers.cloudflare.com/r2/examples/terraform/og.png?v=7493ec17de4a5930


Skip to content

Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/r2/llms.txt
Use this file to discover all available pages before exploring further.