Use SSE-C
  Last reviewed:  6 months ago  
 The following tutorial shows some snippets for how to use Server-Side Encryption with Customer-Provided Keys (SSE-C) on R2.
- 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.
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        });      }    }  }}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?
- Resources
- API
- New to Cloudflare?
- Products
- Sponsorships
- Open Source
- Support
- Help Center
- System Status
- Compliance
- GDPR
- Company
- cloudflare.com
- Our team
- Careers
- 2025 Cloudflare, Inc.
- Privacy Policy
- Terms of Use
- Report Security Issues
- Trademark