On the day a customer’s questionnaire asks whether data is encrypted at rest and in transit, an app on Supabase or Neon is most of the way to yes: the provider encrypts the disk, and connections support TLS. Database encryption becomes your job below that line, in the few columns that should stay unreadable to anyone holding a valid database login.
What is data encryption? Where database encryption happens, in three places
Data encryption turns readable data into ciphertext that only a key holder can turn back. Database encryption happens in three places: at rest on the storage volume, in transit over TLS, and in the data itself, column by column. The first two are transparent, so every valid login still reads plaintext.
This page is the encryption line of the data consistency checklist for SaaS, worked through for a founder who has to answer for it.
A data encryption definition fits in one line, and NIST’s glossary definition of encryption reads: “The process of changing plaintext into ciphertext using a cryptographic algorithm and key.” In plain words, encrypting data means that a copy taken without the key is noise, and the same copy plus the key gives back the original. Encrypted information is only as private as the key that opens it. The primary purpose of data encryption, in my reading, is confidentiality: keeping content away from whoever gets hold of the bytes but was never meant to read them.
Those are the encryption basics. For a database, the useful question is where the encryption of the database happens, because each place stops a different person. At rest means the storage volume, its snapshots and its backups: the disk is scrambled, and the running database reads it normally. In transit means the TLS connection between the app and the database, and between the browser and the app; a query result on its way from the database to your server is data in transit. The third place is the data itself, a column or a single value encrypted before or as it is stored, so the database holds ciphertext even while it runs.
The column that matters most in the table below is who can still read the data, and that column is my reading of the sources, not a vendor’s words. Storage encryption is transparent once the database is running: PostgreSQL’s documentation says that while the file system is mounted, “the operating system provides an unencrypted view of the data.” So every query by every login the database accepts returns plaintext. That is by design, and it is why “we are encrypted” and “a stranger read our customers’ rows” can both be true on the same day.
| Where | Who holds the key | Who can still read the data | Who turns it on |
|---|---|---|---|
| At rest (volume, snapshots, backups) | The provider | Every login the database accepts | The provider: Supabase and Neon state AES-256 at rest; Amazon RDS can encrypt an instance only when it is created |
| In transit (app to database, browser to app) | Keys agreed by each connection’s handshake | The two ends of the connection | The provider offers TLS; Neon requires it on every connection |
| In the data (a column or value) | You: your app, or a key your app passes to the database | Only code that holds the key | You |
Encryption in cyber security is the same idea applied wherever data sits or moves, so data security encryption on laptops, phones and email works on the same principle; this page stays on the database. Which of these settings each provider turns on, and how to make a database refuse unencrypted connections, is covered in how to harden managed database settings.
Why it matters for a small SaaS: how encryption prevents a hacker from getting your data, and when it does not
Encryption prevents a hacker from getting your data only when the hacker lacks the key. Disk encryption stops a stolen disk and TLS stops a listener on the network. Neither stops stolen credentials, SQL injection or a missing authorization check, because those queries run as a valid login and get plaintext back.
Whether it works depends on who the hacker is and what they hold. The map below is my reading, one row per attacker a small SaaS realistically meets; the verdicts are mine, and the mechanisms behind them are the vendor and PostgreSQL pages cited on this page.
| Attacker | Disk encryption | TLS | Column or application encryption | What actually stops them |
|---|---|---|---|---|
| Someone who steals a disk or a cloud snapshot | Stops them | No effect | Protected columns stay ciphertext too | Encryption at rest |
| Someone on the network path | No effect | Stops them, if the database refuses unencrypted connections | Application-level: protected columns travel as ciphertext. pgcrypto: no, its data and key cross in clear text | TLS, enforced as a setting |
| Someone who obtains a database backup file | Only if the backup file is encrypted too | No effect | Protected columns stay ciphertext | An encrypted backup, with its key kept apart |
| Someone with stolen database credentials or a leaked service key | No effect: the database serves them plaintext | No effect: their own connection is encrypted too | Stops them for the protected columns, if the key is held outside the database | Application-level encryption for those columns, and rotating the leaked credential |
| Someone exploiting SQL injection or a missing authorization check | No effect | No effect | Little: the query runs as the app, and the app can read what it decrypts | Authorization checks and safe queries, not encryption |
| An insider or support engineer with console access | No effect | No effect | Stops them for the protected columns, if they cannot reach the key | Column or application-level encryption, plus access logging |
The backup row has its own page: the database backup checklist for startups covers encrypting the file that leaves the database.
Rows four and five are where my audit numbers sit. Of the 21 third-party apps I audited in June and July 2026, 6 shipped a real secret. Those 21 were apps I selected for review, not a random sample, so none of these counts is a rate for AI-built apps in general. In the same audits, 7 of the 21 third-party apps had confirmed cross-user or cross-tenant authorization failures, where a logged-in user could read or write another customer’s data. 9 of the 21 had row-level security gaps. At least 5 of the 21 exposed PII or PHI. Across all 26 apps I audited in those two months, the lowest-scoring one was a medical-advice app at 29 out of 100, with PHI in plaintext plus an ML-model-loading RCE chain; that is one app, not a pattern. In my reading, neither the secret pattern nor the authorization pattern is something a disk-encryption switch addresses.
Take a founder whose app stores the calendar tokens its users connect: a customer’s security questionnaire asks whether data is encrypted at rest, and the founder answers yes, citing the provider’s statement. The answer is true, and it does not cover the case the customer cares about: a leaked database password, or a route that forgets its ownership check, reads those token columns in plaintext, because storage encryption is transparent to anyone the database lets in. The questionnaire row is about the disk, and the tokens need application-level encryption with a key the database never holds. The lesson I take from it: “encrypted at rest” answers a question about stolen disks, and the tokens answer to a different attacker.
Data encryption is important for every app that holds other people’s data, and on Supabase or Neon it is already used at rest. The live decision is when data encryption should be used on top of that, to secure the few values the disk layer leaves readable to every login, which the fields section below answers column by column. Used for data security, encryption protects data from people who hold a copy but not the key; for data protection and privacy it is one control among several, next to authorization, backups and access logs.
Encryption for confidentiality keeps content secret, and that is all plain encryption promises. Encryption for data integrity, knowing the data was not altered, needs an authenticated mode, a message authentication code or a signature: NIST SP 800-38D on GCM says GCM “can detect both 1) accidental modifications of the data and 2) intentional, unauthorized modifications.” That is why a careful spec names a mode such as AES-GCM rather than “AES” alone. Availability is the cost side: lose the key and the data is gone.
How it works: types, techniques, what the platform does, and what is left to you
The five parts below run from vocabulary to decisions: the types, the database techniques, what the platform already does, which fields to encrypt, and where the keys live.
Types of data encryption: symmetric, asymmetric, and hashing, which is not encryption
Types of data encryption come down to two: symmetric, where one key encrypts and decrypts, as AES does for data at rest, and asymmetric, where a public and a private key split the job, as in TLS handshakes and signatures. Hashing is one-way and is what passwords get, never encryption.
Data encryption works with two inputs, an algorithm and a key, which turn plaintext into ciphertext and, with the right key, back again. That is how data is encrypted in every method below; what changes is how many keys there are and who holds them.
| Type | How keys work | Named algorithms | Where a web app meets it |
|---|---|---|---|
| Symmetric | One secret key encrypts and decrypts | AES (AES-128, AES-192, AES-256); ChaCha20 in a TLS 1.3 cipher suite | Disk encryption at the provider, the traffic of a TLS connection after the handshake, your own encrypted columns |
| Asymmetric | A public key and a private key | RSA; elliptic-curve algorithms such as ECDSA and EdDSA | Authenticating the server in a TLS handshake; digital signatures |
| Hashing (not encryption) | No key reverses it | SHA-256 for fingerprints; Argon2id, bcrypt or PBKDF2 for passwords | Password storage, reset tokens, checksums |
Symmetric encryption carries the bulk of the work: the TLS 1.3 standard requires support for an AES-GCM cipher suite to protect the traffic once the handshake is done. Asymmetric algorithms handle the start of a connection and signatures: in TLS 1.3, authentication “can happen via asymmetric cryptography”, with RSA, ECDSA and EdDSA named, and digital signatures exist, in NIST’s words, “to detect unauthorized modifications to data and to authenticate the identity of the signatory.”
Hashing belongs in the table only to say it is not encryption. SHA-256 maps any input to a fixed-length digest, which suits fingerprints and checksums. Passwords get something slower: OWASP’s Password Storage Cheat Sheet says that “in almost all circumstances” passwords “should be securely hashed using modern, adaptive hashing algorithms (e.g., Argon2id, bcrypt, or PBKDF2), rather than encrypted or stored in plaintext”, and that fast algorithms such as SHA-256 “are not suitable for password storage.” How login itself should handle them is in authentication best practices.
The Data Encryption Standard (DES) is the old US standard: NIST approved it as FIPS 46 in January 1977 and withdrew its final revision, FIPS 46-3, in May 2005. AES was approved as FIPS 197 in November 2001.
The block below is an example of encrypted data made with pgcrypto’s documented functions: one token written with the key version stored beside it, then the same value encrypted twice.
-- pgcrypto; the app sends the key as a query parameter ($2), never stored in a table
INSERT INTO calendar_links (user_id, oauth_token, key_version)
VALUES ($1, pgp_sym_encrypt('example-token-value', $2, 'cipher-algo=aes256'), 'v2');
-- armor() prints the ciphertext as base64 text; run twice, the two results differ,
-- because pgcrypto prefixes random bytes to every message
SELECT armor(pgp_sym_encrypt('example-token-value', $2)) AS first_run,
armor(pgp_sym_encrypt('example-token-value', $2)) AS second_run;
Those two runs are the data encryption example worth remembering. The same value never produces the same ciphertext, so an encrypted column cannot be searched or indexed the normal way: a WHERE oauth_token = ... has nothing stable to match.
In my reading, lists of the different types of data encryption that say “four types” or “three types” are regroupings of these same methods. The encryption technologies worth knowing by name are four: TLS, AES-256, a key management service (KMS) and a password hash. TLS, AES-256 and the password hash sit in the table above; the KMS comes up under keys below.
Database encryption techniques: full disk, TDE, column-level and application-level
Database encryption techniques form four levels: full-disk or storage encryption, transparent data encryption by the engine, column-level encryption inside the database, and application-level encryption before the insert. The first two protect stolen media. Only the last two protect a column from someone with a valid login, at the price of search and indexing.
Storage or full-disk encryption is what Supabase, Neon and an encrypted Amazon RDS instance state they do, and it is the at-rest row from the first table. Transparent Data Encryption (TDE) is Microsoft’s term for SQL Server: according to Microsoft’s Transparent Data Encryption docs, TDE “encrypts SQL Server, Azure SQL Database, and Azure Synapse Analytics data files”, and pages “are encrypted before they’re written to disk and are decrypted when read into memory.” Core PostgreSQL is different: PostgreSQL’s encryption options page lists password hashing, encryption for specific columns, data partition encryption at the file-system or block level, network encryption and client-side encryption, and names no transparent data encryption option.
Column-level encryption is data-level encryption inside the database: pgcrypto encrypts chosen values with functions such as pgp_sym_encrypt. Its documentation carries its own caveat: “All pgcrypto functions run inside the database server. That means that all the data and passwords move between pgcrypto and client applications in clear text.” Its advice if you cannot trust the server’s administrators: “better do crypto inside client application.” That is the fourth technique, application-level encryption, where the app encrypts before the insert with a key the database never sees.
| Technique | What it encrypts | Where the key lives | What a valid login sees | Cost in search, indexing and effort |
|---|---|---|---|---|
| Full-disk or storage encryption | The volume under the database files | With the provider or the system that mounts the disk | Plaintext | None for you |
| Transparent data encryption (TDE) | The engine’s data and log files | SQL Server: a database encryption key in the boot record, secured by a certificate or an EKM-protected key | Plaintext | None for queries |
| Column-level (pgcrypto) | Chosen columns | Sent by the client with each call; briefly present on the server | Ciphertext, unless the query passes the key | No range search, sorting or joins on the column; equality search needs a separate keyed hash column (a blind index) |
| Application-level | Chosen values, before the insert | The app’s secret store or a KMS; never the database | Ciphertext | The same as column-level, and every reader of the data needs the key in code |
The cost column is my reading. A database encryption example in one line: an oauth_token column the app writes as ciphertext, so a direct select by anyone without the key returns bytes that open nothing. Whether health columns must be encrypted by law is a different question, answered at whether HIPAA requires application-level encryption for Supabase columns. My working rule for choosing among these database encryption methods: pick the lowest technique that stops the attacker you named in the threat map.
What your platform already does at rest and in transit
What Supabase, Neon, Amazon RDS and Firestore turn on, and whether each enforces TLS, is the provider table on the managed database settings page linked above; it is not repeated here. Supabase’s at-rest statement, and what it does not stop, is quoted in whether the Supabase database is encrypted at rest.
What this page adds is the sentence for a questionnaire. My working rule is to write it like this, with the fields filled in from the next section: “Data is encrypted at rest by our database provider using the algorithm it states, and in transit with TLS; sensitive fields listed below are additionally encrypted in the application.” The browser-side half of in transit, HTTPS and its headers, belongs to content security policy and transport headers.
The fields worth encrypting yourself
Fields worth encrypting in the application are few, and by my working rule 7 classes cover them: hash passwords and reset tokens, never store card numbers, encrypt third-party tokens, identity numbers, bank details and health notes, and leave names and emails to disk encryption plus authorization. Encrypted columns lose normal search, sorting and indexing.
The decisions in the table are my working rule for a small SaaS; the one standards fact in it, the card row, comes from the PCI council’s own questionnaire.
| Field class | Do this | Why | What it costs |
|---|---|---|---|
| Passwords | Hash with a slow, salted algorithm; never encrypt | A login only compares; nothing should read a password back | Nothing extra; the auth layer does it |
| Card numbers | Do not store; let the payment processor hold them | PCI DSS v4.0.1 SAQ A is only for merchants that do not electronically store, process or transmit account data | You depend on the processor’s saved-card feature |
| Third-party OAuth tokens and API keys your users give you | Encrypt in the application | A database read must not become access to their Google or Stripe account | No search on the column; a key read on every use |
| Government identity numbers, bank details | Encrypt in the application; show the last four digits only | The full value is rarely needed after the first check | Lookups need a separate keyed hash column |
| Health notes and other special-category data | Encrypt in the application | The harm of a leak is highest and the field is rarely searched | No full-text search on the notes |
| Password reset and magic-link tokens | Store only a hash | The link only has to be compared once | Nothing extra |
| Emails and names | Usually leave them to disk encryption plus authorization | Search, login and support need them readable | None |
The card row needs one sentence of support: the PCI council’s Self-Assessment Questionnaire A for PCI DSS v4.0.1 assumes the merchant’s own systems hold no card data at all and rely entirely on outside providers for every card-handling function, so a table of card numbers rules it out.
How to encrypt data in one of these columns is my working rule in one line: encrypt in the app before the insert, store the key version beside the ciphertext, and decrypt only in the code path that needs the value. Soft-deleted rows still hold every sensitive column, so the same decision applies to them (a separate question: what soft delete is). Health records carry wider rules than this table, set out in making an app HIPAA ready. Deciding which fields your app holds in the first place is the job of a GDPR data map.
User encryption in the end-to-end sense is a different design: each user holds their own key and the server cannot read the data at all. In my reading it removes password reset, server-side search and support access to the content, so it fits a notes or messaging product and almost nothing else a small SaaS sells.
Keys: where they live, who can decrypt, and rotation
An encryption key never lives beside the data it protects. A small stack has three workable homes: the host’s secret store, a cloud key management service using envelope encryption, or the platform’s vault. Store a key version with each ciphertext so rotation never strands old rows.
The rule is my working rule, and it has three negatives: not in a database table, not in the repository, not in a client bundle. The minimum home is the host’s secret store, read as an environment variable by server code only. A step up is a KMS with envelope encryption, which AWS KMS’s cryptography basics describe as “the practice of encrypting plaintext data with a data key, and then encrypting the data key under another key”; the top key stays in the KMS, and your database holds only wrapped data keys. On Supabase, Supabase Vault stores secrets “using Authenticated Encryption on disk”, and its docs say “the encryption key is never stored in the database alongside the encrypted data.” The same docs list pgsodium as “pending deprecation”.
Who can decrypt is a list of services and people, written down, and it should be short. For rotation, version the key id alongside each ciphertext so old rows stay readable while new writes use the new key, then rehearse the switch before you need it; the steps are in how to rotate API keys safely. Where secrets of every kind should live is the subject of secrets management best practices. Losing the key is losing the data, so the key belongs in the recovery plan, stored separately from the backups.
How to check your own app
Database encryption is verified with five checks: the provider’s dated at-rest statement, ciphertext or a hash in every sensitive column, a known test value that is unreadable in the table and readable through the app, the key found only in the secret store, and one rotation rehearsed on staging.
Whether the database enforces TLS is the settings page’s check, so it is not repeated here. Run these five on a staging copy, and keep the evidence each one names.
- 01 Find your provider's at-rest statement for your plan and region and save the page with the date. On Amazon RDS, open the instance, choose the Configuration tab and read the Encryption value under Storage. Fail: no statement found, or the value reads Not enabled. Evidence: the dated page or a screenshot of your own console.
- 02 For each column from the field decision table that exists in your schema, select five rows using the database role your server code uses. Fail: a readable token, identity number or reset token. Evidence: the query and the ciphertext or hash it returned.
- 03 Write a known test value through the app into one protected column, read the stored column directly, then read it back through the app. Fail: the stored value equals the test value, or the app cannot read it back. Evidence: the two reads, dated.
- 04 Search the repository, the built client bundle and the database for the encryption key's variable name and a known fragment of its value. Fail: any hit outside the secret store. Evidence: the search output.
- 05 On staging, rotate the key and read one old and one new record. Fail: an old record no longer decrypts, or nobody knows the steps. Evidence: the dated runbook step.
Two of these checks match how the Production Hardening Sprint verifies its own work. Deliverable 2.1, Frontend secret removal, is verified this way: “Scan source and built assets and inspect browser traffic for private credentials.” Deliverable 2.6, Secret rotation runbook, is verified this way: “Rehearse rotation with a test credential and record the affected services and checks.”
Where the sprint fits
No deliverable in the Production Hardening Sprint is named database encryption; the four nearest cover managed settings, keys and personal data. Deliverable 2.9 reviews and hardens the settings of every managed service in the stack: auth policies, storage access rules, privileged keys kept server-side, and a plan tier adequate for backups and recovery. Keys are covered twice: deliverable 2.1 removes all private secrets from frontend code and downloadable bundles, relocating their use to server-side code, and deliverable 2.6 documents how to rotate each key, update its dependents, verify the change, and recover from a failed rotation. For personal data, deliverable 12.1 documents what personal data is stored, where it lives, why it is collected, and who can access it. Legal advice and certification are separate services; the sprint implements and documents the technical data-handling controls. Each deliverable’s full wording is in the published scope.
Common questions about encrypting data
Which is better, AES or RSA?
Neither is better, because they do different jobs. AES is a symmetric cipher that encrypts the data itself, at rest and inside a TLS connection; RSA is an asymmetric algorithm used to authenticate the other side and to sign, alongside elliptic-curve algorithms such as ECDSA. A TLS 1.3 connection can use both kinds: asymmetric cryptography to authenticate, and AES to protect the traffic.
TLS 1.3 also dropped RSA as a way to exchange keys: its standard says “Static RSA and Diffie-Hellman cipher suites have been removed.”
Is AES-256 obsolete?
No. AES is on NIST’s current list of approved block ciphers, and AES-256 is one of the three key sizes its standard, FIPS 197, specifies alongside AES-128 and AES-192. “Military grade” is a marketing phrase for the same algorithm, in my reading, not a separate standard.
The numeral in the name is the key length: FIPS 197 says the numerical suffix indicates the bit length of the associated cryptographic keys.
What is replacing RSA?
NIST’s post-quantum standards, released in August 2024: FIPS 203 (ML-KEM) for key establishment, and FIPS 204 (ML-DSA) and FIPS 205 (SLH-DSA) for digital signatures. NIST states it will deprecate and ultimately remove quantum-vulnerable algorithms from its standards by 2035, with high-risk systems moving much earlier.
The details are on NIST’s post-quantum cryptography project page, and NIST IR 8547, still an initial public draft, lists RSA signatures at 112 bits of security as “Deprecated after 2030” and all RSA signatures as “Disallowed after 2035”. In my reading, a web app inherits the change through its TLS libraries and its platform rather than through its own code.
Does PII data need to be encrypted?
Not in every case under EU GDPR. Article 32(1)(a) lists “the pseudonymisation and encryption of personal data” among the measures to use “as appropriate” for a level of security appropriate to the risk, which makes encryption one example of an appropriate measure rather than an absolute duty. Article 34(3)(a) says telling affected people about a breach is not required when measures rendered the data “unintelligible to any person who is not authorised to access it, such as encryption”.
Contracts and sector rules can require more than the regulation does. This is not legal advice.
How to tell if a SQL database is encrypted?
On SQL Server, Microsoft points to the sys.dm_database_encryption_keys dynamic management view “to find the state of database encryption.” On a managed Postgres, read your provider’s at-rest statement; on Amazon RDS, each instance’s Configuration tab in the console reports whether storage encryption is enabled.
That RDS field is the first check in the list above, with the exact place to read it.
The checks in this guide show you where the app is open. The sprint below closes those gaps, tests the result and writes the evidence down.
Built it with AI. Now it has to hold up for real customers.
The Production Hardening Sprint takes the app you already have and builds the production foundation underneath it. Authentication and access rules, payments that stay consistent, error handling, monitoring, backups, automated tests and a documented handover. Our engineers work inside your existing codebase for ten working days. All 123 deliverables are included, and you get the evidence for each one.
See the Production Hardening Sprint →
$2,500 fixed price · 10 working days · One codebase