sql >> Databasteknik >  >> RDS >> Database

The Internals of WITH CRYPTION

Det är ganska enkelt för en SQL Server-administratör att återställa texten från lagrade procedurer, vyer, funktioner och utlösare som skyddas med WITH ENCRYPTION . Många artiklar har skrivits om detta, och flera kommersiella verktyg finns tillgängliga. Grundläggandet av den vanliga metoden är att:

  1. Hämta det krypterade formuläret (A) med den dedikerade administratörsanslutningen.
  2. Starta en transaktion.
  3. Ersätt objektdefinitionen med känd text (B) av minst samma längd som originalet.
  4. Hämta det krypterade formuläret för den kända texten (C).
  5. Rolla tillbaka transaktionen för att lämna målobjektet i dess initiala tillstånd.
  6. Få det okrypterade originalet genom att använda ett exklusivt-eller på varje tecken:A XOR (B XOR C)

Det är ganska okomplicerat, men det verkar lite som magi:det förklarar inte mycket om hur och varför det fungerar . Den här artikeln tar upp den aspekten för er som tycker att den här typen av detaljer är intressanta och ger en alternativ metod för dekryptering som är mer illustrativ för processen.

Strömningschifferet

Den underliggande krypteringsalgoritmen SQL Server använder för modulkryptering är RC4™-strömchifferet. En översikt över krypteringsprocessen är:

  1. Initiera RC4-chifferet med en kryptografisk nyckel.
  2. Generera en pseudoslumpmässig ström av byte.
  3. Kombinera modulens ren text med byteströmmen med hjälp av exklusiv-eller.

Vi kan se denna process ske med hjälp av en debugger och offentliga symboler. Till exempel visar stackspårningen nedan att SQL Server initierar RC4-nyckeln samtidigt som man förbereder kryptering av modultexten:

Den här nästa visar SQL Server som krypterar texten med hjälp av RC4 pseudoslumpmässiga byteström:

Liksom de flesta strömchiffer, är dekrypteringsprocessen densamma som kryptering, och använder sig av det faktum att exklusiv-eller är reversibel (A XOR B XOR B = A ).

Användningen av ett strömchiffer är anledningen till exklusiv-eller används i metoden som beskrivs i början av artikeln. Det finns inget i sig osäkert med att använda exklusiv-eller, förutsatt att en säker krypteringsmetod används, initieringsnyckeln hålls hemlig och nyckeln inte återanvänds.

RC4 är inte speciellt stark, men det är inte huvudfrågan här. Med det sagt är det värt att notera att kryptering med RC4 gradvis tas bort från SQL Server och föråldras (eller inaktiveras, beroende på version och databaskompatibilitetsnivå) för användaroperationer som att skapa en symmetrisk nyckel.

RC4-initieringsnyckeln

SQL Server använder tre delar av information för att generera nyckeln som används för att initiera RC4-strömchifferet:

  1. Databasfamiljen GUID.

    Detta fås enklast genom att fråga sys.database_recovery_status . Det är också synligt i odokumenterade kommandon som DBCC DBINFO och DBCC DBTABLE .

  2. Målmodulens objekt-ID.

    Detta är bara det bekanta objekt-ID. Observera att inte alla moduler som tillåter kryptering är schema-omfattade. Du måste använda metadatavyer (sys.triggers eller sys.server_triggers ) för att få objekt-ID för DDL och serveromfattade utlösare, snarare än sys.objects eller OBJECT_ID , eftersom dessa bara fungerar med schema-omfattade objekt.

  3. Målmodulens underobjekt-ID.

    Detta är procedurnumret för numrerade lagrade procedurer. Det är 1 för en onumrerad lagrad procedur och noll i alla andra fall.

Genom att använda felsökaren igen kan vi se familjens GUID hämtas under nyckelinitiering:

Databasfamiljens GUID är skrivna uniqueidentifier , objekt-ID är heltal , och underobjekts-ID är smallint .

Varje del av nyckeln måste konverteras till ett specifikt binärt format. För databasfamiljens GUID, konvertering av uniqueidentifier skriv till binär(16) producerar den korrekta binära representationen. De två ID:n måste konverteras till binära i little-endian representation (minst signifikant byte först).

Obs! Var mycket försiktig så att du inte av misstag tillhandahåller GUID som en sträng! Det måste skrivas uniqueidentifier .

Kodavsnittet nedan visar korrekta konverteringsåtgärder för vissa exempelvärden:

DECLARE 
    @family_guid binary(16) = CONVERT(binary(16), {guid 'B1FC892E-5824-4FD3-AC48-FBCD91D57763'}),
    @objid binary(4) = CONVERT(binary(4), REVERSE(CONVERT(binary(4), 800266156))),
    @subobjid binary(2) = CONVERT(binary(2), REVERSE(CONVERT(binary(2), 0)));

Det sista steget för att generera RC4-initieringsnyckeln är att sammanfoga de tre binära värdena ovan till en enda binär(22), och beräkna SHA-1-hash för resultatet:

DECLARE 
    @RC4key binary(20) = HASHBYTES('SHA1', @family_guid + @objid + @subobjid);

För exempeldata som ges ovan är den slutliga initieringsnyckeln:

0x6C914908E041A08DD8766A0CFEDC113585D69AF8

Bidraget från målmodulens objekt-ID och subobjekt-ID till SHA-1-hash är svårt att se i en enda debugger-skärmdump, men den intresserade läsaren kan referera till demonteringen av en del av initspkey nedan:

call    sqllang!A_SHAInit
lea     rdx,[rsp+40h]
lea     rcx,[rsp+50h]
mov     r8d,10h
call    sqllang!A_SHAUpdate
lea     rdx,[rsp+24h]
lea     rcx,[rsp+50h]
mov     r8d,4
call    sqllang!A_SHAUpdate
lea     rdx,[rsp+20h]
lea     rcx,[rsp+50h]
mov     r8d,2
call    sqllang!A_SHAUpdate
lea     rdx,[rsp+0D0h]
lea     rcx,[rsp+50h]
call    sqllang!A_SHAFinal
lea     r8,[rsp+0D0h]
mov     edx,14h
mov     rcx,rbx
call    sqllang!rc4_key (00007fff`89672090)

SHAInit och SHAUddate anrop lägger till komponenter till SHA-hash, som så småningom beräknas genom ett anrop till SHAFinal .

SHAInit samtalet bidrar med 10h byte (16 decimaler) lagrade vid [rsp+40h], vilket är familjens GUID . Den första SHAUupdate samtalet lägger till 4 byte (som anges i r8d-registret), lagrad vid [rsp+24h], vilket är objektet ID. Den andra SHAUddate samtalet lägger till 2 byte, lagrad vid [rsp+20h], vilket är subobjid .

De sista instruktionerna skickar den beräknade SHA-1-hash till RC4-nyckelinitieringsrutinen rc4_key . Längden på hashen lagras i register edx:14h (20 decimaler) byte, vilket är den definierade hashlängden för SHA och SHA-1 (160 bitar).

RC4-implementeringen

RC4-algoritmen är välkänd och relativt enkel. Det skulle vara bättre implementerat i ett .Net-språk av effektivitets- och prestandaskäl, men det finns en T-SQL-implementering nedan.

Dessa två T-SQL-funktioner implementerar RC4-nyckelschemaläggningsalgoritmen och pseudoslumptalsgeneratorn och skrevs ursprungligen av SQL Server MVP Peter Larsson. Jag har gjort några mindre ändringar för att förbättra prestandan lite och tillåta LOB-längds binärer att kodas och avkodas. Denna del av processen kan ersättas av vilken standard RC4-implementering som helst.

/*
** RC4 functions
** Based on http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=76258
** by Peter Larsson (SwePeso)
*/
IF OBJECT_ID(N'dbo.fnEncDecRc4', N'FN') IS NOT NULL
    DROP FUNCTION dbo.fnEncDecRc4;
GO
IF OBJECT_ID(N'dbo.fnInitRc4', N'TF') IS NOT NULL
    DROP FUNCTION dbo.fnInitRc4;
GO
CREATE FUNCTION dbo.fnInitRc4
    (@Pwd varbinary(256))
RETURNS @Box table
    (
        i tinyint PRIMARY KEY, 
        v tinyint NOT NULL
    )
WITH SCHEMABINDING
AS
BEGIN
    DECLARE @Key table
    (
        i tinyint PRIMARY KEY,
        v tinyint NOT NULL
    );
 
    DECLARE
        @Index smallint = 0,
        @PwdLen tinyint = DATALENGTH(@Pwd);
 
    WHILE @Index <= 255
    BEGIN
        INSERT @Key
            (i, v)
        VALUES
            (@Index, CONVERT(tinyint, SUBSTRING(@Pwd, @Index % @PwdLen + 1, 1)));
 
        INSERT @Box (i, v)
        VALUES (@Index, @Index);
 
        SET @Index += 1;
    END;
 
    DECLARE
        @t tinyint = NULL,
        @b smallint = 0;
 
    SET @Index = 0;
 
    WHILE @Index <= 255
    BEGIN
        SELECT @b = (@b + b.v + k.v) % 256
        FROM @Box AS b
        JOIN @Key AS k
            ON k.i = b.i
        WHERE b.i = @Index;
 
        SELECT @t = b.v
        FROM @Box AS b
        WHERE b.i = @Index;
 
        UPDATE b1
        SET b1.v = (SELECT b2.v FROM @Box AS b2 WHERE b2.i = @b)
        FROM @Box AS b1
        WHERE b1.i = @Index;
 
        UPDATE @Box
        SET v = @t
        WHERE i = @b;
 
        SET @Index += 1;
    END;
 
    RETURN;
END;
GO
CREATE FUNCTION dbo.fnEncDecRc4
(
    @Pwd varbinary(256),
    @Text varbinary(MAX)
)
RETURNS varbinary(MAX)
WITH 
    SCHEMABINDING, 
    RETURNS NULL ON NULL INPUT
AS
BEGIN
    DECLARE @Box AS table 
    (
        i tinyint PRIMARY KEY, 
        v tinyint NOT NULL
    );
 
    INSERT @Box
        (i, v)
    SELECT
        FIR.i, FIR.v
    FROM dbo.fnInitRc4(@Pwd) AS FIR;
 
    DECLARE
        @Index integer = 1,
        @i smallint = 0,
        @j smallint = 0,
        @t tinyint = NULL,
        @k smallint = NULL,
        @CipherBy tinyint = NULL,
        @Cipher varbinary(MAX) = 0x;
 
    WHILE @Index <= DATALENGTH(@Text)
    BEGIN
        SET @i = (@i + 1) % 256;
 
        SELECT
            @j = (@j + b.v) % 256,
            @t = b.v
        FROM @Box AS b
        WHERE b.i = @i;
 
        UPDATE b
        SET b.v = (SELECT w.v FROM @Box AS w WHERE w.i = @j)
        FROM @Box AS b
        WHERE b.i = @i;
 
        UPDATE @Box
        SET v = @t
        WHERE i = @j;
 
        SELECT @k = b.v
        FROM @Box AS b
        WHERE b.i = @i;
 
        SELECT @k = (@k + b.v) % 256
        FROM @Box AS b
        WHERE b.i = @j;
 
        SELECT @k = b.v
        FROM @Box AS b
        WHERE b.i = @k;
 
        SELECT
            @CipherBy = CONVERT(tinyint, SUBSTRING(@Text, @Index, 1)) ^ @k,
            @Cipher = @Cipher + CONVERT(binary(1), @CipherBy);
 
        SET @Index += 1;
    END;
 
    RETURN @Cipher;
END;
GO

Den krypterade modultexten

Det enklaste sättet för en SQL Server-administratör att få detta är att läsa varbinary(max) värde lagrat i imageval kolumn för sys.sysobjvalues , som endast är tillgänglig via DAC (Dedicated Administrator Connection).

Detta är samma idé som rutinmetoden som beskrivs i inledningen, även om vi lägger till ett filter på valclass =1. Den här interna tabellen är också ett bekvämt ställe att hämta subobjid . Annars skulle vi behöva kontrollera sys.numbered_procedures när målobjektet är en procedur, använd 1 för en onumrerad procedur eller noll för något annat, som beskrivits tidigare.

Det är möjligt att undvika att använda DAC genom att läsa imageval från sys.sysobjvalues direkt med flera DBCC PAGE samtal. Detta innebär lite mer arbete för att hitta sidorna från metadata, följ imageval LOB-kedja och läs binära måldata från varje sida. Det senare steget är mycket lättare att göra i ett annat programmeringsspråk än T-SQL. Observera att DBCC PAGE kommer att fungera, även om basobjektet normalt inte är läsbart från en icke-DAC-anslutning. Om sidan inte finns i minnet kommer den att läsas in från beständig lagring som vanligt.

Den extra ansträngningen att undvika DAC-kravet lönar sig genom att tillåta flera användare att använda dekrypteringsprocessen samtidigt. Jag kommer att använda DAC-metoden i den här artikeln av enkelhets- och utrymmesskäl.

Fungerat exempel

Följande kod skapar en testkrypterad skalär funktion:

CREATE FUNCTION dbo.FS()
RETURNS varchar(255)
WITH ENCRYPTION, SCHEMABINDING AS
BEGIN
    RETURN 
    (
        SELECT 'My code is so awesome is needs to be encrypted!'
    );
END;

Den fullständiga dekrypteringsimplementeringen finns nedan. Den enda parametern som behöver ändras för att fungera för andra objekt är initialvärdet för @objectid ställs in i den första DECLARE uttalande.

-- *** DAC connection required! ***
-- Make sure the target database is the context
USE Sandpit;
 
DECLARE
    -- Note: OBJECT_ID only works for schema-scoped objects
    @objectid integer = OBJECT_ID(N'dbo.FS', N'FN'),
    @family_guid binary(16),
    @objid binary(4),
    @subobjid binary(2),
    @imageval varbinary(MAX),
    @RC4key binary(20);
 
-- Find the database family GUID
SELECT @family_guid = CONVERT(binary(16), DRS.family_guid)
FROM sys.database_recovery_status AS DRS
WHERE DRS.database_id = DB_ID();
 
-- Convert object ID to little-endian binary(4)
SET @objid = CONVERT(binary(4), REVERSE(CONVERT(binary(4), @objectid)));
 
SELECT
    -- Read the encrypted value
    @imageval = SOV.imageval,
    -- Get the subobjid and convert to little-endian binary
    @subobjid = CONVERT(binary(2), REVERSE(CONVERT(binary(2), SOV.subobjid)))
FROM sys.sysobjvalues AS SOV
WHERE 
    SOV.[objid] = @objectid
    AND SOV.valclass = 1;
 
-- Compute the RC4 initialization key
SET @RC4key = HASHBYTES('SHA1', @family_guid + @objid + @subobjid);
 
-- Apply the standard RC4 algorithm and
-- convert the result back to nvarchar
PRINT CONVERT
    (
        nvarchar(MAX),
        dbo.fnEncDecRc4
        (
            @RC4key,
            @imageval
        )
    );

Notera den slutliga konverteringen till nvarchar eftersom modultexten skrivs som nvarchar(max) .

Utdata är:

Slutsats

Anledningarna till att metoden som beskrivs i inledningen fungerar är:

  • SQL-servern använder RC4-strömchifferet för att reversibelt exklusiva-eller källtexten.
  • RC4-nyckeln beror endast på databasfamiljens guide, objekt-id och subobjid.
  • Att tillfälligt ersätta modultexten innebär att samma (SHA-1 hashade) RC4-nyckel genereras.
  • Med samma nyckel genereras samma RC4-ström, vilket tillåter exklusiv eller dekryptering.

Användare som inte har tillgång till systemtabeller, databasfiler eller annan åtkomst på administratörsnivå kan inte hämta krypterad modultext. Eftersom SQL Server själv måste kunna dekryptera modulen, finns det inget sätt att förhindra lämpligt privilegierade användare från att göra detsamma.


  1. Är det möjligt att framtvinga datakontroll i MySQL med hjälp av reguljära uttryck

  2. Hur undkommer jag reserverade ord som används som kolumnnamn? MySQL/Skapa tabell

  3. Hur man visar frågefel i PDO PHP

  4. Ladda Excel-datablad till Oracle-databasen