Pro administrátora SQL Serveru je velmi snadné obnovit text uložených procedur, pohledů, funkcí a spouštěčů chráněných pomocí WITH ENCRYPTION
. O tom bylo napsáno mnoho článků a je k dispozici několik komerčních nástrojů. Základní náčrt běžné metody je:
- Získejte zašifrovaný formulář (A) pomocí připojení Dedicated Administrator Connection.
- Zahajte transakci.
- Nahraďte definici objektu známým textem (B), který má alespoň stejnou délku jako originál.
- Získejte zašifrovanou formu známého textu (C).
- Vraťte transakci zpět a ponechte cílový objekt v původním stavu.
- Získejte nezašifrovaný originál použitím exkluzivního znaku nebo na každý znak:
A XOR (B XOR C)
To vše je docela jednoduché, ale vypadá to trochu jako kouzlo:nevysvětluje to moc o tom, jak a proč to funguje . Tento článek pokrývá tento aspekt pro ty z vás, kteří považují tyto druhy detailů za zajímavé, a poskytuje alternativní metodu dešifrování, která tento proces více ilustruje.
Streamová šifra
Základní šifrovací algoritmus SQL Server používá pro šifrování modulu je proudová šifra RC4™. Nástin procesu šifrování je:
- Inicializujte šifru RC4 pomocí kryptografického klíče.
- Vygenerujte pseudonáhodný proud bajtů.
- Zkombinujte prostý text modulu s byte streamem pomocí exkluzivního-nebo.
Tento proces můžeme vidět pomocí debuggeru a veřejných symbolů. Například trasování zásobníku níže ukazuje, že SQL Server inicializuje klíč RC4 při přípravě na šifrování textu modulu:
Tento další ukazuje, jak SQL Server šifruje text pomocí pseudonáhodného byte streamu RC4:
Stejně jako většina proudových šifer je proces dešifrování stejný jako šifrování, přičemž se využívá skutečnosti, že výhradní nebo je reverzibilní (A XOR B XOR B = A
).
Důvodem exclusive-or je použití proudové šifry se používá v metodě popsané na začátku článku. Na použití výhradního nebo za předpokladu, že je použita zabezpečená metoda šifrování, je inicializační klíč udržován v tajnosti a klíč není znovu použit, není nic nebezpečného.
RC4 není nijak zvlášť silný, ale to zde není hlavní problém. Nicméně stojí za zmínku, že šifrování pomocí RC4 je postupně odstraňováno z SQL Serveru a je zastaralé (nebo zakázáno, v závislosti na verzi a úrovni kompatibility databáze) pro uživatelské operace, jako je vytváření symetrického klíče.
Inicializační klíč RC4
SQL Server používá ke generování klíče použitého k inicializaci proudové šifry RC4 tři informace:
- Identifikátor GUID databázové rodiny.
To lze nejsnáze získat dotazem na sys.database_recovery_status . Je také vidět v nezdokumentovaných příkazech, jako je
DBCC DBINFO
aDBCC DBTABLE
. - ID objektu cílového modulu.
Toto je pouze známé ID objektu. Všimněte si, že ne všechny moduly, které umožňují šifrování, jsou omezeny na schéma. Budete muset použít zobrazení metadat (sys.triggers nebo sys.server_triggers ), abyste získali ID objektu pro DDL a spouštěče v rozsahu serveru, nikoli sys.objects nebo
OBJECT_ID
, protože tyto fungují pouze s objekty v rozsahu schématu. - ID dílčího objektu cílového modulu.
Toto je číslo procedury pro číslované uložené procedury. Je 1 pro nečíslovanou uloženou proceduru a nula ve všech ostatních případech.
Opětovným použitím ladicího programu můžeme vidět, jak se GUID rodiny načítá během inicializace klíče:
Typ GUID databázové rodiny je uniqueidentifier , ID objektu je celé číslo a ID dílčího objektu je smallint .
Každá část klíče musí převést do určitého binárního formátu. Pro GUID databázové rodiny převod uniqueidentifier zadejte do binary(16) vytváří správnou binární reprezentaci. Tato dvě ID musí být převedena na binární v reprezentaci little-endian (nejméně významný bajt jako první).
Poznámka: Buďte velmi opatrní, abyste omylem nezadali GUID jako řetězec! Musí být zadán uniqueidentifier .
Níže uvedený fragment kódu ukazuje správné převodní operace pro některé vzorové hodnoty:
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)));
Posledním krokem k vygenerování inicializačního klíče RC4 je zřetězení tří výše uvedených binárních hodnot do jediné binární (22) a výpočet SHA-1 hash výsledku:
DECLARE @RC4key binary(20) = HASHBYTES('SHA1', @family_guid + @objid + @subobjid);
Pro ukázková data uvedená výše je konečný inicializační klíč:
0x6C914908E041A08DD8766A0CFEDC113585D69AF8
Příspěvek ID objektu cílového modulu a ID dílčího objektu k hash SHA-1 je těžké vidět na jediném snímku obrazovky ladicího programu, ale čtenář, který má zájem, se může odkázat na rozebrání části initspkey. níže:
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 a SHAUpdate volání přidávají komponenty do hash SHA, který je nakonec vypočítán voláním SHAFinal .
SHAInit volání přispívá 10h bajtů (16 dekadických) uložených na [rsp+40h], což je family GUID . První SHAUpdate volání přidá 4 bajty (jak je uvedeno v registru r8d), uložené na [rsp+24h], což je objekt ID. Druhá SHAUpdate volání přidá 2 bajty, uložené na [rsp+20h], což je subobjid .
Poslední instrukce předají vypočítaný hash SHA-1 inicializační rutině klíče RC4 rc4_key . Délka hashe je uložena v registru edx:14h (20 dekadických) bajtů, což je definovaná délka hashe pro SHA a SHA-1 (160 bitů).
Implementace RC4
Základní algoritmus RC4 je dobře známý a relativně jednoduchý. Z důvodů efektivity a výkonu by bylo lepší implementovat v jazyce .Net, ale níže je implementace T-SQL.
Tyto dvě funkce T-SQL implementují algoritmus plánování klíčů RC4 a generátor pseudonáhodných čísel a původně je napsal Peter Larsson, MVP SQL Server. Udělal jsem několik drobných úprav, abych trochu zlepšil výkon a umožnil kódování a dekódování binárních souborů délky LOB. Tuto část procesu lze nahradit jakoukoli standardní implementací RC4.
/* ** 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
Text šifrovaného modulu
Nejjednodušší způsob, jak to administrátor SQL Server získat, je přečíst si varbinary(max) hodnota uložená v imageval sloupec sys.sys.sysobjvalues , která je přístupná pouze prostřednictvím připojení Dedicated Administrator Connection (DAC).
Toto je stejný nápad jako rutinní metoda popsaná v úvodu, i když jsme přidali filtr na valclass =1. Tato interní tabulka je také vhodným místem pro získání subobjid . Jinak bychom museli zkontrolovat sys.numbered_procedures když je cílovým objektem procedura, použijte 1 pro nečíslovanou proceduru nebo nulu pro cokoli jiného, jak bylo popsáno dříve.
Je možné vyhnout se použití DAC přečtením imagevalu z sys.sysobjvalues přímo pomocí více DBCC PAGE
hovory. To vyžaduje trochu více práce s vyhledáním stránek z metadat, postupujte podle imageval řetězec LOB a číst cílová binární data z každé stránky. Poslední krok je mnohem snazší provést v jiném programovacím jazyce než T-SQL. Všimněte si, že DBCC PAGE
bude fungovat, i když základní objekt není normálně čitelný z připojení bez DAC. Pokud stránka není v paměti, bude načtena z trvalého úložiště jako obvykle.
Zvláštní úsilí vyhnout se požadavku DAC se vyplácí tím, že umožňuje více uživatelům používat proces dešifrování současně. Z důvodu jednoduchosti a prostoru použiji v tomto článku přístup DAC.
Zpracovaný příklad
Následující kód vytvoří testovací šifrovanou skalární funkci:
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;
Kompletní implementace dešifrování je uvedena níže. Jediný parametr, který je třeba změnit, aby fungoval pro jiné objekty, je počáteční hodnota @objectid
nastavte v prvním DECLARE
prohlášení.
-- *** 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 ) );
Poznamenejte si konečný převod na nvarchar protože text modulu je zadán jako nvarchar(max) .
Výstup je:
Závěr
Důvody, proč metoda popsaná v úvodu funguje, jsou:
- SQL Server používá proudovou šifru RC4 k reverzibilní exkluzi – neboli zdrojového textu.
- Klíč RC4 závisí pouze na guid databázové rodiny, id objektu a subobjid.
- Dočasné nahrazení textu modulu znamená, že se vygeneruje stejný klíč RC4 (s hash SHA-1).
- Se stejným klíčem se generuje stejný stream RC4, což umožňuje exkluzivní nebo dešifrování.
Uživatelé, kteří nemají přístup k systémovým tabulkám, databázovým souborům nebo jiným přístupům na úrovni správce, nemohou načíst zašifrovaný text modulu. Vzhledem k tomu, že samotný SQL Server musí být schopen modul dešifrovat, neexistuje způsob, jak zabránit vhodně privilegovaným uživatelům, aby udělali totéž.