Back to Blog
HealthcareDecember 28, 20259 min read

Encrypting Patient Data: What You Actually Need to Do

Encrypting Patient Data: What You Actually Need to Do

Encrypting Patient Data in Web Apps: A Practical Guide

There is a lot of confusion about what HIPAA actually requires when it comes to encryption. Some teams over-engineer everything, encrypting fields that do not need it and burning cycles on key rotation schemes they read about in a blog post. Other teams do the bare minimum and hope for the best. This guide is about finding the right level for a healthcare web application, with specific implementation details for a Supabase/Postgres stack.

What HIPAA Actually Requires

Here is the part that surprises people: HIPAA does not technically mandate encryption. The Security Rule lists encryption as an "addressable" specification, not "required." That means you need to either implement it or document why an equivalent alternative is reasonable.

In practice, every auditor expects encryption in transit and at rest. Not implementing these is indefensible in 2026. But the regulation gives you flexibility in how you implement it and where you apply additional layers.

The key standard to know is the safe harbor provision. If you encrypt PHI according to NIST guidelines and the encryption keys were not compromised, a breach of that data is not a reportable breach. That alone makes encryption worth every bit of engineering effort.

TLS for Data in Transit

This is table stakes. Every connection that carries PHI needs TLS 1.2 or higher. In practice, you should be on TLS 1.3 everywhere you can. What people miss:

Internal service-to-service traffic matters. If your API talks to a microservice over plain HTTP inside your VPC, that is a gap. "It is inside our network" is not a sufficient control. Use mTLS between services, or at minimum TLS with certificate verification.

Database connections need TLS too. Supabase enables this by default, which is great. If you are self-hosting Postgres, configure sslmode=verify-full in your connection string. Do not use sslmode=require, because it does not verify the server certificate, leaving you vulnerable to MITM attacks.

WebSocket connections for real-time features (chat, notifications) must use WSS, not WS. If you are using Supabase Realtime, this is handled for you. If you are running your own WebSocket server, do not forget this.

Certificate management: Use Let's Encrypt or AWS ACM for automated renewal. Expired certificates are one of the most common causes of healthcare app outages. Set up monitoring that alerts 30 days before expiration, not 7.

Third-party API calls are easy to overlook. If your app sends data to a lab integration, a pharmacy network, or a billing service, verify that each connection uses TLS. We have seen vendors accept unencrypted connections without complaint. Just because they support HTTPS does not mean your client library is configured to require it.

Encryption at Rest: Database Level

Supabase runs on AWS, and all databases have disk-level encryption enabled by default using AES-256. If you are on a self-managed Postgres instance, make sure your cloud provider's encryption is turned on (it is not always the default).

What disk-level encryption protects against: someone physically stealing a hard drive, or accessing raw disk snapshots. What it does not protect against: anyone with database credentials reading data through normal queries. This is an important distinction that trips up a lot of teams during audits.

For most PHI fields (names, dates of birth, addresses, appointment records), disk-level encryption combined with proper access controls is sufficient. You do not need to encrypt every column individually.

Backups deserve special attention. Supabase point-in-time recovery backups are encrypted, but if you are exporting data for analytics or taking manual pg_dump backups, those files need encryption too. We use gpg for encrypting database exports with a passphrase stored in our secrets manager. Never put that passphrase in a shell script or a CI config file.

Also think about your staging and development environments. It is common to restore production backups into staging for debugging. If those backups contain real PHI and your staging environment has weaker access controls, you have created a compliance gap. Use anonymized or synthetic data in non-production environments whenever possible.

Application-Layer Encryption for Sensitive Fields

Some data deserves an extra layer of encryption above what the database provides. You encrypt data before it hits Postgres, so even a database administrator cannot read it.

Fields that warrant this treatment: Social Security Numbers, psychotherapy notes (special protection under HIPAA), substance abuse treatment records (42 CFR Part 2), genetic information, and HIV/AIDS status (many states have additional protections).

Use AES-256-GCM, not AES-256-CBC. GCM provides authenticated encryption, which means it detects if the ciphertext has been tampered with. CBC does not give you that guarantee. In your implementation, generate a random IV for each encryption operation, capture the authentication tag, and store IV plus auth tag plus ciphertext together in a single column as a delimited string.

One important trade-off: application-encrypted fields cannot be searched or indexed by the database. If you need to look up a patient by SSN, you will need a separate hashed index column (using HMAC-SHA256 with a different key) alongside the encrypted value. This adds complexity to your data model, but it is the standard approach.

Another consideration: encrypted fields make debugging harder. You cannot just query the database to check a value. Build admin tooling early that can decrypt and display sensitive fields for authorized users, with full audit logging of every access.

One more thing on field-level encryption: be deliberate about what you encrypt at the application layer versus what you protect with row-level security. Postgres RLS policies can restrict which rows a user can access without encrypting the data itself. For fields like diagnosis codes that need to be queryable but should not be visible to all staff roles, RLS is often the better tool. Reserve application-layer encryption for data that truly needs to be opaque to the database layer.

Key Management

This is where most teams cut corners, and it is the part that matters most. Do not store encryption keys in environment variables. Yes, every tutorial does this. It is still wrong for healthcare. Environment variables end up in logs, crash reports, container metadata, and CI/CD artifacts.

Use a proper key management service: AWS KMS, Google Cloud KMS, Azure Key Vault, or HashiCorp Vault if you want to be cloud-agnostic.

The pattern we recommend is envelope encryption. KMS holds a master key that encrypts data encryption keys (DEKs) stored in your database. When your app starts, it fetches and decrypts the DEK using KMS. The DEK stays in memory for the process lifetime. Your actual data keys never exist in plaintext outside your application's memory.

Key rotation is straightforward with this pattern. Generate a new DEK, encrypt it with the master key, store it alongside the old one. New writes use the new key. Old data gets re-encrypted lazily on read or in a background migration. Tag each encrypted field with the key version it was encrypted with so you always know which DEK to use for decryption.

One practical tip: build a key rotation dry-run command that reports how many records would need re-encryption and estimates the time required. Running a migration that touches every row in your patients table without knowing the scope first is a recipe for downtime.

What Is Overkill

Not everything needs the full treatment. Here is where teams waste time:

Encrypting appointment times at the application layer. These are PHI, yes, but disk encryption plus access controls is sufficient. You need to query by time range, and application-layer encryption makes that impossible.

Rotating keys weekly. Annual rotation is fine for most healthcare applications. The NIST recommendation for AES-256 keys is that they remain secure for decades. Rotate because of policy, not because the key is getting "weak."

Client-side encryption for all data. Some teams try to encrypt everything in the browser so the server never sees plaintext. This is noble but impractical for most healthcare apps. You need server-side access for clinical decision support, reporting, and integrations. Save client-side encryption for specific scenarios like messaging content in mental health apps.

Encrypting data that is not PHI. Provider office hours, insurance company names, generic educational content. Not everything in a healthcare app is protected information. Encrypt what needs encrypting and apply access controls to the rest.

Auditing and Getting Started

Encryption is only as good as your ability to prove it is working. Log every key access event (KMS makes this easy). Monitor for unencrypted writes to fields that should be encrypted (a database trigger can catch this). Set up alerts for failed decryption attempts, bulk data access patterns, and changes to encryption configuration. Run quarterly audits where you verify that a sample of records are actually encrypted, not just stored as plaintext.

If you are starting from zero, here is the order: enable TLS everywhere first, confirm disk-level encryption on your database and backups, identify which fields need application-layer encryption, set up KMS-based key management, implement field-level encryption for those columns, add audit logging, and document it all for your compliance team. Steps one through three take about a week. The rest takes two to three more weeks depending on your KMS choice. That is a reasonable investment for something that could save you from a reportable breach.