Learn OpenTofu - Exclusive Feature 1: Native Client-Side State Encryption
Episode 6 of 21

Learn OpenTofu - Exclusive Feature 1: Native Client-Side State Encryption

State files store secret data such as passwords and API keys in plaintext in the remote backend. This episode covers OpenTofu's exclusive feature: client-side state encryption before it's sent to storage, complete with AWS KMS, GCP KMS, and PBKDF2 passphrase key providers.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In the previous episode 5 we covered remote state management and state locking — how the state file moves from a laptop to centralized storage like S3, GCS, or an HTTP backend, complete with a locking mechanism so two people don't apply at the same time. State lives in the cloud, collaboration gets easier, and conflicts decrease. But there's one big problem we deliberately haven't mentioned yet: the state content itself isn't secure.

Try to remember what's stored inside opentofu.tfstate. It's not just resource IDs like i-0a1b2c3d, but also all the post-apply attributes — and that's where the danger is. When a database auto-generates a password, or a module stores a secret value as an attribute, that value gets embedded in the state in plaintext. Anyone who can read your S3 bucket — or a leaked state backend — can automatically read all those secrets.

In this episode we cover OpenTofu's first exclusive feature that sets it apart from Terraform: native client-side state encryption. You'll see how state is encrypted on the client side before being sent to storage, learn the structure of the encryption block, and get to know the available key provider options.

Main Discussion

Why Plaintext State Is a Time Bomb

Many engineers treat the state file as an "internal technical detail" and put it in an S3 bucket without extra protection. Yet behind the innocuous-looking resource IDs, state stores sensitive data:

  • Database passwords auto-generated by providers.
  • API keys and access tokens passed as resource arguments.
  • Connection strings and internal infrastructure addresses.
  • The full metadata of your architecture — a prime target for attack planning.

Warning

Access control on the S3 bucket is important, but it's not enough. Remote state backends are read automatically by CI machines, other tooling, and are sometimes backed up to unmonitored locations. You can't rely on "who can open the bucket" alone — the data must be secure even while sitting in storage you don't fully control.

Terraform has long relied on two defenses: sensitive = true to hide values from output and logs, plus tight backend access. Both hide information from human eyes, but the file in storage is still stored plain — anyone who steals the file gets everything immediately. That's where OpenTofu goes further.

The Concept: Client-Side Encryption

Native client-side state encryption works on a simple principle: data is encrypted on the machine running tofu plan or tofu apply, and only ciphertext is sent to the remote backend. Storage only sees a blob of random bytes — even if the bucket leaks, nothing can be read without the key.

The concept is like a diary kept in a bank vault: the vault (S3 bucket) only stores the book already locked with a padlock, while the person who opens the padlock holds a key you keep separately.

The configuration architecture splits into three layers:

LayerRole
key_providerThe encryption key source — where the key comes from (KMS, passphrase, etc.)
methodThe encryption algorithm used (e.g. AES-GCM) plus which key provider's key
target (state / plan)The data portion protected — state file, plan file, or both

Configuring the encryption Block

Let's look at a full configuration for encrypting the state file using AWS KMS:

encryption.tf
encryption {
  key_provider "aws_kms" "main" {
    kms_key_id = "arn:aws:kms:ap-southeast-1:123456789012:key/xxxx"
  }
 
  method "aes_gcm" "main" {
    keys = key_provider.aws_kms.main
  }
 
  state {
    method = method.aes_gcm.main
  }
}

Read this configuration from the bottom up: the state target uses method.aes_gcm.main, that method takes its key from key_provider.aws_kms.main, and the key provider requests the key from AWS KMS. Every time OpenTofu writes state, it calls AWS KMS to get a data key, encrypts the state with AES-GCM, then sends the result to the backend.

Once this block is active, try opening the state in S3 via the console — what you'll see isn't a pretty JSON with resources, but an encrypted blob. Running tofu plan and tofu apply stays exactly the same; your workflow doesn't change at all.

Note

The encryption block can also apply to plan files by adding a plan { method = method.aes_gcm.main } block inside it. The benefit: plan files from tofu plan -out=plan.tfplan — which are often stored in CI caches — get protected too.

Key Provider Options

OpenTofu designed the key_provider abstraction so you can choose your key source according to your ecosystem:

Key ProviderKey SourceBest For
aws_kmsAWS KMS Customer Managed KeyTeams fully on AWS
gcp_kmsGoogle Cloud KMSTeams running on GCP
azure_keyvaultAzure Key VaultTeams in the Azure ecosystem
pbkdf2Passphrase derived into a keySolo developers or small teams
openpgpOpenPGP public keyCases without a cloud KMS
externalCustom external programHSM or internal vault integration

For small teams without KMS yet, the pbkdf2 provider is the easiest path — just one passphrase that OpenTofu turns into an AES key through PBKDF2 iteration:

encryption-pbkdf2.tf
encryption {
  key_provider "pbkdf2" "my_passphrase" {
    passphrase = "replace-with-a-strong-passphrase"
  }
 
  method "aes_gcm" "main" {
    keys = key_provider.pbkdf2.my_passphrase
  }
 
  state {
    method = method.aes_gcm.main
  }
}

Warning

A lost PBKDF2 passphrase means the stored state cannot be recovered — there's no built-in recovery mechanism because encryption happens before data leaves the client. Store the passphrase in the team's password manager and combine it with backups of older state versions. For production, cloud KMS is more recommended because its keys are centrally managed and can be rotated.

Best Practices & Comparison with Terraform

This feature is purely exclusive to OpenTofu — in Terraform, state is always stored as-is in the backend. After migrating, you simply add an encryption block and run tofu plan and tofu apply; old state gets encrypted automatically on the next write, with no manual migration needed.

Recommended practices:

  • Encrypt both state and plan in production environments.
  • Use cloud KMS (AWS, GCP, or Azure) so keys are centralized and rotatable; store PBKDF2 passphrases in a secret manager.
  • Combine with state locking and strict backend access control — encryption is not a replacement for access control.
  • Test the recovery procedure by trying to read state after the key is changed or rotated.

Conclusion

In episode 6 we built the state security foundation in OpenTofu:

  • State files store plaintext secrets — resource IDs, passwords, API keys, and architecture metadata.
  • Client-side state encryption encrypts data on the local machine before it's sent to the remote backend, so storage only holds ciphertext.
  • The encryption block consists of three layers: key_provider (key source), method (algorithm like AES-GCM), and target (state / plan).
  • Available key providers: AWS KMS, GCP KMS, Azure Key Vault, PBKDF2 passphrase, OpenPGP, and custom external providers.

This is the best layered defense for one of the most important assets in IaC. In the next episode, episode 7, we'll cover OpenTofu's second exclusive feature: Dynamic Provider Iteration — loading many provider instances dynamically using for_each to manage multi-region and multi-account setups with concise code. See you in the next episode!