How Always Encrypted keeps keys away from the database engine, deterministic versus randomised encryption, and secure enclaves.
From Ultra Transcenders DP-800 by Tony Rough (coming December 2026)
Always Encrypted encrypts selected columns inside the client driver, so plaintext data and keys never appear in the Database Engine. It separates data owners from data managers such as on-premises DBAs and cloud operators. It’s available in SQL Server, Azure SQL Database and Azure SQL Managed Instance; it isn’t available in SQL database in Fabric. Figure 7.1 shows where the keys live and what each side sees.
Always Encrypted uses two kinds of keys:
The database stores only metadata: the CMK’s location and the encrypted value of each CEK. Because the engine must never see keys in plaintext, T-SQL can’t provision keys or encrypt existing data; SSMS, PowerShell or SqlPackage do it outside the database. Four database permissions govern the feature.
| Permission | Needed for |
|---|---|
ALTER ANY COLUMN MASTER KEY |
Creating and dropping CMK metadata |
ALTER ANY COLUMN ENCRYPTION KEY |
Creating and dropping CEK metadata |
VIEW ANY COLUMN MASTER KEY DEFINITION |
Querying encrypted columns |
VIEW ANY COLUMN ENCRYPTION KEY DEFINITION |
Querying encrypted columns |
In SQL Server the public role holds the two VIEW ANY permissions by default; in Azure SQL Database it doesn’t, so they must be granted explicitly before users can work with encrypted columns.
Both types use the AEAD_AES_256_CBC_HMAC_SHA_256 algorithm; the choice is about what queries remain possible.
| Aspect | Deterministic | Randomized |
|---|---|---|
| Same plaintext gives same ciphertext | Yes | No |
| Operations without enclaves | Equality (=), IN, GROUP BY, DISTINCT, equality joins, indexing |
None |
| Leaks patterns | Yes, especially with few distinct values (True/False, regions) | No |
| Character column collation | Must be a _BIN2 collation |
_BIN2 required for Always Encrypted columns generally |
| Typical use without enclaves | Values searched or joined, such as a national ID | Values never searched, such as a card number found by another key |
| Recommended with secure enclaves | No | Yes |
Joining two deterministically encrypted columns needs both to use the same CEK. The documented operand clash error (Msg 206) appears when a query compares an encrypted column with a literal or a plaintext column, or copies data between encrypted and plaintext columns with INSERT...SELECT, SELECT INTO, UPDATE or BULK INSERT. Applications must pass values as parameters; in SSMS, turn on Parameterization for Always Encrypted.
CREATE TABLE dbo.Patients (
PatientId int IDENTITY PRIMARY KEY,
NationalId char(11) COLLATE Latin1_General_BIN2
ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = CEK1,
ENCRYPTION_TYPE = DETERMINISTIC,
ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL,
BirthDate date
ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = CEK1,
ENCRYPTION_TYPE = RANDOMIZED,
ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
);NationalId supports point lookups and an index; BirthDate can only be stored and returned. The client turns the feature on with Column Encryption Setting=Enabled in a Microsoft.Data.SqlClient connection string (ODBC uses ColumnEncryption=Enabled), and needs access to the CMK in its key store; the Azure Key Vault provider for .NET ships as a separate NuGet package.
Always Encrypted can’t be applied to columns that are xml, rowversion, sql_variant, hierarchyid, spatial or vector types; that have IDENTITY or a DEFAULT constraint; that are referenced by CHECK constraints, used as partitioning columns, included in full-text indexes, masked with DDM, or captured by change data capture. Queries using FOR JSON or FOR XML aren’t supported, nor are table-valued parameters targeting encrypted columns. Randomized columns can’t be primary keys, index keys or unique-constraint columns. Transactional, merge and snapshot replication don’t work on encrypted columns, although availability groups do. After changing an encrypted column, run sp_refresh_parameter_encryption for dependent modules.
A secure enclave is a protected memory region inside the engine process where the driver can share CEKs so the server can compute on plaintext. It adds rich confidential queries and in-place encryption: encrypting, re-encrypting (key rotation or type change) and decrypting a column with ALTER TABLE ... ALTER COLUMN without moving data to the client.
| Operation on enclave-enabled randomized columns | Azure SQL Database | SQL Server 2022 and later | SQL Server 2019 |
|---|---|---|---|
Comparison operators, BETWEEN, IN, LIKE, DISTINCT |
Supported | Supported | Supported |
| Joins | Supported | Supported | Nested loop joins only |
ORDER BY, GROUP BY |
Supported | Supported | Not supported |
Key facts:
ENCLAVE_COMPUTATIONS); deterministic columns still allow only equality.Common trap: Expecting range or
LIKEqueries on a deterministically encrypted column once enclaves are enabled - enclave computations require randomized encryption; deterministic columns remain limited to equality.
Common trap: Planning Always Encrypted for SQL database in Microsoft Fabric - the Fabric limitations page lists Always Encrypted as not supported and says Always Encrypted tables can’t be created there; use cell-level encryption or keep the data in Azure SQL Database.
This note is one section of Ultra Transcenders DP-800: Developing AI-Enabled Database Solutions, an independent study guide that explains every topic the exam covers by technology, with comparison tables, diagrams and the common traps, plus a glossary linked to Microsoft Learn.
Due on Amazon in December 2026, in Kindle and paperback editions.
About the book · DP-800 terms in the glossary · All DP-800 study notes
How each isolation level trades consistency for concurrency, when to use RCSI or snapshot isolation, and which anomalies each one prevents.
How to build row-level security with an inline predicate function and a security policy, and how filter and block predicates differ.
How the OVER clause partitions, orders and frames rows, and how ROWS and RANGE frames decide which rows each calculation sees.
How temporal tables keep row history automatically, and how FOR SYSTEM_TIME AS OF, BETWEEN and ALL query it.
When exact k-nearest-neighbour search is enough, when an approximate DiskANN vector index pays off, and why its metric must match the query.
How to combine full-text and vector search results with reciprocal rank fusion in T-SQL, and why RRF uses ranks rather than raw scores.
Choosing between change event streaming, change data capture, change tracking, Azure Functions and Logic Apps to react to row changes.