Für einen SQL Server-Administrator ist es ziemlich einfach, den Text von gespeicherten Prozeduren, Ansichten, Funktionen und Triggern wiederherzustellen, die mit WITH ENCRYPTION
geschützt sind . Darüber sind viele Artikel geschrieben worden, und mehrere kommerzielle Tools sind verfügbar. Die Grundzüge der gemeinsamen Methode sind:
- Beziehen Sie das verschlüsselte Formular (A) über die Dedicated Administrator Connection.
- Starten Sie eine Transaktion.
- Ersetzen Sie die Objektdefinition durch bekannten Text (B) von mindestens derselben Länge wie das Original.
- Erhalte die verschlüsselte Form für den bekannten Text (C).
- Die Transaktion rückgängig machen, um das Zielobjekt in seinem Anfangszustand zu belassen.
- Erhalten Sie das unverschlüsselte Original, indem Sie ein exklusives Oder auf jedes Zeichen anwenden:
A XOR (B XOR C)
Das ist alles ziemlich einfach, wirkt aber ein bisschen wie Zauberei:Es erklärt nicht viel darüber, wie und warum es funktioniert . Dieser Artikel behandelt diesen Aspekt für diejenigen unter Ihnen, die diese Art von Details interessant finden, und bietet eine alternative Methode zur Entschlüsselung, die den Prozess besser veranschaulicht.
Die Stream-Chiffre
Der zugrunde liegende Verschlüsselungsalgorithmus, den SQL Server für die Modulverschlüsselung verwendet ist die RC4™ Stromchiffre. Eine Übersicht über den Verschlüsselungsprozess ist:
- Initialisieren Sie die RC4-Chiffre mit einem kryptografischen Schlüssel.
- Generieren Sie einen pseudozufälligen Bytestrom.
- Kombinieren Sie den Klartext des Moduls mit dem Byte-Stream mithilfe von Exklusiv-ODER.
Wir können sehen, wie dieser Prozess mit einem Debugger und öffentlichen Symbolen abläuft. Der folgende Stack-Trace zeigt beispielsweise, wie SQL Server den RC4-Schlüssel initialisiert, während die Verschlüsselung des Modultexts vorbereitet wird:
Das nächste zeigt, wie SQL Server den Text mit dem pseudozufälligen RC4-Bytestrom verschlüsselt:
Wie bei den meisten Stream-Chiffren ist der Prozess der Entschlüsselung derselbe wie der der Verschlüsselung, wobei die Tatsache genutzt wird, dass Exklusiv-Oder umkehrbar ist (A XOR B XOR B = A
).
Die Verwendung einer Stromchiffre ist der Grund exclusive-or wird in dem am Anfang des Artikels beschriebenen Verfahren verwendet. Die Verwendung von Exklusiv-ODER ist grundsätzlich nicht unsicher, vorausgesetzt, dass eine sichere Verschlüsselungsmethode verwendet wird, der Initialisierungsschlüssel geheim gehalten und der Schlüssel nicht wiederverwendet wird.
RC4 ist nicht besonders stark, aber das ist hier nicht das Hauptproblem. Es ist jedoch erwähnenswert, dass die Verschlüsselung mit RC4 schrittweise aus SQL Server entfernt wird und für Benutzervorgänge wie das Erstellen eines symmetrischen Schlüssels veraltet (oder deaktiviert, je nach Version und Kompatibilitätsgrad der Datenbank) ist.
Der RC4-Initialisierungsschlüssel
SQL Server verwendet drei Informationen, um den Schlüssel zu generieren, der zum Initialisieren der RC4-Stromchiffre verwendet wird:
- Die GUID der Datenbankfamilie.
Dies kann am einfachsten durch Abfragen von sys.database_recovery_status ermittelt werden . Es ist auch in undokumentierten Befehlen wie
DBCC DBINFO
sichtbar undDBCC DBTABLE
. - Die Objekt-ID des Zielmoduls.
Dies ist nur die bekannte Objekt-ID. Beachten Sie, dass nicht alle Module, die eine Verschlüsselung zulassen, schemabezogen sind. Sie müssen Metadatenansichten verwenden (sys.triggers oder sys.server_triggers ), um die Objekt-ID für DDL- und serverbezogene Trigger anstelle von sys.objects abzurufen oder
OBJECT_ID
, da diese nur mit schemabezogenen Objekten funktionieren. - Die Unterobjekt-ID des Zielmoduls.
Dies ist die Prozedurnummer für nummerierte gespeicherte Prozeduren. Es ist 1 für eine nicht nummerierte gespeicherte Prozedur und null in allen anderen Fällen.
Wenn wir den Debugger erneut verwenden, können wir sehen, wie die Familien-GUID während der Schlüsselinitialisierung abgerufen wird:
Die GUID der Datenbankfamilie ist uniqueidentifier , Objekt-ID ist Integer , und die Unterobjekt-ID ist smallint .
Jeder Teil des Schlüssels muss in ein bestimmtes Binärformat konvertiert werden. Konvertieren Sie für die GUID der Datenbankfamilie den uniqueidentifier Typ zu binary(16) erzeugt die korrekte binäre Darstellung. Die beiden IDs müssen in Little-Endian-Darstellung binär konvertiert werden (niederwertigstes Byte zuerst).
Hinweis: Achten Sie darauf, die GUID nicht versehentlich als Zeichenfolge anzugeben! Es muss uniqueidentifier eingegeben werden .
Das folgende Code-Snippet zeigt korrekte Konvertierungsvorgänge für einige Beispielwerte:
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)));
Der letzte Schritt zum Generieren des RC4-Initialisierungsschlüssels besteht darin, die drei obigen Binärwerte zu einem einzigen Binärwert (22) zu verketten und den SHA-1-Hash des Ergebnisses zu berechnen:
DECLARE @RC4key binary(20) = HASHBYTES('SHA1', @family_guid + @objid + @subobjid);
Für die oben angegebenen Beispieldaten lautet der endgültige Initialisierungsschlüssel:
0x6C914908E041A08DD8766A0CFEDC113585D69AF8
Der Beitrag der Objekt-ID und Unterobjekt-ID des Zielmoduls zum SHA-1-Hash ist in einem einzelnen Debugger-Screenshot schwer zu erkennen, aber der interessierte Leser kann sich auf die Zerlegung eines Teils von initspkey beziehen unten:
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)
Die SHAInit und SHAUpdate Aufrufe fügen dem SHA-Hash Komponenten hinzu, der schließlich durch einen Aufruf von SHAFinal berechnet wird .
Die SHAInit Aufruf trägt 10 h Bytes (16 Dezimalstellen) bei, die unter [rsp+40h] gespeichert werden, was die Familien-GUID ist . Das erste SHAUpdate call fügt 4 Bytes hinzu (wie im r8d-Register angegeben), gespeichert unter [rsp+24h], was das Objekt ist ICH WÜRDE. Das zweite SHAUpdate call fügt 2 Bytes hinzu, gespeichert bei [rsp+20h], was das subobjid ist .
Die abschließenden Anweisungen übergeben den berechneten SHA-1-Hash an die RC4-Schlüsselinitialisierungsroutine rc4_key . Die Länge des Hash wird im Register edx gespeichert:14h (20 dezimal) Bytes, was die definierte Hash-Länge für SHA und SHA-1 (160 Bit) ist.
Die RC4-Implementierung
Der RC4-Kernalgorithmus ist bekannt und relativ einfach. Es wäre aus Effizienz- und Leistungsgründen besser in einer .Net-Sprache implementiert, aber es gibt unten eine T-SQL-Implementierung.
Diese beiden T-SQL-Funktionen implementieren den RC4-Schlüsselplanungsalgorithmus und den Pseudozufallszahlengenerator und wurden ursprünglich von SQL Server MVP Peter Larsson geschrieben. Ich habe einige geringfügige Änderungen vorgenommen, um die Leistung ein wenig zu verbessern und Binärdateien mit LOB-Länge zu codieren und zu decodieren. Dieser Teil des Prozesses könnte durch jede Standard-RC4-Implementierung ersetzt werden.
/* ** 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
Der verschlüsselte Modultext
Der einfachste Weg für einen SQL Server-Administrator, dies zu erfahren, besteht darin, die varbinary(max) zu lesen Wert, der in imageval gespeichert ist Spalte von sys.sysobjvalues , auf die nur über die Dedicated Administrator Connection (DAC) zugegriffen werden kann.
Dies ist die gleiche Idee wie die in der Einführung beschriebene Routinemethode, obwohl wir einen Filter für valclass hinzufügen =1. Diese interne Tabelle ist auch ein bequemer Ort, um das subobjid zu erhalten . Andernfalls müssten wir sys.numbered_procedures überprüfen Wenn das Zielobjekt eine Prozedur ist, verwenden Sie 1 für eine nicht nummerierte Prozedur oder null für alles andere, wie zuvor beschrieben.
Es ist möglich, die Verwendung des DAC zu vermeiden durch Lesen des imageval aus sys.sysobjvalues direkt, unter Verwendung mehrerer DBCC PAGE
Anrufe. Dies erfordert etwas mehr Arbeit, um die Seiten anhand von Metadaten zu finden, folgen Sie dem imageval LOB-Kette und lesen Sie die binären Zieldaten von jeder Seite. Der letzte Schritt ist in einer anderen Programmiersprache als T-SQL viel einfacher durchzuführen. Beachten Sie, dass DBCC PAGE
funktioniert, obwohl das Basisobjekt normalerweise nicht von einer Nicht-DAC-Verbindung gelesen werden kann. Wenn sich die Seite nicht im Speicher befindet, wird sie wie gewohnt aus dem persistenten Speicher eingelesen.
Der zusätzliche Aufwand zur Vermeidung der DAC-Anforderung zahlt sich dadurch aus, dass mehrere Benutzer den Entschlüsselungsprozess gleichzeitig verwenden können. Ich werde in diesem Artikel der Einfachheit halber und aus Platzgründen den DAC-Ansatz verwenden.
Durchgeführtes Beispiel
Der folgende Code erstellt eine testverschlüsselte 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;
Die vollständige Entschlüsselungsimplementierung finden Sie unten. Der einzige Parameter, der geändert werden muss, damit er für andere Objekte funktioniert, ist der Anfangswert von @objectid
im ersten DECLARE
gesetzt Aussage.
-- *** 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 ) );
Beachten Sie die endgültige Umwandlung in nvarchar da Modultext als nvarchar(max) eingegeben wird .
Die Ausgabe ist:
Schlussfolgerung
Die Gründe, warum die in der Einführung beschriebene Methode funktioniert, sind:
- SQL Server verwendet die RC4-Stromverschlüsselung, um den Quelltext umkehrbar auszuschließen.
- Der RC4-Schlüssel hängt nur von der GUID der Datenbankfamilie, der Objekt-ID und dem Unterobjid ab.
- Das vorübergehende Ersetzen des Modultexts bedeutet, dass derselbe (SHA-1-gehashte) RC4-Schlüssel generiert wird.
- Mit demselben Schlüssel wird derselbe RC4-Stream generiert, was eine Exklusiv-Oder-Entschlüsselung ermöglicht.
Benutzer, die keinen Zugriff auf Systemtabellen, Datenbankdateien oder anderen Zugriff auf Administratorebene haben, können keinen verschlüsselten Modultext abrufen. Da SQL Server selbst in der Lage sein muss, das Modul zu entschlüsseln, gibt es keine Möglichkeit, entsprechend privilegierte Benutzer daran zu hindern, dasselbe zu tun.