sql >> Database teknologi >  >> RDS >> Database

Det indre af MED KRYPTERING

Det er ret nemt for en SQL Server-administrator at gendanne teksten fra lagrede procedurer, visninger, funktioner og udløsere, der er beskyttet ved hjælp af WITH ENCRYPTION . Der er skrevet mange artikler om dette, og flere kommercielle værktøjer er tilgængelige. Den grundlæggende oversigt over den almindelige metode er at:

  1. Få den krypterede formular (A) ved hjælp af den dedikerede administratorforbindelse.
  2. Start en transaktion.
  3. Erstat objektdefinitionen med kendt tekst (B) af mindst samme længde som originalen.
  4. Få den krypterede form for den kendte tekst (C).
  5. Rul transaktionen tilbage for at efterlade målobjektet i dets oprindelige tilstand.
  6. Få den ukrypterede original ved at anvende et eksklusivt-eller på hvert tegn:A XOR (B XOR C)

Det hele er ret ligetil, men det virker lidt som magi:Det forklarer ikke meget om hvordan og hvorfor det virker . Denne artikel dækker dette aspekt for dem af jer, der finder denne slags detaljer interessante, og giver en alternativ metode til dekryptering, der er mere illustrativ for processen.

Strømchifferet

Den underliggende krypteringsalgoritme SQL Server bruger til modulkryptering er RC4™-strømchifferet. En oversigt over krypteringsprocessen er:

  1. Initialiser RC4-chifferet med en kryptografisk nøgle.
  2. Generer en pseudotilfældig strøm af bytes.
  3. Kombiner modulets almindelig tekst med bytestrømmen ved hjælp af eksklusiv-eller.

Vi kan se denne proces ske ved hjælp af en debugger og offentlige symboler. For eksempel viser stak-sporingen nedenfor, at SQL Server initialiserer RC4-nøglen, mens den forbereder kryptering af modulteksten:

Denne næste viser SQL Server, der krypterer teksten ved hjælp af RC4 pseudorandom byte-strømmen:

Ligesom de fleste stream-cifre er dekrypteringsprocessen den samme som kryptering, idet den gør brug af det faktum, at eksklusiv-eller er reversibel (A XOR B XOR B = A ).

Brugen af ​​en stream-chiffer er årsagen til eksklusiv-eller bruges i metoden beskrevet i starten af ​​artiklen. Der er intet i sagens natur usikkert ved at bruge eksklusiv-eller, forudsat at en sikker krypteringsmetode anvendes, initialiseringsnøglen holdes hemmelig, og nøglen genbruges ikke.

RC4 er ikke specielt stærk, men det er ikke hovedproblemet her. Når det er sagt, er det værd at bemærke, at kryptering ved hjælp af RC4 gradvist fjernes fra SQL Server og er forældet (eller deaktiveret, afhængigt af version og databasekompatibilitetsniveau) for brugeroperationer som at oprette en symmetrisk nøgle.

RC4-initialiseringsnøglen

SQL Server bruger tre stykker information til at generere nøglen, der bruges til at initialisere RC4-strømchifferet:

  1. Databasefamiliens GUID.

    Dette opnås nemmest ved at forespørge sys.database_recovery_status . Det er også synligt i udokumenterede kommandoer som DBCC DBINFO og DBCC DBTABLE .

  2. Målmodulets objekt-id.

    Dette er blot det velkendte objekt-id. Bemærk, at ikke alle moduler, der tillader kryptering, er skema-omfattede. Du skal bruge metadatavisninger (sys.triggers eller sys.server_triggers ) for at få objekt-id'et for DDL og server-scoped triggere i stedet for sys.objects eller OBJECT_ID , da disse kun virker med skema-omfattede objekter.

  3. Målmodulets underobjekt-id.

    Dette er procedurenummeret for nummererede lagrede procedurer. Det er 1 for en unummereret lagret procedure og nul i alle andre tilfælde.

Ved at bruge debuggeren igen kan vi se familie-GUID'et blive hentet under nøgleinitialisering:

Databasefamiliens GUID er skrevet uniqueidentifier , objekt-id er heltal , og underobjekt-id er smallint .

Hver del af nøglen skal konverteres til et specifikt binært format. For databasefamiliens GUID, konvertering af uniqueidentifier skriv til binær(16) producerer den korrekte binære repræsentation. De to ID'er skal konverteres til binære i little-endian repræsentation (mindst signifikant byte først).

Bemærk: Vær meget forsigtig med ikke ved et uheld at angive GUID'en som en streng! Det skal skrives uniqueidentifier .

Kodestykket nedenfor viser korrekte konverteringshandlinger for nogle eksempelværdier:

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 sidste trin for at generere RC4-initialiseringsnøglen er at sammenkæde de tre binære værdier ovenfor i en enkelt binær(22) og beregne SHA-1-hashen for resultatet:

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

For eksempeldataene givet ovenfor er den endelige initialiseringsnøgle:

0x6C914908E041A08DD8766A0CFEDC113585D69AF8

Bidraget fra målmodulets objekt-id og underobjekt-id til SHA-1-hash er svært at se i et enkelt debugger-skærmbillede, men den interesserede læser kan henvise til adskillelsen af ​​en del af initspkey nedenfor:

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 og SHAUdater opkald tilføjer komponenter til SHA-hashen, som til sidst beregnes af et kald til SHAFinal .

SHAInit opkaldet bidrager med 10h bytes (16 decimaler) gemt ved [rsp+40h], som er familiens GUID . Den første SHAUupdate opkald tilføjer 4 bytes (som angivet i r8d-registret), gemt ved [rsp+24h], som er objektet ID. Den anden SHAUupdate opkald tilføjer 2 bytes, gemt ved [rsp+20h], som er subobjid .

De sidste instruktioner videregiver den beregnede SHA-1-hash til RC4-nøgleinitieringsrutinen rc4_key . Længden af ​​hashen er gemt i register edx:14h (20 decimal) bytes, som er den definerede hash-længde for SHA og SHA-1 (160 bit).

RC4-implementeringen

Kernen RC4-algoritmen er velkendt og relativt enkel. Det ville være bedre implementeret i et .Net-sprog af effektivitets- og ydeevnehensyn, men der er en T-SQL-implementering nedenfor.

Disse to T-SQL-funktioner implementerer RC4-nøgleplanlægningsalgoritmen og pseudorandom-talgeneratoren og blev oprindeligt skrevet af SQL Server MVP Peter Larsson. Jeg har lavet nogle mindre ændringer for at forbedre ydeevnen lidt og tillade at LOB-længde binære filer kodes og afkodes. Denne del af processen kan erstattes af enhver standard RC4-implementering.

/*
** 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 krypterede modultekst

Den nemmeste måde for en SQL Server-administrator at få dette på er at læse varbinary(max) værdi gemt i imageval kolonne med sys.sysobjvalues , som kun er tilgængelig via DAC (Dedicated Administrator Connection).

Dette er den samme idé som rutinemetoden beskrevet i introduktionen, selvom vi tilføjer et filter på valclass =1. Denne interne tabel er også et praktisk sted at få subobjid . Ellers bliver vi nødt til at tjekke sys.numbered_procedures når målobjektet er en procedure, skal du bruge 1 for en unummereret procedure eller nul for noget andet, som beskrevet tidligere.

Det er muligt at undgå at bruge DAC ved at læse imageval fra sys.sysobjvalues direkte ved at bruge flere DBCC PAGE opkald. Dette indebærer lidt mere arbejde for at finde siderne fra metadata, følg imageval LOB-kæde, og læs de binære måldata fra hver side. Sidstnævnte trin er meget nemmere at udføre i et andet programmeringssprog end T-SQL. Bemærk, at DBCC PAGE vil fungere, selvom basisobjektet normalt ikke kan læses fra en ikke-DAC-forbindelse. Hvis siden ikke er i hukommelsen, vil den blive læst ind fra vedvarende lagring som normalt.

Den ekstra indsats for at undgå DAC-kravet betaler sig ved at tillade flere brugere at bruge dekrypteringsprocessen samtidigt. Jeg vil bruge DAC-tilgangen i denne artikel af enkeltheds- og pladshensyn.

Et fungeret eksempel

Følgende kode opretter en testkrypteret skalarfunktion:

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 komplette dekrypteringsimplementering er nedenfor. Den eneste parameter, der skal ændres for at fungere for andre objekter, er startværdien af ​​@objectid indstillet i den første DECLARE erklæring.

-- *** 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
        )
    );

Bemærk den endelige konvertering til nvarchar fordi modultekst er skrevet som nvarchar(max) .

Outputtet er:

Konklusion

Årsagerne til, at metoden beskrevet i introduktionen virker er:

  • SQL-serveren bruger RC4-streamchifferet til reversibelt at ekskludere - eller kildeteksten.
  • RC4-nøglen afhænger kun af databasefamilieguiden, objekt-id og subobjid.
  • Midlertidig udskiftning af modulteksten betyder, at den samme (SHA-1 hashed) RC4-nøgle genereres.
  • Med den samme nøgle genereres den samme RC4-stream, hvilket tillader eksklusiv- eller dekryptering.

Brugere, der ikke har adgang til systemtabeller, databasefiler eller anden adgang på adminniveau, kan ikke hente krypteret modultekst. Da SQL Server selv skal være i stand til at dekryptere modulet, er der ingen måde at forhindre passende privilegerede brugere i at gøre det samme.


  1. Forskelle mellem MySql og MySqli i PHP

  2. Hvorfor er MySQL InnoDB indsættelse så langsom?

  3. Sådan returneres antallet af rækker i et forespørgselsresultat i SQL Server

  4. MySQLi udarbejdede erklæringer fejlrapportering