Möchtest Du den gesamten Artikel lesen? Und vielleicht sogar den Artikel im PDF-Format und die Beispieldateien herunterladen? Dann hole Dir den Artikel gleich hier - völlig kostenlos!
Im Artikel “SQL Server: Tabellen verknüpfen” (www.vbentwickler.de/485) haben wir gezeigt, wie wir die Tabellen eines SQL Servers per ODBC als Tabellenverknüpfung in Access-Datenbanken verfügbar machen können. Dabei gibt es zwei Varianten, um über entsprechende Verbindungszeichenfolgen auf die Tabellen des SQL Servers zuzugreifen: Windows-Authentifizierung und SQL Server-Authentifizierung. Bei der ersten werden die Zugangsdaten über den aktuellen Windows-Benutzer ermittelt. Dieses Kennwort kennt in der Regel nur der Benutzer und die Verknüpfung der Tabellen kann nach seiner Anmeldung am Rechner ohne weitere Interaktion erfolgen. Bei der SQL Server-Authentifizierung jedoch müssen zusätzlich die Zugangsdaten des Benutzers zum SQL Server angegeben werden. Damit der Benutzer diese nicht immer manuell eingeben muss, möchten wir diese sicher speichern – so, dass niemand diese auslesen kann, auch wenn er Zugriff zum Rechner des Benutzers hat. Wie das gelingt, zeigen wir in diesem Artikel.
Wenn ein Benutzer sich an seinen Rechner angemeldet hat und seine Access-Anwendung geöffnet hat, muss er, um auf per ODBC verknüpfte SQL Server-Tabellen zuzugreifen, auf irgendeine Weise seine Zugangsdaten für den SQL Server und die entsprechende Datenbank angeben, damit diese zum Aktualisieren oder Neuanlegen der Tabellenverknüpfungen genutzt werden können. Wie das Aktualisieren oder Neuverknüpfen gelingt, haben wir in aller Ausführlichkeit im Artikel SQL Server: Tabellen verknüpfen (www.vbentwickler.de/485) beschrieben. Dazu müssen der Benutzername und das Kennwort einmalig angegeben und in die Verbindungszeichenfolge zum Herstellen der Tabellenverknüpfungen eingetragen werden. Nach dem initialen Aktualisieren oder Neuanlegen der Tabellenverknüpfungen ist während der aktiven Access-Session keine weitere Eingabe dieser Daten erforderlich, da diese intern gespeichert werden. Erst wenn der Benutzer diese Access-Session durch Schließen der Anwendung beendet, werden die temporär gespeicherten Zugangsdaten aus dem Speicher gelöscht.
Da jede Eingabe Zeit kostet, wollen wir hier eine Lösung vorstellen, mit der wir die Zugangsdaten an irgendeiner Stelle im System speichern können, damit der Benutzer diese nicht bei jedem Start der Anwendung manuell eingeben muss. Wir gehen an dieser Stelle davon aus, dass der Benutzer sich nach Feierabend von seinem Rechner abmeldet und diesen auch in kurzen Pausen sperrt. Ansonsten könnten andere Benutzer auf den Rechner zugreifen und die Daten der verknüpften Tabellen mühelos auslesen und in eine andere Datenbank kopieren.
Dennoch würde er dazu eine gewisse Zeit benötigen. Sicherstellen wollen wir auf jeden Fall, dass ein Dritter sich keinen Zugang zu den von uns im System des Benutzers gespeicherten Zugangsdaten zum SQL Server zu verschaffen. Dies wäre kritisch, weil ein Dritter diese dann nutzen könnte, um von einem anderen Rechner in aller Ruhe auf die Tabellen der entsprechenden SQL Server-Datenbank zuzugreifen und diese zu kopieren, zu ändern oder zu löschen oder auch neue Daten hinzuzufügen.
Die vorgestellte Lösung soll also nicht nur dazu dienen, dem Benutzer mehr Komfort beim Starten der Access-Anwendung zu bieten, indem er nicht jedes Mal seine SQL Server-Zugangsdaten eingeben muss. Sie soll auch dafür sorgen, dass diese Zugangsdaten so gespeichert werden, dass kein Dritter diese auslesen kann, auch wenn er uneingeschränkten Zugriff auf den Rechner hat.
Geeignete Speicherorte
Es gibt verschiedene geeignete Speicherorte für solche Zugangsdaten. Der naheliegendste ist eine Optionentabelle in der Access-Anwendung selbst. Der Benutzer müsste einmal seine SQL Server-Zugangsdaten eingeben. Diese würden dann direkt nach der Eingabe in der entsprechenden Tabelle gespeichert werden.
Eine weitere Option ist eine externe Datei – dabei kann es sich um eine simple Textdatei handeln oder auch eine XML- oder eine JSON-Datei, in die wir diese Informationen in strukturierter Form eintragen können.
Schließlich gibt es noch die Registry. Diese bietet im Pfad HKEY_CURRENT_USER\Software\VB and VBA Program Settings einen Bereich, der speziell per VBA oder VB leicht beschrieben oder ausgelesen werden kann, ohne dass wir Funktionen der Windows-API bemühen müssen.
All diese Speicherorte haben Vor- und Nachteile gemeinsam. Der Vorteil ist, dass man diese nur auslesen kann, wenn man unter dem entsprechenden Benutzerkonto angemeldet ist. Voraussetzung dafür ist, dass sich die Dateien im jeweiligen Benutzerordner befinden. Bei dem angesprochenen Registry-Bereich ist das automatisch sichergestellt, da sich dieser unter dem Element HKEY_CURRENT_USER befindet, der ohnehin nur im Kontext der entsprechenden Benutzeranmeldung auslesbar ist.
Aber wenn der Benutzer einmal seinen Arbeitsplatz verlässt, zum Beispiel um sich einen Kaffee zu holen, ohne sich abzumelden oder den Bildschirm zu sperren, könnte ein Dritter diese Daten ebenfalls mühelos auslesen und anschließend von einem anderen Rechner über diesen Zugang auf den SQL Server zugreifen.
Es gilt also, noch eine weitere Sicherheitsstufe zu integrieren.
Sicheres Speichern der Zugangsdaten
Dies geschieht wiederum in zwei Stufen. Die erste Stufe ist, dass wir die Zugangsdaten nach der Eingabe und vor dem Speichern durch einen entsprechenden Algorithmus verschlüsseln. Dieser Algorithmus wird als Funktion in der Datenbankanwendung hinterlegt.
Nun kann ein Dritter ohne weitere Maßnahmen immer noch das VBA-Projekt der Access-Anwendung öffnen und Einblick in diesen Algorithmus erhalten, um diesen dann auf einem anderen Rechner auszuführen. Hier folgt die zweite Stufe, indem wir sicherstellen, dass Benutzer keine ungesicherte .accdb-Version der Access-Anwendung nutzen, sondern nur eine .accde-Version, die keinen Einblick in den VBA-Code mehr gewährt. So ist sichergestellt, dass niemand den Verschlüsselungsalgorithmus ermitteln kann.
Schließlich sollten wir nicht nur einfach den Benutzernamen und das Kennwort verschlüsseln, zum Beispiel mit SHA1. Das Problem ist: Wenn jemand Zugriff auf die Registry oder die Datei hat, in der die Zugangsdaten gespeichert sind, kann er einfach alle gängigen Algorithmen zur Entschlüsselung einmal durchgehen und wird so früher oder später auf die richtige Methode stoßen und kann die Zugangsdaten entschlüsseln.
Das Zauberwort an dieser Stelle lautet Pepper. So nennt man eine Zeichenkette, die als zusätzlicher Faktor für die Verschlüsselung verwendet werden kann. Diesen Faktor speichern wir wiederum im VBA-Projekt, das wir durch Umwandeln der Datenbankdatei in eine .accde weitgehend unlesbar machen.
Weitgehend deshalb, weil es durchaus Unternehmen gibt, die .accde-Dateien entschlüsseln können, so dass auch unsere Pepper-Zeichenkette ausgelesen werden kann. Solche Unternehmen lassen sich aber, wenn sie im legalen Bereich operieren, vom Auftraggeber bestätigen, dass sie berechtigt sind, das VBA-Projekt dieser Anwendung wieder lesbar zu machen.
Verwenden von DPAPI zum Ver- und Entschlüsseln
In diesem Artikel verwenden wir keine der üblichen Verschlüsselungsmethoden wie SHA1 et cetera, sondern DPAPI. DPAPI steht für Data Protection API und ist ein integrierter Windows-Mechanismus, mit dem Anwendungen Daten einfach und sicher verschlüsseln können, ohne selbst Kryptographie implementieren zu müssen.
Die Besonderheit von DPAPI ist, dass es einen Schlüssel verwendet, der an das Windows-Benutzerkonto oder an den Computer gebunden ist.
Es gibt zwei Modi:
- USER-MODE: Hier ist der Schlüssel ist an den aktuell angemeldeten Windows-Benutzer gebunden. Nur dieser Benutzer kann die Daten wieder entschlüsseln. Dazu kann er die API-Funktionen CryptProtectData und CryptUnprotectData nutzen.
- MACHINE-MODE: Hier ist der Schlüssel ist an den Rechner gebunden. Damit kann jedes Programm unter jedem Benutzer kann entschlüsseln, was zum Beispiel für Serverdienste verwendet werden kann.
Für Access-Frontend-Anwendungen ist USER-MODE perfekt. Wenn wir die Funktion CryptProtectData nutzen, wird die zu verschlüsselnde Zeichenkette in einen Datenblock gepackt. Windows verschlüsselt diesen Datenblock mit einem Schlüssel, der aus dem Windows-Benutzerpasswort und geheimen Systemschlüsselnabgeleitet wird. Dies liefert ein Byte-Array zurück, den wir dann zum Beispiel in der Registry speichern können.
Wenn wir später mit der Funktion CrypUnprotectData entschlüsseln, übergeben wir das verschlüsselte Byte-Array. Windows prüft dann, ob der aktuelle Benutzer der gleiche Benutzer ist, der verschlüsselt hat. Wenn ja, wird der verschlüsselte Text unverschlüsselt wieder ausgegeben. Anderenfalls schlägt die Verschlüsselung fehl.
Code zum Ver- und Entschlüsseln
Um eine Verschlüsselung mit einer Pepper-Zeichenkette zu nutzen, benötigen wir als Erstes eine Konstante, in der wir diese Zeichenkette speichern:
Private Const APP_PEPPER As String = "DEIN_GEHEIMER_PEPPER"
Hier musst Du Deine eigene Pepper-Zeichenfolge eintragen.
Außerdem benötigen wir die Deklaration zweier API-Funktionen namens CryptProtectData und CryptUnprotectData und zwei weitere API-Funktionen namens RtlMoveMemory und LocalFree. Diese finden wir, neben dem Typ DATA_BLOB, in Listing 1.
Private Type DATA_BLOB cbData As Long pbData As LongPtr End Type #If VBA7 Then Private Declare PtrSafe Function CryptProtectData Lib "crypt32.dll" ( _ ByRef pDataIn As DATA_BLOB, _ ByVal szDataDescr As LongPtr, _ ByVal pOptionalEntropy As LongPtr, _ ByVal pvReserved As LongPtr, _ ByVal pPromptStruct As LongPtr, _ ByVal dwFlags As Long, _ ByRef pDataOut As DATA_BLOB) As Long Private Declare PtrSafe Function CryptUnprotectData Lib "crypt32.dll" ( _ ByRef pDataIn As DATA_BLOB, _ ByVal ppszDataDescr As LongPtr, _ ByVal pOptionalEntropy As LongPtr, _ ByVal pvReserved As LongPtr, _ ByVal pPromptStruct As LongPtr, _ ByVal dwFlags As Long, _ ByRef pDataOut As DATA_BLOB) As Long Private Declare PtrSafe Sub RtlMoveMemory Lib "kernel32" ( _ ByRef Destination As Any, _ ByVal Source As LongPtr, _ ByVal Length As LongPtr) Private Declare PtrSafe Function LocalFree Lib "kernel32" ( _ ByVal hMem As LongPtr) As LongPtr #End If
Listing 1: API-Deklarationen für das Ver- und Entschlüsseln
Die Funktion ProtectString
Die Funktion ProtectString übernimmt die Aufgabe, unsere Zeichenkette, also zum Beispiel den Benutzernamen oder das Kennwort sicher zu verschlüsseln, bevor sie beispielsweise in der Registry gespeichert wird. Dabei kombiniert die Funktion den in APP_PEPPER hinterlegten Pepper mit der Windows-internen DPAPI-Verschlüsselung (siehe Listing 2).
Public Function ProtectString(ByVal s As String) As String Dim sWithPepper As String Dim inBlob As DATA_BLOB Dim outBlob As DATA_BLOB Dim bytes() As Byte Dim lRes As Long If Len(s) = 0 Then Exit Function sWithPepper = APP_PEPPER & s bytes = StrConv(sWithPepper, vbFromUnicode) inBlob.cbData = UBound(bytes) - LBound(bytes) + 1 inBlob.pbData = VarPtr(bytes(LBound(bytes))) lRes = CryptProtectData(inBlob, 0, 0, 0, 0, 0, outBlob) If lRes <> 0 Then ReDim bytes(0 To outBlob.cbData - 1) RtlMoveMemory bytes(0), outBlob.pbData, outBlob.cbData Call LocalFree(outBlob.pbData) ProtectString = BytesToHex(bytes) Else Debug.Print "CryptProtectData failed. LastDllError=" & Err.LastDllError ProtectString = "" End If End Function
Listing 2: Funktion zum Verschlüsseln von Zeichenketten
Zunächst prüft die Funktion, ob der übergebene String überhaupt einen Wert enthält. Ist das nicht der Fall, bricht sie sofort ab und liefert einen leeren Rückgabewert.
Danach wird der Pepper aus APP_PEPPER vor das eigentliche Klartextpasswort gesetzt. Dieser Pepper wird nicht mitgespeichert und dient als zusätzliche Schutzschicht, da ein Angreifer ohne Kenntnis dieses Werts den entschlüsselten Text nicht korrekt validieren könnte.
Anschließend wird die zusammengesetzte Zeichenkette (APP_PEPPER plus Benutzername/Kennwort) durch die Funktion StrConv in ein ANSI-Byte-Array umgewandelt. DPAPI arbeitet intern mit Binärdaten, daher muss der String zunächst in ein Array von Bytes transformiert werden. Dieses Bytearray wird danach in eine DATA_BLOB-Struktur übergeben, die lediglich aus der Anzahl der Bytes und einem Zeiger auf das erste Byte besteht. Wichtig ist hierbei, dass die Struktur nicht selbst Daten enthält, sondern direkt auf das vorhandene Array zeigt.
Nun kommt der zentrale Schritt: Die Funktion ruft CryptProtectData auf, eine Windows-API-Funktion, die die Daten benutzergebunden verschlüsselt. Das bedeutet, dass nur derselbe Windows-Benutzer die Daten später wieder entschlüsseln kann – ideal für sichere lokale Speicherung.
Der Rückgabewert zeigt an, ob der Vorgang erfolgreich war. Bei Erfolg werden die verschlüsselten Bytes aus dem zurückgegebenen BLOB in ein eigenes Bytearray kopiert. Anschließend wird der durch die API belegte Speicher wieder freigegeben, um Speicherlecks zu vermeiden.
Zum Abschluss wandelt die Funktion das verschlüsselte Bytearray mit einer weiteren Funktion namens BytesToHex in einen Hex-String um. Diese Darstellung eignet sich besonders gut für Registry-Einträge oder andere textbasierte Speicherorte, da sie keinerlei Sonderzeichen enthält und verlustfrei wieder zurückkonvertiert werden kann.
Falls die Verschlüsselung fehlschlägt, gibt die Funktion eine Debug-Meldung aus und liefert einen leeren String zurück.
Die Funktion BytesToHex sieht wie folgt aus:
Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...
den kompletten Artikel im PDF-Format mit Beispieldatenbank
diesen und alle anderen Artikel mit dem Jahresabo
