Konfigurera Always Encrypted med hjälp av Azure Key Vault

GÄLLER FÖR: Azure SQL Database Azure SQL Managed Instance

Den här artikeln visar hur du skyddar känsliga data i en databas i Azure SQL Database med datakryptering med hjälp av Always Encrypted-guiden i SQL Server Management Studio (SSMS). Den innehåller också instruktioner som visar hur du lagrar varje krypteringsnyckel i Azure Key Vault.

Always Encrypted är en datakrypteringsteknik som hjälper till att skydda känsliga data i vila på servern, under förflyttning mellan klient och server och när data används. Always Encrypted ser till att känsliga data aldrig visas i klartext i databassystemet. När du har konfigurerat datakryptering kan endast klientprogram eller appservrar som har åtkomst till nycklarna komma åt klartextdata. Detaljerad information finns i Always Encrypted (databasmotor).

När du har konfigurerat databasen för Always Encrypted ska du skapa ett klientprogram i C# med Visual Studio att arbeta med krypterade data.

Följ stegen i den här artikeln och lär dig att konfigurera Always Encrypted för din databas i Azure SQL Database eller SQL Managed Instance. I den här artikeln får du lära dig hur du utför följande uppgifter:

Förutsättningar

Aktivera åtkomst till klientprogram

Du måste aktivera klientprogrammet för att få åtkomst till databasen i SQL Database genom att konfigurera ett Azure Active Directory-program (Azure AD) och kopiera det program-ID och den nyckel som du behöver för att autentisera ditt program.

Hämta program-ID:t och nyckeln genom att följa stegen i skapa ett Azure Active Directory program och tjänstens huvudnamn som har åtkomst till resurser.

Skapa ett nyckelvalv för att lagra dina nycklar

Nu när klientappen har konfigurerats och du har ditt program-ID är det dags att skapa ett nyckelvalv och konfigurera dess åtkomstprincip så att du och programmet kan komma åt valvets hemligheter (Always Encrypted-nycklarna). Behörigheterna create, get, list, sign, verify, wrapKey och unwrapKey krävs för att skapa en ny kolumn huvudnyckel och för att konfigurera kryptering med SQL Server Management Studio.

Du kan snabbt skapa ett nyckelvalv genom att köra följande skript. En detaljerad förklaring av dessa kommandon och mer information om hur du skapar och konfigurerar ett nyckelvalv finns i Vad är Azure Key Vault?.

Viktigt

Modulen PowerShell Azure Resource Manager (RM) stöds fortfarande av Azure SQL Database, men all framtida utveckling är för Az.Sql-modulen. AzureRM-modulen fortsätter att ta emot felkorrigeringar fram till åtminstone december 2020. Argumenten för kommandona i Az-modulen och i AzureRm-modulerna är betydligt identiska. Mer information om deras kompatibilitet finns i Introduktion till den nya Azure PowerShell Az-modulen.

$subscriptionName = '<subscriptionName>'
$userPrincipalName = '<username@domain.com>'
$applicationId = '<applicationId from AAD application>'
$resourceGroupName = '<resourceGroupName>' # use the same resource group name when creating your SQL Database below
$location = '<datacenterLocation>'
$vaultName = '<vaultName>'

Connect-AzAccount
$subscriptionId = (Get-AzSubscription -SubscriptionName $subscriptionName).Id
Set-AzContext -SubscriptionId $subscriptionId

New-AzResourceGroup -Name $resourceGroupName -Location $location
New-AzKeyVault -VaultName $vaultName -ResourceGroupName $resourceGroupName -Location $location

Set-AzKeyVaultAccessPolicy -VaultName $vaultName -ResourceGroupName $resourceGroupName -PermissionsToKeys create,get,wrapKey,unwrapKey,sign,verify,list -UserPrincipalName $userPrincipalName
Set-AzKeyVaultAccessPolicy  -VaultName $vaultName  -ResourceGroupName $resourceGroupName -ServicePrincipalName $applicationId -PermissionsToKeys get,wrapKey,unwrapKey,sign,verify,list

Anslut med SSMS

Öppna SQL Server Management Studio (SSMS) och anslut till servern eller hanteras med din databas.

  1. Öppna SSMS. (Gå till Anslut > Databasmotor för att öppna fönstret Anslut till server om det inte är öppet.)

  2. Ange servernamnet eller instansnamnet och autentiseringsuppgifterna.

    Kopiera anslutningssträngen

Om fönstret Ny brandväggsregel öppnas loggar du in på Azure och låter SSMS skapa en ny brandväggsregel åt dig.

Skapa en tabell

I det här avsnittet skapar du en tabell för att lagra patientdata. Den är inte krypterad inledningsvis – du konfigurerar krypteringen i nästa avsnitt.

  1. Expandera Databaser.
  2. Högerklicka på databasen och klicka på Ny fråga.
  3. Klistra in följande Transact-SQL (T-SQL) i det nya frågefönstret och kör det.
CREATE TABLE [dbo].[Patients](
         [PatientId] [int] IDENTITY(1,1),
         [SSN] [char](11) NOT NULL,
         [FirstName] [nvarchar](50) NULL,
         [LastName] [nvarchar](50) NULL,
         [MiddleName] [nvarchar](50) NULL,
         [StreetAddress] [nvarchar](50) NULL,
         [City] [nvarchar](50) NULL,
         [ZipCode] [char](5) NULL,
         [State] [char](2) NULL,
         [BirthDate] [date] NOT NULL
         PRIMARY KEY CLUSTERED ([PatientId] ASC) ON [PRIMARY] );
GO

Kryptera kolumner (konfigurera Always Encrypted)

SSMS innehåller en guide som hjälper dig att enkelt konfigurera Always Encrypted genom att konfigurera kolumnens huvudnyckel, kolumnkrypteringsnyckel och krypterade kolumner åt dig.

  1. Expandera Databases > Clinic > Tables.

  2. Högerklicka på tabellen Patienter och välj Kryptera kolumner för att öppna Always Encrypted guiden:

    Skärmbild som visar krypterade kolumner... menyalternativ.

Guiden Always Encrypted följande avsnitt: Kolumnval, Konfiguration av huvudnyckel, Validering och Sammanfattning.

Kolumnval

Klicka Nästa på sidan Introduktion för att öppna sidan Kolumnval. På den här sidan väljer du vilka kolumner som du vill kryptera, typen av kryptering och vilken kolumnkrypteringsnyckel (CEK) som ska användas.

Kryptera SSN- och BirthDate-information för varje patient. SSN-kolumnen använder deterministisk kryptering, som stöder likhetsuppslag, kopplingar och gruppera efter. Kolumnen BirthDate använder slumpmässig kryptering, som inte stöder åtgärder.

Ange Krypteringstyp för SSN-kolumnen till Deterministisk och kolumnen BirthDate till Randomized. Klicka på Nästa.

Kryptera kolumner

Konfiguration av huvudnyckel

Sidan Konfiguration av huvudnyckel är där du ställer in din CMK och väljer den nyckellagerprovider där CMK ska lagras. För närvarande kan du lagra en CMK i Windows-certifikatarkivet, Azure Key Vault eller en maskinvarusäkerhetsmodul (HSM).

Den här självstudien visar hur du lagrar dina nycklar i Azure Key Vault.

  1. Välj Azure Key Vault.
  2. Välj önskat nyckelvalv i listrutan.
  3. Klicka på Nästa.

Konfiguration av huvudnyckel

Validering

Du kan kryptera kolumnerna nu eller spara ett PowerShell-skript för senare körning. För den här självstudien väljer du Fortsätt för att slutföra nu och klickar på Nästa.

Sammanfattning

Kontrollera att inställningarna är korrekta och klicka på Slutför för att slutföra konfigurationen för Always Encrypted.

Skärmbild som visar resultatsidan med uppgifter som markerats som skickade.

Verifiera guidens åtgärder

När guiden är klar har databasen ställts in för Always Encrypted. Guiden utförde följande åtgärder:

  • Skapat en kolumn huvudnyckel och lagrat den i Azure Key Vault.
  • Skapat en kolumnkrypteringsnyckel och lagrat den i Azure Key Vault.
  • Konfigurerade de valda kolumnerna för kryptering. Tabellen Patienter har för närvarande inga data, men alla befintliga data i de valda kolumnerna krypteras nu.

Du kan verifiera skapandet av nycklarna i SSMS genom att expandera Clinic > Security > Always Encrypted Keys.

Skapa ett klientprogram som fungerar med krypterade data

Nu när Always Encrypted har ställts in kan du skapa ett program som utför infogningar och väljer på de krypterade kolumnerna.

Viktigt

Programmet måste använda SqlParameter-objekt när data i klartext ska skickas till servern med Always Encrypted kolumner. Att skicka literalvärden utan att använda SqlParameter-objekt resulterar i ett undantag.

  1. Öppna Visual Studio och skapa ett nytt C#-konsolprogram (Visual Studio 2015 och tidigare) eller Konsolapp (.NET Framework) (Visual Studio 2017 och senare). Kontrollera att projektet är inställt på .NET Framework 4.6 eller senare.
  2. Ge projektet namnet AlwaysEncryptedConsoleAKVApp och klicka på OK.
  3. Installera följande NuGet-paket genom att gå till Tools > NuGet Package Manager Package Manager > Console.

Kör följande två kodrader i Package Manager-konsolen:

Install-Package Microsoft.SqlServer.Management.AlwaysEncrypted.AzureKeyVaultProvider
Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory

Ändra anslutningssträngen för att aktivera Always Encrypted

Det här avsnittet beskriver hur du aktiverar Always Encrypted i databasanslutningssträngen.

Om du Always Encrypted måste du lägga till nyckelordet Kolumnkrypteringsinställning i anslutningssträngen och ställa in den på Aktiverad.

Du kan ange detta direkt i anslutningssträngen, eller så kan du ange det med hjälp av SqlConnectionStringBuilder. Exempelprogrammet i nästa avsnitt visar hur du använder SqlConnectionStringBuilder.

Aktivera Always Encrypted i anslutningssträngen

Lägg till följande nyckelord i anslutningssträngen.

Column Encryption Setting=Enabled

Aktivera Always Encrypted med SqlConnectionStringBuilder

Följande kod visar hur du aktiverar Always Encrypted genom att ange SqlConnectionStringBuilder.ColumnEncryptionSetting till Aktiverad.

// Instantiate a SqlConnectionStringBuilder.
SqlConnectionStringBuilder connStringBuilder = new SqlConnectionStringBuilder("replace with your connection string");

// Enable Always Encrypted.
connStringBuilder.ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Enabled;

Registrera Azure Key Vault providern

Följande kod visar hur du registrerar Azure Key Vault providern med ADO.NET drivrutin.

private static ClientCredential _clientCredential;

static void InitializeAzureKeyVaultProvider() {
    _clientCredential = new ClientCredential(applicationId, clientKey);

    SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider(GetToken);

    Dictionary<string, SqlColumnEncryptionKeyStoreProvider> providers = new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>();

    providers.Add(SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider);
    SqlConnection.RegisterColumnEncryptionKeyStoreProviders(providers);
}

Always Encrypted exempelkonsolprogram

Det här exemplet visar hur du:

  • Ändra anslutningssträngen för att aktivera Always Encrypted.
  • Registrera Azure Key Vault som programmets nyckellagerprovider.
  • Infoga data i de krypterade kolumnerna.
  • Välj en post genom att filtrera efter ett specifikt värde i en krypterad kolumn.

Ersätt innehållet i Program.cs med följande kod. Ersätt anslutningssträngen för den globala connectionString-variabeln på raden som direkt föregår Main-metoden med din giltiga anslutningssträng från Azure Portal. Det här är den enda ändring du behöver göra i den här koden.

Kör appen för att Always Encrypted i praktiken.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
using System.Data.SqlClient;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.SqlServer.Management.AlwaysEncrypted.AzureKeyVaultProvider;

namespace AlwaysEncryptedConsoleAKVApp {
    class Program {
        // Update this line with your Clinic database connection string from the Azure portal.
        static string connectionString = @"<connection string from the portal>";
        static string applicationId = @"<application ID from your AAD application>";
        static string clientKey = "<key from your AAD application>";

        static void Main(string[] args) {
            InitializeAzureKeyVaultProvider();

            Console.WriteLine("Signed in as: " + _clientCredential.ClientId);

            Console.WriteLine("Original connection string copied from the Azure portal:");
            Console.WriteLine(connectionString);

            // Create a SqlConnectionStringBuilder.
            SqlConnectionStringBuilder connStringBuilder =
                new SqlConnectionStringBuilder(connectionString);

            // Enable Always Encrypted for the connection.
            // This is the only change specific to Always Encrypted
            connStringBuilder.ColumnEncryptionSetting =
                SqlConnectionColumnEncryptionSetting.Enabled;

            Console.WriteLine(Environment.NewLine + "Updated connection string with Always Encrypted enabled:");
            Console.WriteLine(connStringBuilder.ConnectionString);

            // Update the connection string with a password supplied at runtime.
            Console.WriteLine(Environment.NewLine + "Enter server password:");
            connStringBuilder.Password = Console.ReadLine();

            // Assign the updated connection string to our global variable.
            connectionString = connStringBuilder.ConnectionString;

            // Delete all records to restart this demo app.
            ResetPatientsTable();

            // Add sample data to the Patients table.
            Console.Write(Environment.NewLine + "Adding sample patient data to the database...");

            InsertPatient(new Patient() {
                SSN = "999-99-0001",
                FirstName = "Orlando",
                LastName = "Gee",
                BirthDate = DateTime.Parse("01/04/1964")
            });
            InsertPatient(new Patient() {
                SSN = "999-99-0002",
                FirstName = "Keith",
                LastName = "Harris",
                BirthDate = DateTime.Parse("06/20/1977")
            });
            InsertPatient(new Patient() {
                SSN = "999-99-0003",
                FirstName = "Donna",
                LastName = "Carreras",
                BirthDate = DateTime.Parse("02/09/1973")
            });
            InsertPatient(new Patient() {
                SSN = "999-99-0004",
                FirstName = "Janet",
                LastName = "Gates",
                BirthDate = DateTime.Parse("08/31/1985")
            });
            InsertPatient(new Patient() {
                SSN = "999-99-0005",
                FirstName = "Lucy",
                LastName = "Harrington",
                BirthDate = DateTime.Parse("05/06/1993")
            });

            // Fetch and display all patients.
            Console.WriteLine(Environment.NewLine + "All the records currently in the Patients table:");

            foreach (Patient patient in SelectAllPatients()) {
                Console.WriteLine(patient.FirstName + " " + patient.LastName + "\tSSN: " + patient.SSN + "\tBirthdate: " + patient.BirthDate);
            }

            // Get patients by SSN.
            Console.WriteLine(Environment.NewLine + "Now lets locate records by searching the encrypted SSN column.");

            string ssn;

            // This very simple validation only checks that the user entered 11 characters.
            // In production be sure to check all user input and use the best validation for your specific application.
            do {
                Console.WriteLine("Please enter a valid SSN (ex. 999-99-0003):");
                ssn = Console.ReadLine();
            } while (ssn.Length != 11);

            // The example allows duplicate SSN entries so we will return all records
            // that match the provided value and store the results in selectedPatients.
            Patient selectedPatient = SelectPatientBySSN(ssn);

            // Check if any records were returned and display our query results.
            if (selectedPatient != null) {
                Console.WriteLine("Patient found with SSN = " + ssn);
                Console.WriteLine(selectedPatient.FirstName + " " + selectedPatient.LastName + "\tSSN: "
                    + selectedPatient.SSN + "\tBirthdate: " + selectedPatient.BirthDate);
            }
            else {
                Console.WriteLine("No patients found with SSN = " + ssn);
            }

            Console.WriteLine("Press Enter to exit...");
            Console.ReadLine();
        }

        private static ClientCredential _clientCredential;

        static void InitializeAzureKeyVaultProvider() {
            _clientCredential = new ClientCredential(applicationId, clientKey);

            SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider =
              new SqlColumnEncryptionAzureKeyVaultProvider(GetToken);

            Dictionary<string, SqlColumnEncryptionKeyStoreProvider> providers =
              new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>();

            providers.Add(SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider);
            SqlConnection.RegisterColumnEncryptionKeyStoreProviders(providers);
        }

        public async static Task<string> GetToken(string authority, string resource, string scope) {
            var authContext = new AuthenticationContext(authority);
            AuthenticationResult result = await authContext.AcquireTokenAsync(resource, _clientCredential);

            if (result == null)
                throw new InvalidOperationException("Failed to obtain the access token");
            return result.AccessToken;
        }

        static int InsertPatient(Patient newPatient) {
            int returnValue = 0;

            string sqlCmdText = @"INSERT INTO [dbo].[Patients] ([SSN], [FirstName], [LastName], [BirthDate])
     VALUES (@SSN, @FirstName, @LastName, @BirthDate);";

            SqlCommand sqlCmd = new SqlCommand(sqlCmdText);

            SqlParameter paramSSN = new SqlParameter(@"@SSN", newPatient.SSN);
            paramSSN.DbType = DbType.AnsiStringFixedLength;
            paramSSN.Direction = ParameterDirection.Input;
            paramSSN.Size = 11;

            SqlParameter paramFirstName = new SqlParameter(@"@FirstName", newPatient.FirstName);
            paramFirstName.DbType = DbType.String;
            paramFirstName.Direction = ParameterDirection.Input;

            SqlParameter paramLastName = new SqlParameter(@"@LastName", newPatient.LastName);
            paramLastName.DbType = DbType.String;
            paramLastName.Direction = ParameterDirection.Input;

            SqlParameter paramBirthDate = new SqlParameter(@"@BirthDate", newPatient.BirthDate);
            paramBirthDate.SqlDbType = SqlDbType.Date;
            paramBirthDate.Direction = ParameterDirection.Input;

            sqlCmd.Parameters.Add(paramSSN);
            sqlCmd.Parameters.Add(paramFirstName);
            sqlCmd.Parameters.Add(paramLastName);
            sqlCmd.Parameters.Add(paramBirthDate);

            using (sqlCmd.Connection = new SqlConnection(connectionString)) {
                try {
                    sqlCmd.Connection.Open();
                    sqlCmd.ExecuteNonQuery();
                }
                catch (Exception ex) {
                    returnValue = 1;
                    Console.WriteLine("The following error was encountered: ");
                    Console.WriteLine(ex.Message);
                    Console.WriteLine(Environment.NewLine + "Press Enter key to exit");
                    Console.ReadLine();
                    Environment.Exit(0);
                }
            }
            return returnValue;
        }


        static List<Patient> SelectAllPatients() {
            List<Patient> patients = new List<Patient>();

            SqlCommand sqlCmd = new SqlCommand(
              "SELECT [SSN], [FirstName], [LastName], [BirthDate] FROM [dbo].[Patients]",
                new SqlConnection(connectionString));

            using (sqlCmd.Connection = new SqlConnection(connectionString))

            using (sqlCmd.Connection = new SqlConnection(connectionString)) {
                try {
                    sqlCmd.Connection.Open();
                    SqlDataReader reader = sqlCmd.ExecuteReader();

                    if (reader.HasRows) {
                        while (reader.Read()) {
                            patients.Add(new Patient() {
                                SSN = reader[0].ToString(),
                                FirstName = reader[1].ToString(),
                                LastName = reader["LastName"].ToString(),
                                BirthDate = (DateTime)reader["BirthDate"]
                            });
                        }
                    }
                }
                catch (Exception ex) {
                    throw;
                }
            }

            return patients;
        }

        static Patient SelectPatientBySSN(string ssn) {
            Patient patient = new Patient();

            SqlCommand sqlCmd = new SqlCommand(
                "SELECT [SSN], [FirstName], [LastName], [BirthDate] FROM [dbo].[Patients] WHERE [SSN]=@SSN",
                new SqlConnection(connectionString));

            SqlParameter paramSSN = new SqlParameter(@"@SSN", ssn);
            paramSSN.DbType = DbType.AnsiStringFixedLength;
            paramSSN.Direction = ParameterDirection.Input;
            paramSSN.Size = 11;

            sqlCmd.Parameters.Add(paramSSN);

            using (sqlCmd.Connection = new SqlConnection(connectionString)) {
                try {
                    sqlCmd.Connection.Open();
                    SqlDataReader reader = sqlCmd.ExecuteReader();

                    if (reader.HasRows) {
                        while (reader.Read()) {
                            patient = new Patient() {
                                SSN = reader[0].ToString(),
                                FirstName = reader[1].ToString(),
                                LastName = reader["LastName"].ToString(),
                                BirthDate = (DateTime)reader["BirthDate"]
                            };
                        }
                    }
                    else {
                        patient = null;
                    }
                }
                catch (Exception ex) {
                    throw;
                }
            }
            return patient;
        }

        // This method simply deletes all records in the Patients table to reset our demo.
        static int ResetPatientsTable() {
            int returnValue = 0;

            SqlCommand sqlCmd = new SqlCommand("DELETE FROM Patients");
            using (sqlCmd.Connection = new SqlConnection(connectionString)) {
                try {
                    sqlCmd.Connection.Open();
                    sqlCmd.ExecuteNonQuery();

                }
                catch (Exception ex) {
                    returnValue = 1;
                }
            }
            return returnValue;
        }
    }

    class Patient {
        public string SSN { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime BirthDate { get; set; }
    }
}

Kontrollera att data är krypterade

Du kan snabbt kontrollera att faktiska data på servern krypteras genom att fråga patientdata med SSMS (med din aktuella anslutning där kolumnkrypteringsinställningen inte är aktiverad ännu).

Kör följande fråga på databasen Clinic.

SELECT FirstName, LastName, SSN, BirthDate FROM Patients;

Du kan se att de krypterade kolumnerna inte innehåller några klartextdata.

Skärmbild som visar att de krypterade kolumnerna inte innehåller några klartextdata.

Om du vill använda SSMS för att få åtkomst till klartextdata måste du först se till att användaren har rätt behörigheter till Azure Key Vault: hämta, unwrapKey och verifiera. Detaljerad information finns i Skapa och lagra huvudnycklar för kolumner (Always Encrypted).

Lägg sedan till parametern Column Encryption Setting=enabled under anslutningen.

  1. I SSMS högerklickar du på servern i Object Explorer väljer Koppla från.

  2. Klicka > Anslut databasmotor för att öppna fönstret Anslut till server och klicka på Alternativ.

  3. Klicka på Ytterligare anslutningsparametrar och skriv Column Encryption Setting=enabled.

    Skärmbild som visar fliken Ytterligare korrigeringsparametrar.

  4. Kör följande fråga på databasen Clinic.

    SELECT FirstName, LastName, SSN, BirthDate FROM Patients;
    

    Nu kan du se klartextdata i de krypterade kolumnerna.

    Nytt konsolprogram

Nästa steg

När databasen har konfigurerats att använda Always Encrypted kanske du vill göra följande: