WebDisk
Object Storage

Encryption in S3: SSE-S3, SSE-KMS and SSE-C – who holds the key to your files?

Data publikacji:

WebDisk Blog · category: Security · reading time: ~12 minutes

In short:- SSE-S3 – the keys are managed by the object storage; turn it on and forget about it.- SSE-KMS – the keys live in an external vault (KMS); you gain auditing, rotation and separation of permissions.- SSE-C – you supply the key with every request and the server does not store it; maximum control and full responsibility. >Not a terminal person? You can safely skip the code examples – the comparison table and the section on WebDisk Files and Send give you the full picture.

When you upload a file to the cloud, something we rarely think about happens in the background: your data lands on physical disks in someone else's server room. What happens if such a disk is replaced during servicing and leaves that server room? Or if a backup falls into the wrong hands? The answer to these questions is encryption at rest – and in the world of the S3 protocol, which our platform also speaks, it comes in three standard variants: SSE-S3, SSE-KMS and SSE-C.

They differ in one detail, but a fundamental one: who generates, stores and controls the encryption key. In this article we explain how each variant works, show by example how to use them on your own, and describe how we use them in practice in the WebDisk Files and WebDisk Send services.

Two different problems: data in transit and data at rest

Before we move on to SSE, let's get the terminology straight, because "encryption" tends to be a catch-all label for very different things:

  • Encryption in transit protects data on the way between you and the server. That is the job of TLS – the same technology behind the padlock in your browser. Without TLS, someone eavesdropping on the network (for example on open Wi-Fi) could intercept the file being sent.
  • Encryption at rest protects data lying on the disks. Even if someone physically gains possession of the medium or of the raw files from the server, all they will see is cryptographic noise.

These mechanisms complement each other rather than replace each other. All communication with WebDisk services always goes over TLS – that layer is mandatory. Encryption at rest is the second layer, and this article is about it.

Server-Side Encryption (SSE) means that the server takes care of the encryption: the data arrives over TLS, the server encrypts it before writing it to disk, and decrypts it on download. The alternative is client-side (end-to-end) encryption, where from beginning to end the server sees only the ciphertext – the encrypted, unreadable form of the data. We will come back to this distinction in the FAQ, because it matters for an honest assessment of the guarantees.

How the server encrypts: an envelope inside an envelope

All three variants encrypt data with the same proven algorithm: AES-256 – a symmetric block cipher which, when correctly implemented, is beyond the reach of any known brute-force attack. What is genuinely interesting is what happens to the keys.

The first two variants – SSE-S3 and SSE-KMS – use what is known as envelope encryption:

  1. Every object (file) is encrypted with its own, single-use data key (DEK – data encryption key).
  2. The data key is in turn encrypted with a master key (KEK – key encryption key) and only in that encrypted form is it stored in the object's metadata.
  3. The master key is never stored next to the data – and where it lives and who manages it is what tells SSE-S3 and SSE-KMS apart.

Thanks to the envelope, compromising a single data key exposes one file, not the whole archive; rotating the master key also does not require re-encrypting petabytes of data – it is enough to re-encrypt the small data keys.

SSE-C works differently: the key supplied by the client encrypts the object directly. The server does not generate a data key and does not store any wrapped key – it keeps only a digest of the key, so that on subsequent requests it can check that the same key was supplied.

From the perspective of an S3 user, choosing a variant comes down to the HTTP headers sent when writing an object (PUT). Reading – apart from SSE-C – is transparent: the server recognises by itself that the object is encrypted and decrypts it on download, as long as you have the right to access the object (and in the SSE-KMS variant – also the right to use the key in the KMS).

Before you start: configuring the client. The examples use the official aws CLI, which works with any S3-compatible storage. You need a pair of access keys (access key + secret key) from your provider – in WebDisk services you will find them in the panel. Configure them once: >

aws configure # enter the access key and secret key (region: e.g. us-east-1)

>

If your storage is not in AWS, add your provider's endpoint to every command: >

aws --endpoint-url https://s3.twoj-dostawca.example s3 ls

>

For readability we omit --endpoint-url in the examples below – don't forget it if you are not using AWS.

SSE-S3 – the server manages everything

This is the "turn it on and forget" variant. The data keys and the master key are generated, stored and rotated by the object storage itself. The user sees no key at all – they merely declare that they want encryption.

Technically, a single header on write is enough:

x-amz-server-side-encryption: AES256

In practice it is rarely added by hand to every request. Default bucket encryption is more convenient (a bucket is a container for objects in S3 – the equivalent of a disk or a share; in WebDisk services we call it a storage). From the moment it is switched on, every newly written object is encrypted automatically, whether or not the client asked for it. That, incidentally, is how AWS works today – since January 2023 all new objects in S3 there are encrypted with SSE-S3 by default. In Ceph RGW (the engine of our platform) default bucket encryption is configured with an identical API call.

# a single file with an explicit encryption request
aws s3 cp raport.pdf s3://moj-bucket/ --sse AES256

# or once and for all: default encryption for the entire bucket
aws s3api put-bucket-encryption --bucket moj-bucket \
  --server-side-encryption-configuration \
  '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'

# verification: is the object encrypted?
aws s3api head-object --bucket moj-bucket --key raport.pdf
# in the response: "ServerSideEncryption": "AES256"

What SSE-S3 gives you: protection against "physical" scenarios – a disk carried out of the building, a medium after a failure, raw files copied from the server's file system are all useless.

What it does not give you: protection against compromise of the account. Whoever has the right to read an object receives it decrypted – the server does that automatically. Stolen API keys mean access to readable data, exactly as if there were no encryption at all. That is not a flaw in the implementation, but the boundary of the model: SSE-S3 protects media, not the account.

SSE-KMS – keys moved out into a vault

SSE-KMS works like SSE-S3, except that the master key no longer belongs to the object storage but to an external key management service (KMS – Key Management Service). On every operation the object storage has to ask the KMS to decrypt the data key – and this is where three practical benefits appear:

  1. Separation of permissions. Access to the object and access to the key are two independent decisions. The right to use the key can be withdrawn – and the data becomes unreadable, even for someone who still has access to the bucket.
  2. Auditing. Every use of the key leaves a trace in the KMS logs: who, when, for what. For compliance (GDPR, ISO 27001) this is often a hard requirement.
  3. Key lifecycle. Rotation, disabling, revocation – managed centrally, per key, without touching the data. Many KMS deployments also let you bring your own key material (BYOK – bring your own key).

On write you declare the aws:kms variant and, optionally, a key identifier:

x-amz-server-side-encryption: aws:kms
x-amz-server-side-encryption-aws-kms-key-id: <key-identifier>

# in AWS you first create a key in the KMS service and give it an alias
aws kms create-key --description "Key for company documents"
aws kms create-alias --alias-name alias/moj-klucz-firmowy \
  --target-key-id <KeyId-from-the-previous-command>

# upload with SSE-KMS pointing at a specific key
aws s3 cp raport.pdf s3://moj-bucket/ \
  --sse aws:kms --sse-kms-key-id alias/moj-klucz-firmowy

In AWS the role of the KMS is played by the AWS KMS service. In the Ceph RGW world the KMS backend can be, among others, HashiCorp Vault or its open fork OpenBao – and that is the path we chose at WebDisk: the encryption keys live in a separate, isolated cryptographic vault, and the object servers fetch them on demand, over an authenticated channel, without writing them to disk next to the data. The key identifier from the header then corresponds to the name of the key in the vault.

In WebDisk services SSE-KMS works "under the hood" of the platform – it is not an option you click yourself. The choices we hand directly to the user are SSE-S3 (encrypted storages in Files) and SSE-C (the organisation key in Files) – more about both in a moment.

The trade-off: SSE-KMS is an additional moving part (the KMS has to be available for the data to be readable) and adds the overhead of talking to the vault. In return we get auditing, central management and the ability to "cut off" the data in a single move.

SSE-C – you bring the key

The third variant reverses the roles: the client generates and stores the key, and the server only performs the encryption. With every request – on write and on read – the client sends its 256-bit key in the headers:

x-amz-server-side-encryption-customer-algorithm: AES256
x-amz-server-side-encryption-customer-key: <base64-encoded key>
x-amz-server-side-encryption-customer-key-MD5: <MD5 sum of the key, base64>

The server uses the key to encrypt the object and then discards the key – it does not store it anywhere. It remembers only a cryptographic fingerprint of the key (AWS S3 – a randomly salted HMAC, Ceph RGW – the MD5 sum of the key): enough to recognise on subsequent requests that the same key was supplied, but useless for reconstructing it.

The consequences are serious and you need to understand them before you use it:

  • Losing the key = losing the data. Irrevocably. The provider has no copy and no recovery procedure – this is not a marketing declaration but a property of the design.
  • TLS is mandatory. The key travels in an HTTP header, so the server rejects SSE-C requests arriving without an encrypted connection (in Ceph RGW this is enforced by the rgw_crypt_require_ssl option, enabled by default).
  • Ordinary links stop being enough. Most mechanisms for sharing files from S3 rely on so-called presigned URLs – a link with a built-in, time-limited signature of permissions, thanks to which an object can be downloaded without logging in. A presigned URL signs the request, but it cannot smuggle in the headers carrying the key – whoever downloads the file still has to know the key. A browser that received nothing but the link will not download an SSE-C object. Applications have to handle this case separately (in a moment we will show how Files does it).

An example from scratch, using openssl and the aws CLI:

# 1. generate a 32-byte (256-bit) key and keep it somewhere safe
openssl rand -out sse-c.key 32

# 2. upload with SSE-C
aws s3 cp tajne.pdf s3://moj-bucket/tajne.pdf \
  --sse-c AES256 --sse-c-key fileb://sse-c.key

# 3. download – requires THE SAME key
aws s3 cp s3://moj-bucket/tajne.pdf pobrane.pdf \
  --sse-c AES256 --sse-c-key fileb://sse-c.key

# 4. an attempt to download without the key ends with a 400 error
aws s3 cp s3://moj-bucket/tajne.pdf test.pdf

Note for AWS S3 users: since April 2026 new buckets (and existing ones on accounts that had no SSE-C objects) have SSE-C writes blocked by default – an upload with SSE-C headers will return a 403 AccessDenied error. You unblock it once: >

aws s3api put-bucket-encryption --bucket moj-bucket \
  --server-side-encryption-configuration \
  '{"Rules":[{"BlockedEncryptionTypes":{"EncryptionType":["NONE"]}}]}'

>

Reading SSE-C objects written earlier works unchanged. On Ceph RGW storage – including WebDisk services – SSE-C works straight away, without this step.

The same programmatically in Python (boto3 will base64-encode the key and add the MD5 sum by itself):

import boto3, os

s3 = boto3.client("s3", endpoint_url="https://s3.twoj-dostawca.example")

key = os.urandom(32) # 256-bit key – store it safely!

with open("tajne.pdf", "rb") as f:
    s3.put_object(
        Bucket="moj-bucket", Key="tajne.pdf", Body=f,
        SSECustomerAlgorithm="AES256",
        SSECustomerKey=key,
    )

obj = s3.get_object(
    Bucket="moj-bucket", Key="tajne.pdf",
    SSECustomerAlgorithm="AES256",
    SSECustomerKey=key,
)

An important nuance, for the sake of full honesty: with SSE-C the server does see the key and the contents of the file while it is handling the request (in memory, for the duration of the operation) – but it never sees them after the operation ends. If the requirement is that the server must not see the contents at any moment, what you need is client-side encryption, not SSE.

The three variants in a nutshell

  • Who generates the master key – SSE-S3: the object storage · SSE-KMS: the KMS service · SSE-C: the client
  • Where it is stored – SSE-S3: inside the storage · SSE-KMS: in the KMS (a vault outside the storage) · SSE-C: exclusively at the client
  • What the server stores – SSE-S3: everything · SSE-KMS: encrypted data keys · SSE-C: the ciphertext + a digest of the key
  • Losing the key on the client side – SSE-S3: impossible · SSE-KMS: impossible · SSE-C: loss of the data
  • Key auditing and rotation – SSE-S3: internal, automatic · SSE-KMS: central, visible, per key · SSE-C: entirely on the client side
  • User effort – SSE-S3: none · SSE-KMS: small (managing the key in the KMS) · SSE-C: large (full responsibility)
  • Typical scenario – SSE-S3: a sensible default standard · SSE-KMS: compliance, auditing, BYOK · SSE-C: maximum control over the key A simple rule for choosing: SSE-S3 when you simply want your data encrypted at rest and do not want to manage anything; SSE-KMS when an auditor asks "who used the key and when" or company policy requires control over the key lifecycle; SSE-C when the key is to be in your hands alone – and you are ready to bear the consequences.

How we do it at WebDisk: Files and Send

Theory starts to make sense when you can see it in a working product. Our two file services deliberately sit at the two ends of the "simplicity ↔ control" spectrum.

WebDisk Files – layers to choose from

Files is our secure file storage platform for companies. Encryption in it is arranged in layers, and the two SSE mechanisms from this article appear in the roles that suit them best:

  • SSE-S3 at the storage level. When creating an additional storage (bucket) you can enable its permanent encryption with a single switch. From that moment every file – uploaded from the panel, through the desktop application or as part of sharing – is encrypted automatically, at the source, by default bucket encryption. The decision is one-way (a storage cannot be "decrypted" with a switch), the user manages no key at all, and the master keys are not stored next to the data – they live in a dedicated cryptographic vault (OpenBao), with a separate key for each storage.
  • SSE-C at the organisation level. An organisation can enable additional encryption of selected files and folders using the SSE-C mechanism. An important difference from the standalone SSE-C described above: here the key is not managed by the user but by the Files application on behalf of the organisation – when the feature is switched on, a 256-bit organisation key is generated and stored by the application in encrypted form, outside the object storage layer. Thanks to this the object storage never persists the key – a medium carried out of the building or a compromise of the storage layer itself does not expose these files – while at the same time the organisation does not bear the risk of irreversible data loss: the "I lost the key – the data is gone" scenario applies to standalone use of SSE-C over the API, not to this feature. In fairness it has to be added: in this model the application has access to the key – and therefore, technically, so does the provider; if the requirement is that the provider must not be able to read the contents at any moment, the right tool is client-side encryption (see the FAQ). The application handles the consequences of the SSE-C protocol itself: encrypted files cannot be downloaded with an ordinary presigned URL (which does not carry the headers with the key), so Files serves them through the application, streaming the data once permissions have been verified. Our desktop application for folder synchronisation supports SSE-C as well.
  • Complementary layers. Encryption protects confidentiality, but it does not protect against files being overwritten or encrypted by ransomware on the user's computer. That is why, alongside SSE, Files offers versioning and the Anti-Ransomware mode based on S3 Object Lock in Compliance mode – an immutability lock enforced by the object storage layer, resistant even to a compromise of the application itself. The full security picture is always the sum of layers, not a single mechanism.

WebDisk Send – security without an instruction manual

Send solves a different problem: "I have a big file, I want to send it to someone, right now". When designing it we deliberately did not carry over the per-file encryption option from Files – because a service whose strength is the absence of configuration should not open the conversation with a question about keys.

The security of Send rests on mechanisms matched to that scenario: transfer exclusively over TLS, an optional password on the link (which controls access to the download – it is not a key that encrypts the contents), a download limit and an expiry date, and above all retention – files live as long as the plan allows, after which they are automatically and permanently deleted. The safest file is the one that no longer exists. The data sits on the same Ceph object platform described above – with the same operational backing.

If you need encryption that you control yourself – encrypted storages, an organisation key, retention policies and locks – that is a sign that your tool is Files, not Send. This division is intentional.

Frequently asked questions

Does encryption at rest slow down uploads and downloads? In practice, no. Modern processors have hardware AES support (AES-NI), and encryption happens on the fly. With SSE-KMS there is a brief exchange with the key vault, negligible next to the transfer of the file.

Does SSE-S3 protect me if someone steals my password or my API keys? No – and no SSE variant has that job. The server decrypts data for anyone holding valid permissions. Strong passwords, MFA and API key hygiene protect against credential theft; versioning and Object Lock protect against its consequences.

I have lost my SSE-C key. What now? Nothing. This is not a "contact technical support" scenario – the server does not have the key, the provider does not have it, nobody has it. The data is cryptographically unrecoverable. That is why SSE-C keys are kept in a secrets manager and covered by company procedures (copies, escrow, succession). The above concerns standalone use of SSE-C over the S3 API – in the Files service the SSE-C key is managed by the application on behalf of the organisation, so that scenario does not arise there.

How does SSE differ from end-to-end encryption? With SSE the server does the encrypting – so at the moment of writing and reading it sees the contents (and with SSE-C also the key) in working memory. With client-side (end-to-end) encryption the file is encrypted on your own device and the server never sees either the contents or the key – at the cost of losing the functions that require the contents on the server side (previews, search, zipping a folder). These are two different points on the trust spectrum; SSE-C is as far as you can go while still staying on the server side.

How will I know that an object is encrypted? aws s3api head-object returns the x-amz-server-side-encryption header (AES256 or aws:kms) – a good, quick audit of your own buckets. Watch out for SSE-C: head-object on such an object without supplying the key returns a 400 Bad Request error – and it is precisely that error which signals that the object is encrypted with a customer key; you will see the x-amz-server-side-encryption-customer-algorithm header only in the response to a request that carries the key.

I use rclone, not the aws CLI. Is that possible? Yes – rclone supports all three variants in the S3 remote configuration: server_side_encryption = AES256 (SSE-S3), server_side_encryption = aws:kms + sse_kms_key_id (SSE-KMS) or sse_customer_algorithm = AES256 + sse_customer_key_base64 (SSE-C).

Summary

Encryption at rest in S3 is not a single feature but a spectrum of trust models:

  • SSE-S3 – the server manages the keys; zero effort, protection of media and copies.
  • SSE-KMS – keys in an external vault; auditing, rotation, separation of permissions.
  • SSE-C – the key is yours alone; maximum control, full responsibility.

At WebDisk we use these mechanisms where they make sense: Files puts the choice in the hands of the organisation's administrator (SSE-S3 encrypted storages, optional SSE-C encryption with an organisation key managed by the application), Send takes that choice away from the user and rounds out security with retention and passwords on links, and wherever encryption is enabled the keys live in a separate vault – not next to the data.

Would you like to see it in practice? Create a free account with WebDisk Send or try out WebDisk Files – and if you have questions about encryption in your organisation, write to us.


This article is part of a series on data security in WebDisk services. You can run all the examples on any S3-compatible storage (on AWS S3 the SSE-C example requires a one-time unblocking – see the box in the SSE-C section); the --endpoint-url parameter in the aws CLI points to your provider's endpoint.

Encryption in S3: SSE-S3, SSE-KMS and SSE-C – who holds the | WebDisk