sql >> Database >  >> RDS >> Database

De binnenkant van WITH ENCRYPTIE

Het is vrij eenvoudig voor een SQL Server-beheerder om de tekst van opgeslagen procedures, weergaven, functies en triggers te herstellen die zijn beveiligd met WITH ENCRYPTION . Hier zijn veel artikelen over geschreven en er zijn verschillende commerciële tools beschikbaar. Het basisschema van de algemene methode is om:

  1. Verkrijg het versleutelde formulier (A) met behulp van de Dedicated Administrator Connection.
  2. Start een transactie.
  3. Vervang de objectdefinitie door bekende tekst (B) van minimaal dezelfde lengte als het origineel.
  4. Verkrijg het versleutelde formulier voor de bekende tekst (C).
  5. Rol de transactie terug om het doelobject in de oorspronkelijke staat te laten.
  6. Verkrijg het niet-versleutelde origineel door een exclusief-of toe te passen op elk teken:A XOR (B XOR C)

Dat is allemaal vrij eenvoudig, maar lijkt een beetje op magie:het verklaart niet veel over hoe en waarom het werkt . Dit artikel behandelt dat aspect voor degenen onder u die dit soort details interessant vinden, en biedt een alternatieve methode voor decodering die meer illustratief is voor het proces.

Het stroomcijfer

Het onderliggende versleutelingsalgoritme dat SQL Server gebruikt voor moduleversleuteling is de RC4™-stroomcodering. Een overzicht van het coderingsproces is:

  1. Initialiseer de RC4-codering met een cryptografische sleutel.
  2. Genereer een pseudo-willekeurige stroom van bytes.
  3. Combineer de module platte tekst met de bytestroom met behulp van exclusive-or.

We kunnen dit proces zien plaatsvinden met behulp van een debugger en openbare symbolen. De onderstaande stacktracering laat bijvoorbeeld zien dat SQL Server de RC4-sleutel initialiseert tijdens het voorbereiden om de moduletekst te versleutelen:

Deze volgende toont SQL Server die de tekst versleutelt met behulp van de RC4 pseudo-willekeurige bytestroom:

Zoals bij de meeste stroomcoderingen is het decoderingsproces hetzelfde als encryptie, waarbij gebruik wordt gemaakt van het feit dat exclusief of omkeerbaar is (A XOR B XOR B = A ).

Het gebruik van een stroomcijfer is de reden exclusief-of wordt gebruikt in de methode beschreven aan het begin van het artikel. Er is niets inherent onveilig aan het gebruik van exclusieve-of, op voorwaarde dat een veilige coderingsmethode wordt gebruikt, de initialisatiesleutel geheim wordt gehouden en de sleutel niet opnieuw wordt gebruikt.

RC4 is niet bijzonder sterk, maar daar gaat het hier niet om. Dat gezegd hebbende, is het vermeldenswaard dat codering met RC4 geleidelijk wordt verwijderd uit SQL Server en wordt afgekeurd (of uitgeschakeld, afhankelijk van de versie en het compatibiliteitsniveau van de database) voor gebruikersbewerkingen zoals het maken van een symmetrische sleutel.

De RC4-initialisatiesleutel

SQL Server gebruikt drie stukjes informatie om de sleutel te genereren die wordt gebruikt om de RC4-stroomcodering te initialiseren:

  1. De databasefamilie GUID.

    Dit kan het gemakkelijkst worden verkregen door sys.database_recovery_status . op te vragen . Het is ook zichtbaar in ongedocumenteerde commando's zoals DBCC DBINFO en DBCC DBTABLE .

  2. De object-ID van de doelmodule.

    Dit is gewoon de bekende object-ID. Houd er rekening mee dat niet alle modules die versleuteling toestaan, schemagericht zijn. U moet metadataweergaven gebruiken (sys.triggers of sys.server_triggers ) om de object-ID voor DDL en server-scoped triggers op te halen, in plaats van sys.objects of OBJECT_ID , omdat deze alleen werken met objecten met schemabereik.

  3. De subobject-ID van de doelmodule.

    Dit is het procedurenummer voor genummerde opgeslagen procedures. Het is 1 voor een ongenummerde opgeslagen procedure en nul in alle andere gevallen.

Als we de debugger opnieuw gebruiken, kunnen we zien dat de familie-GUID wordt opgehaald tijdens de sleutelinitialisatie:

De GUID van de databasefamilie wordt getypt uniqueidentifier , object-ID is geheel getal , en het subobject-ID is smallint .

Elk deel van de sleutel moet worden geconverteerd naar een specifiek binair formaat. Voor de databasefamilie GUID, het converteren van de uniqueidentifier typ naar binair (16) produceert de juiste binaire representatie. De twee ID's moeten worden omgezet naar binair in little-endian-weergave (minst significante byte eerst).

Opmerking: Wees heel voorzichtig dat u de GUID niet per ongeluk als een tekenreeks opgeeft! Het moet worden getypt uniqueidentifier .

Het onderstaande codefragment toont de juiste conversiebewerkingen voor enkele voorbeeldwaarden:

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

De laatste stap om de RC4-initialisatiesleutel te genereren, is door de drie bovenstaande binaire waarden samen te voegen tot een enkele binaire (22) en de SHA-1-hash van het resultaat te berekenen:

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

Voor de bovenstaande voorbeeldgegevens is de uiteindelijke initialisatiesleutel:

0x6C914908E041A08DD8766A0CFEDC113585D69AF8

De bijdrage van de object-ID en sub-object-ID van de doelmodule aan de SHA-1-hash is moeilijk te zien in een enkele debugger-screenshot, maar de geïnteresseerde lezer kan verwijzen naar de demontage van een deel van initspkey hieronder:

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)

De SHAInit en SHAUpdate aanroepen voegen componenten toe aan de SHA-hash, die uiteindelijk wordt berekend door een aanroep naar SHAFinal .

De SHAInit oproep draagt ​​10h bytes (16 decimaal) bij, opgeslagen op [rsp+40h], wat de familie-GUID is . De eerste SHAUpdate oproep voegt 4 bytes toe (zoals aangegeven in het r8d-register), opgeslagen op [rsp+24h], wat het object is ID KAART. De tweede SHAUpdate oproep voegt 2 bytes toe, opgeslagen op [rsp+20h], wat de subobjid is .

De laatste instructies geven de berekende SHA-1-hash door aan de RC4-sleutelinitialisatieroutine rc4_key . De lengte van de hash wordt opgeslagen in register edx:14h (20 decimaal) bytes, wat de gedefinieerde hashlengte is voor SHA en SHA-1 (160 bits).

De RC4-implementatie

Het kern-RC4-algoritme is bekend en relatief eenvoudig. Het zou om redenen van efficiëntie en prestatie beter in een .Net-taal geïmplementeerd kunnen worden, maar hieronder staat een T-SQL-implementatie.

Deze twee T-SQL-functies implementeren het RC4-sleutelplanningsalgoritme en de pseudo-willekeurige nummergenerator en zijn oorspronkelijk geschreven door SQL Server MVP Peter Larsson. Ik heb enkele kleine wijzigingen aangebracht om de prestaties een beetje te verbeteren, en om LOB-lengte binaire bestanden te coderen en te decoderen. Dit deel van het proces kan worden vervangen door elke standaard RC4-implementatie.

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

De gecodeerde moduletekst

De eenvoudigste manier voor een SQL Server-beheerder om dit te krijgen, is door de varbinary(max) . te lezen waarde opgeslagen in de imageval kolom met sys.sysobjvalues , die alleen toegankelijk is via de Dedicated Administrator Connection (DAC).

Dit is hetzelfde idee als de routinemethode die in de inleiding is beschreven, hoewel we een filter toevoegen aan valclass =1. Deze interne tabel is ook een handige plaats om de subobjid . te krijgen . Anders moeten we sys.numbered_procedures . controleren wanneer het doelobject een procedure is, gebruik dan 1 voor een ongenummerde procedure, of nul voor iets anders, zoals eerder beschreven.

Het is mogelijk om het gebruik van de DAC te vermijden door de imageval . te lezen van sys.sysobjvalues rechtstreeks, met behulp van meerdere DBCC PAGE belt. Dit brengt wat meer werk met zich mee om de pagina's uit metadata te lokaliseren, volg de imageval LOB-keten en lees de binaire doelgegevens van elke pagina. De laatste stap is een stuk makkelijker te doen in een andere programmeertaal dan T-SQL. Merk op dat DBCC PAGE zal werken, ook al is het basisobject normaal niet leesbaar vanaf een niet-DAC-verbinding. Als de pagina zich niet in het geheugen bevindt, wordt deze normaal ingelezen vanuit de permanente opslag.

De extra inspanning om de DAC-vereiste te vermijden, loont door meerdere gebruikers het decoderingsproces gelijktijdig te laten gebruiken. Ik zal de DAC-aanpak in dit artikel gebruiken om redenen van eenvoud en ruimte.

Uitgewerkt voorbeeld

De volgende code maakt een test-gecodeerde scalaire functie:

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;

De volledige decoderingsimplementatie staat hieronder. De enige parameter die moet worden gewijzigd om voor andere objecten te werken, is de beginwaarde van @objectid ingesteld in de eerste DECLARE verklaring.

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

Let op de uiteindelijke conversie naar nvarchar omdat moduletekst wordt getypt als nvarchar(max) .

De uitvoer is:

Conclusie

De redenen waarom de in de inleiding beschreven methode werkt zijn:

  • SQL Server gebruikt de RC4-stroomcodering om de brontekst omkeerbaar uit te sluiten.
  • De RC4-sleutel hangt alleen af ​​van de databasefamilie-guid, object-ID en subobjid.
  • Het tijdelijk vervangen van de moduletekst betekent dat dezelfde (SHA-1 gehashte) RC4-sleutel wordt gegenereerd.
  • Met dezelfde sleutel wordt dezelfde RC4-stream gegenereerd, waardoor exclusieve of ontsleuteling mogelijk is.

Gebruikers die geen toegang hebben tot systeemtabellen, databasebestanden of andere toegang op beheerdersniveau, kunnen geen versleutelde moduletekst ophalen. Aangezien SQL Server zelf de module moet kunnen ontsleutelen, is er geen manier om te voorkomen dat voldoende bevoorrechte gebruikers hetzelfde doen.


  1. Update-instructie met inner join op Oracle

  2. Verschil tussen multi-statement tabelwaardige functies en inline tabelwaardige functies in SQL Server

  3. Wat betekent mysql-fout 1025 (HY000):Fout bij hernoemen van './foo' (errorno:150)?

  4. Een overzicht van JSON-mogelijkheden binnen PostgreSQL