MSDN Magazin > Home > Ausgaben > 2007 > July >  Sicherheit: Anwenden von Kryptografie mithilfe ...
Sicherheit
Anwenden von Kryptografie mithilfe der CNG-API in Windows Vista
Kenny Kerr

Themen in diesem Artikel:
  • Unterschiede zwischen CryptoAPI und CNG
  • Algorithmusanbieter
  • Zufallszahlen, Verschlüsselung, Signaturen und Überprüfung
  • Interoperabilität mit .NET-Anwendungen
In diesem Artikel werden folgende Technologien verwendet:
Windows Vista
Laden Sie den Code für diesen Artikel herunter: CNG2007_07.exe (158 KB)
Code online durchsuchen
Mit Windows Vista™ wurde eine neue Kryptografie-API eingeführt, welche die alten CryptoAPI ersetzen soll, die noch aus den ersten Versionen von Windows® NT und Windows 95 stammt. CNG (Cryptography Next Generation) soll langfristig die CryptoAPI und alle von ihr bereitgestellten kryptografischen Primitive ersetzen. CNG unterstützt nicht nur alle von der CryptoAPI bereitgestellten Algorithmen, sondern bietet auch viele neue Algorithmen und ein wesentlich flexibleres Design, um Entwicklern eine bessere Kontrolle darüber zu geben, wie kryptografische Vorgänge durchgeführt werden und wie Algorithmen in verschiedenen Vorgängen zusammenarbeiten.
Abbildung 1 zeigt das High-Level-Design von CNG. Dieser Artikel befasst sich vor allem mit BCrypt, einer Teilmenge von CNG, welche die kryptografischen Primitive wie z. B. Zufallszahlengenerierung, Hashfunktionen, Signaturen und Verschlüsselungsschlüssel bereitstellt. Im Gegensatz dazu ist NCrypt die Teilmenge von CNG, die Schlüsselspeicherfunktionen bietet, um permanente asymmetrische Schlüssel und Hardware wie Smartcards zu unterstützen. Rätseln Sie nicht zu viel, was BCrypt und NCrypt bedeuten. BCrypt ist einfach der Name der Headerdatei und der DLL, welche die Basisdienste für CNG bereitstellt. „B“ steht in diesem Fall für „Basis“. NCrypt ist der Name der Headerdatei und der DLL, die die Schlüsselspeicherfunktionen auf höherer Ebene bereitstellt. „N“ steht für „neu“.
Abbildung 1 CNG-Architektur 
Die von BCrypt bereitgestellten kryptografischen Primitive sind direkt im Kernelmodus verfügbar und bieten zum ersten Mal ein gemeinsames kryptografisches Framework für Anwendungen im Benutzermodus wie auch im Kernelmodus. Die von NCrypt bereitgestellten Schlüsselspeicherfunktionen sind aber nur für Anwendungen im Benutzermodus verfügbar.
Die von CNG bereitgestellten kryptografischen Primitive lassen sich auf zweierlei Arten betrachten. Einerseits als Satz logischer Objekte, die Methoden bereitstellen, die Sie aufrufen können, und Eigenschaften, die Sie abfragen und manchmal ändern können. Diese Objekte umfassen Algorithmusanbieter, Hashfunktionen, Schlüssel und geheime Vereinbarungen. Wo kommen nun Zufallszahlengeneratoren, Signaturen und verschiedene Schlüsselarten ins Spiel? Die Zufallszahlengenerierung wird direkt von Algorithmusanbietern durchgeführt, und Schlüssel stellen fast alles andere bereit. Sie können symmetrische und asymmetrische Schlüssel darstellen und werden zum Signieren und Prüfen von Hashsignaturen verwendet. Hash- und Schlüsselobjekte werden von Algorithmusanbietern abgeleitet und geheime Vereinbarungen von einem Schlüsselobjektpaar: dem öffentlichen Schlüssel eines Prinzipals und dem privaten Schlüssel eines anderen Prinzipals, die sicher kommunizieren möchten.
Als zweite Möglichkeit kann man CNG als Softwarerouter oder Zwischenstufe für kryptografische Vorgänge betrachten. Die CNG-API baut auf einem Satz logischer kryptografischer Schnittstellen auf. Sie können zum Beispiel die Hashschnittstelle verwenden, ohne eine spezielle Hashalgorithmusimplementierung hartcodieren zu müssen. Die meisten Kryptografie-APIs sind algorithmusorientiert, während CNG schnittstellenorientiert ist. Dies bietet sowohl dem Entwickler wie auch dem Anwendungsadministrator mehr Flexibilität, wenn beispielsweise ein Algorithmus einer Anwendung ersetzt werden muss, der sich nach Auslieferung der Anwendung als fehlerhaft herausstellt. Abbildung 2 zeigt die schnittstellenorientierte Ansicht von CNG.
Abbildung 2 CNG-Schnittstellen (Klicken Sie zum Vergrößern auf das Bild)
Das CNG-Konfigurationssystem verdient einen eigenen Artikel, aber zunächst genügt es zu wissen, dass Entwickler mit CNG Algorithmen anfordern können, ohne einen Algorithmusanbieter angeben zu müssen. Die Anbieter sind für die Algorithmusimplementierung verantwortlich, und aufgrund dieser Eigenschaft von CNG sind Administratoren in der Lage, Anwendungen neu zu konfigurieren, um verschiedene Implementierungen zu verwenden, und zwar entweder deklarativ in einer Anwendung oder durch die Systemrichtlinie.

Algorithmusanbieter
Betrachten wir nach dieser kurzen Einführung, wie CNG zur Durchführung verschiedener allgemeiner kryptografischer Vorgänge verwendet werden kann. Zunächst brauchen Sie einen Algorithmusanbieter. Alle von BCrypt definierten CNG-Objekte werden von BCRYPT_HANDLE identifiziert, Algorithmusanbieter sind dabei keine Ausnahme. Die Funktion „BCryptOpenAlgorithmProvider“ lädt einen Algorithmusanbieter auf Grundlage des von Ihnen gewählten Algorithmus und einer optionalen Implementierung und gibt ein Handle zurück, das in nachfolgenden Aufrufen von CNG-Funktionen verwendet wird. BCrypt übernimmt auch den Typ NTSTATUS vom Windows Driver Kit (WDK) zur Anzeige von Fehlern, unabhängig davon, ob Sie im Benutzer- oder Kernelmodus programmieren. So laden Sie einen Algorithmusanbieter in den Speicher:
BCRYPT_HANDLE algorithmProvider = 0;

NTSTATUS status = ::BCryptOpenAlgorithmProvider(
                      &algorithmProvider, algorithmName,
                      implementation, flags);

if (NT_SUCCESS(status))
{
    // Use algorithm provider
}
In den meisten Fällen geben Sie für die Implementierungs- und Kennzeichenparameter Null an. Durch Übergeben von Null für die Implementierung wird angegeben, dass für den Algorithmus, der durch den algorithmName-Parameter bezeichnet wird, der Standardalgorithmusanbieter geladen werden soll. Das Makro NT_SUCCESS sollte denjenigen vertraut sein, die das WDK bereits in der Vergangenheit verwendet haben. Dieses Makro ähnelt dem von COM-Entwicklern verwendeten Makro SUCCEEDED und gibt den Statuswert an (erfolgreich oder fehlgeschlagen).
Wenn Sie mit dem Algorithmusanbieter fertig sind, müssen Sie ihn entladen. Dazu müssen Sie das von BCryptOpenAlgorithmProvider zurückgegebene Handle wie hier abgebildet der Funktion „BCryptCloseAlgorithmProvider“ übergeben:
  status = ::BCryptCloseAlgorithmProvider(
    algorithmProvider, flags);
  ASSERT(NT_SUCCESS(status));
Derzeit sind für diese Funktion keine Kennzeichen definiert, deshalb muss an den Kennzeichenparameter Null übergeben werden. Natürlich ist die Assertion nicht notwendig, hilft aber, Fehler in Ihrer Anwendung frühzeitig zu erkennen.
In den übrigen Abschnitten dieses Artikels wird mit dem Makro NT_VERIFY das Ergebnis von CNG-Funktionen überprüft. Dies ist nur ein Platzhalter, der Sie daran erinnern soll, den zurückgegebenen Statuscode zu prüfen. Wenn Sie alle bisherigen Schritte richtig ausgeführt haben, können Sie die Makros wie folgt definieren:
#define NT_VERIFY(x) ASSERT(NT_SUCCESS(x))
Achten Sie darauf, im Produktionscode eine Fehlerbehandlung entsprechend der Fehlerbehandlungsstrategie Ihres Projekts zu implementieren.
Das Laden von Algorithmusanbietern kann teuer sein, deshalb sollte das Nutzungsmuster der Anbieter in Ihrer Anwendung berücksichtigt werden, um sie angemessen oft wiederverwenden zu können. In den übrigen Kapiteln dieses Artikels werden Algorithmusanbieter für verschiedene Ziele eingesetzt.

Zufallszahlengenerierung
Zufallszahlen werden mit der Funktion „BCryptGenRandom“ erzeugt. Diese Funktion könnte auch Zufallspuffergenerierung heißen, denn mit ihr kann jeder Puffer mit einem zufälligen Wert gefüllt werden. Nehmen wir an, Sie wollen zehn Zufallszahlen erzeugen. Abbildung 3 zeigt, wie Sie das erreichen können.
BCRYPT_HANDLE algorithmProvider = 0;

NT_VERIFY(::BCryptOpenAlgorithmProvider(
    &algorithmProvider, 
    BCRYPT_RNG_ALGORITHM,
    0,                     // implementation,
    0));                   // flags

for (int i = 0; i < 10; ++i)
{
    UINT random = 0;

    NT_VERIFY(::BCryptGenRandom(
        algorithmProvider, 
        reinterpret_cast<PUCHAR>(&random),
        sizeof(UINT), 
        0));

    cout << random << endl;
}

In diesem Beispiel habe ich die Algorithmuskennung BCRYPT_RNG_ALGORITHMUS angegeben, um den Standardalgorithmus für die Zufallszahlengenerierung anzufordern. Zusätzlich ist auch die Algorithmuskennung BCRYPT_RNG_FIPS186_DSA_ALGORITHM verfügbar, falls Sie einen Algorithmus benötigen, der die Federal Information Processing Standards (FIPS) für die Verwendung mit dem Digital Signature Algorithm (DSA) erfüllt.
Wie das Beispiel in Abbildung 3 zeigt, erwartet die Funktion „BCryptGenRandom“ zur Identifizierung des Puffers einen Zeiger auf einen UCHAR, statt der praktischeren Lösung, den Zeiger ungültig zu machen (jeder beliebige Typ). Glücklicherweise kann das leicht und typsicher mit etwas C++ gelöst werden. Sehen Sie sich die folgende Funktionsvorlage an:
template <typename T>
__checkReturn
NTSTATUS GenRandom(BCRYPT_HANDLE algorithmProvider,
                   T& buffer, ULONG flags)
{
    ASSERT(0 != algorithmProvider);

    return ::BCryptGenRandom(algorithmProvider,
                             reinterpret_cast<PUCHAR>(&buffer),
                             sizeof(T), flags);
}
Wenn Sie mit Standard Annotation Language (SAL) nicht vertraut sind: Das Makro „__checkReturn“ ist einfach ein Hinweis für die Analysetools, dass Aufrufer der Funktion das Ergebnis der Funktion prüfen sollten.
Obwohl diese Funktionsvorlage nicht für alle Fälle geeignet ist (wenn Sie z. B. einen Puffer auffüllen möchten, der in eine Auflistungsklasse integriert ist), vereinfacht sie doch die Zufallszahlengenerierung in den meisten Fällen erheblich, da die Umwandlung isoliert innerhalb der Funktionsvorlage erfolgt. Die Vorlage bestimmt auch automatisch die Größe des Puffers. Beachten Sie das folgende Beispiel für das Generieren von zehn zufälligen GUID-Werten:
for (int i = 0; i < 10; ++i)
{
    GUID random = { 0 };

    NT_VERIFY(GenRandom(algorithmProvider, random, 0));

    // Use ‘random’ value
}
Die Funktion „BCryptGenRandom“ unterstützt ein optionales Kennzeichen, mit dem Sie eine zusätzliche Entropie für den Algorithmus zur Zufallszahlengenerierung bereitstellen können. Das Kennzeichen BCRYPT_RNG_USE_ENTROPY_IN_BUFFER gibt an, dass der Algorithmus den Wert aus dem Puffer, der der Funktion übergeben wurde, als zusätzliche Entropie zur Berechnung der Zufallszahl verwenden muss, die dann dem gleichen Puffer zurückgegeben wird. Hier ein Beispiel:
UINT random = 0;

for (int i = 0; i < 10; ++i)
{
    NT_VERIFY(GenRandom(algorithmProvider, random,
                        BCRYPT_RNG_USE_ENTROPY_IN_BUFFER));

    // Use ‘random’ value
}
Beachten Sie, dass die Definition der Zufallsvariablen außerhalb der Schleife steht, damit der Wert der vorherigen Zufallszahl eine zusätzliche Entropie für die nächste generierte Zahl liefert.

Hashfunktionen
So wie die Zufallszahlengenerierung spielen auch die Hashfunktionen eine wichtige Rolle bei Sicherheitsmaßnahmen und -features in der modernen Datenverarbeitung. Hashfunktionen stehen in CNG als Objekte zur Verfügung, aber da die API im Kernelmoduscode zugänglich sein muss (der größtenteils in C geschrieben ist), bedarf es etwas Vorarbeit, um das Objekt wieder zusammenzustellen. Wie ich bereits in der Einleitung erwähnt habe, werden Algorithmusanbieter und Hashfunktionen in CNG durch Objekte vertreten. Da diese ein Teil von BCrypt sind, können Sie mit den Funktionen „BCryptGetProperty“ und „BCryptSetProperty“ benannte Eigenschaften für ein bestimmtes Objekt abfragen und festlegen. Entsprechende Funktionen gibt es auch zum Behandeln von Objekteigenschaften mit NCrypt.
Die Funktion „BCryptCreateHash“ erstellt ein neues Hashobjekt. Bevor Sie dieses aber aufrufen, müssen Sie einen Puffer zuordnen, den die Hashfunktion zur Verarbeitung verwenden soll. Hier sehen Sie einen der Nebeneffekte bei der Unterstützung von Benutzer- und Kernelmodus in einer einzigen API: Der Kernelmodus ist sehr empfindlich, was die Speicherzuordnung anbelangt, und vor allem, ob die Zuordnung von einem ausgelagerten oder nicht ausgelagerten Speicher stammt. Neben dem Puffer sind Sie auch für das Verwalten der Handle- und Hashtabellenressourcen für das Hashobjekt verantwortlich.
Die Größe des zum Erstellen des Hashobjekts erforderlichen Puffers ist eine Eigenschaft des Algorithmusanbieters, und die Kennung BCRYPT_OBJECT_LENGTH liefert Ihnen den Namen der abzufragenden Eigenschaft. Abbildung 4 zeigt, wie Sie den Algorithmusanbieter für den Hashalgorithmus SHA-256 laden und die Hashpuffergröße abfragen können.
BCRYPT_HANDLE algorithmProvider = 0;

NT_VERIFY(::BCryptOpenAlgorithmProvider(
    &algorithmProvider, 
    BCRYPT_SHA256_ALGORITHM,
    0,                      // implementation,
    0));                    // flags

ULONG hashBufferSize = 0;
ULONG bytesCopied = 0;

NT_VERIFY(::BCryptGetProperty(
    algorithmProvider, 
    BCRYPT_OBJECT_LENGTH,
    reinterpret_cast<PUCHAR>(&hashBufferSize),
    sizeof(ULONG), 
    &bytesCopied, 0));

ASSERT(sizeof(ULONG) == bytesCopied);

Der erste Parameter von BCryptGetProperty gibt das abzufragende Objekt an. Der zweite Parameter gibt den Namen der Eigenschaft an. Der dritte und vierte identifiziert den Zielpuffer, in dem der Eigenschaftswert gespeichert wird, sowie die Größe des Puffers. Der bytesCopied-Parameter ist dann nützlich, wenn Sie vorher nicht wissen, welche Puffergröße erwartet wird. Derzeit sind für diese Funktion keine Kennzeichen definiert, deshalb muss an den Kennzeichenparameter Null übergeben werden.
Nachdem die Größe des Hashpuffers bestimmt wurde, müssen Sie dem Hashobjekt einen Speicherblock zuordnen. Außer im Kernelmodus ist es relativ egal, wo Sie diesen Puffer zuordnen, und Sie dürfen jeden beliebigen Speicher verwenden, sofern er stabil ist (anders gesagt, Sie dürfen kein verwaltetes Bytearray verwenden, dessen Befestigung aufgehoben ist). Abbildung 5 zeigt eine einfache Buffer-Klasse, die für einfache Pufferzuordnungen im Benutzermodus verwendet werden kann. Diese Klasse wird beim Erstellen von Verschlüsselungsschlüsselobjekten wieder verwendet, deshalb lohnt es sich, eine solche Buffer-Klasse zur Hand zu haben.
Da nun alle Voraussetzungen erfüllt sind, können Sie jetzt endlich das Hashobjekt mit der Funktion „BCryptCreateHash“ wie hier gezeigt erstellen:
Buffer hashBuffer;
NT_VERIFY(hashBuffer.Create(hashBufferSize));

BCRYPT_HANDLE hash = 0;

NT_VERIFY(::BCryptCreateHash(algorithmProvider, &hash,
                             hashBuffer.GetData(),
                             hashBuffer.GetSize(),
                             0,   // secret
                             0,   // secret size
                             0)); // flags
Der erste Parameter von BCryptCreateHash definiert den Algorithmusanbieter, der die Hashschnittstelle implementiert. Der zweite Parameter erhält das Handle zum Hashobjekt. Der dritte und vierte Parameter gibt den Hashpuffer und seine Größe an. Verschlüsselte Hashalgorithmen verwenden zum Identifizieren des geheimen Schlüssels die geheimen Parameter. Derzeit sind für diese Funktion keine Kennzeichen definiert, deshalb muss an den Kennzeichenparameter Null übergeben werden. Wenn Sie mit dem Hashobjekt fertig sind, müssen Sie es mit der Funktion „BCryptDestroyHash“ zerstören und dann den Hashobjektpuffer freigeben.
Nachdem Sie nun wissen, wie Hashobjekte erstellt und zerstört werden, lassen Sie uns etwas wirklich Nützliches mit ihnen machen. Sobald ein Hashobjekt erstellt wurde, können Sie die Funktion „BCryptHashData“ aufrufen, um ein unidirektionales Hashing eines Puffers durchzuführen. Diese Funktion kann wiederholt aufgerufen werden, um zusätzliche Puffer im Hash zu kombinieren. Beachten Sie, dass die Größe eines Hashwerts fest ist und vom Algorithmusanbieter bestimmt wird. Deshalb ist es egal, wie oft Sie BCryptHashData aufrufen, der ausgegebene Hashwert hat immer die gleiche Größe.
In diesem Beispiel wird der Hashvorgang für Daten gezeigt, die mit der COM-IStream-Schnittstelle aus einem Datenstrom gelesen werden:
BYTE buffer[256] = { 0 };
ULONG bytesRead = 0;

while (SUCCEEDED(stream->Read(buffer, _countof(buffer),
                              &bytesRead)) && 0 < bytesRead)
{
    NT_VERIFY(::BCryptHashData(hash, buffer, bytesRead, 0));
}
Der erste Parameter von BCryptHashData ist das Handle, das von der Funktion „BCryptCreateHash“ zurückgegeben wird. Der zweite und dritte Parameter gibt den Puffer mit den Daten für das Hashing sowie die Größe des Puffers an. Beachten Sie, dass ich bei der Übergabe von bytesRead für die Größe des Puffers vorsichtig bin, um sicherzustellen, dass ich nur Daten erfasse, die tatsächlich aus dem Datenstrom gelesen werden. Schließlich muss ich, da noch keine Kennzeichen für BCryptHashData definiert sind, für den letzten Parameter Null übergeben.
Wenn Sie alle Daten der Hashfunktion übergeben haben, können Sie durch Aufrufen der Funktion „BCryptFinishHash“ den entstehenden Hashwert abrufen. Die Größe des sich ergebenden Hashwerts ist vom verwendeten Hashalgorithmus abhängig. Wenn Sie diese Größe nicht kennen, können Sie das Hashobjekt mit der Kennung BCRYPT_HASH_LENGTH abfragen. Im folgenden Beispiel frage ich die Hashwertgröße ab, erstelle für sie einen Puffer und kopiere dann mit BCryptFinishHash den Hashwert in den Puffer:
ULONG hashValueSize = 0;
NT_VERIFY(::BCryptGetProperty(hash, BCRYPT_HASH_LENGTH, ...))

Buffer hashValue;
NT_VERIFY(hashValue.Create(hashValueSize));

NT_VERIFY(::BCryptFinishHash(hash,
                             hashValue.GetData(),
                             hashValue.GetSize(),
                             0)); // flags
Das Muster wird Ihnen wahrscheinlich schon bekannt vorkommen. Der erste Parameter von BCryptFinishHash ist das Hashobjekthandle. Der zweite und dritte Parameter gibt den Puffer an, der den Hashwert erhalten wird, sowie die Größe des Puffers. Der letzte Parameter gibt die Kennzeichen an, von denen es momentan keine gibt.
Schließlich können Hashobjekte dupliziert werden. Das ist dann nützlich, wenn Sie zwei oder mehr Hashwerte erzeugen wollen, die auf einigen gemeinsamen Daten basieren. Dazu können Sie zunächst ein Hashobjekt erstellen, um die gemeinsamen Daten zu hashen, das Sie dann einmal oder mehrmals duplizieren. Dann fügen Sie den Duplikaten beliebige Deltas hinzu. Nachdem ein Hashobjekt dupliziert worden ist, enthalten beide Hashobjekte den gleichen Zustand, sind aber nicht miteinander verbunden, und Sie können jedem Objekt einmalige Daten hinzuzufügen, einen Hashwert erzeugen oder eines der Hashobjekte zerstören, ohne dass das andere davon betroffen wird.
Die Funktion „BCryptDuplicateHash“ dupliziert Hashobjekte. Sie müssen dem zu duplizierenden Hashobjekt nur ein Handle sowie einen neuen Puffer bereitstellen, den es für die Verarbeitung verwendet:
Buffer newHashBuffer;
NT_VERIFY(newHashBuffer.Create(hashBufferSize));
BCRYPT_HANDLE newHash = 0;

NT_VERIFY(::BCryptDuplicateHash(hash,
                                &newHash,
                                newHashBuffer.GetData(),
                                newHashBuffer.GetSize(),
                                0));

Symmetrische Verschlüsselung
Eine symmetrische Verschlüsselung beginnt genau so wie die Hashfunktionen, d. h. Sie müssen den symmetrischen Schlüssel und seinen Objektpuffer vorbereiten und erstellen. Die Schritte kennen Sie schon: einen Algorithmusanbieter erstellen, die Größe des Schlüsselobjektpuffers durch Abfragen der Eigenschaft BCRYPT_OBJECT_LENGTH des Anbieters bestimmen und einen Puffer mit dieser Größe zuordnen. Durch einen Aufruf der Funktion „BCryptGenerateSymmetricKey“ mit einem Geheimnis für die Initialisierung wird dann der symmetrische Schlüssel erstellt. Dieser Prozess ist in Abbildung 6 dargestellt.
Abbildung 6 Erstellen eines symmetrischen Schlüsselobjekts 
Da ich bereits das Öffnen von Algorithmusanbietern und das Erstellen von Puffern in vorherigen Abschnitten beschrieben habe, konzentriere ich mich hier auf das Generieren des symmetrischen Schlüssels. Dieser Code zeigt einen typischen Aufruf von BCryptGenerateSymmetricKey:
BCRYPT_KEY_HANDLE key = 0;

NT_VERIFY(::BCryptGenerateSymmetricKey(algorithmProvider,
                                       &key,
                                       keyBuffer.GetData(),
                                       keyBuffer.GetSize(),
                                       secret.GetData(),
                                       secret.GetSize(),
                                       0)); // flags
Der erste Parameter gibt den Algorithmusanbieter an, der einen symmetrischen Verschlüsselungsalgorithmus implementiert. Der zweite Parameter erhält das Handle zum Schlüsselobjekt. Der dritte und vierte Parameter identifiziert den Schlüsselpuffer und seine Größe. Die nächsten zwei Parameter geben einen Puffer an, der den geheimen Schlüssel enthält, der vom Absender und Empfänger verwendet wird. Dies kann ein beliebiges Bytearray (und sogar leer) sein, aber es ist normalerweise ein Hash irgendeines Kennworts. Derzeit sind für diese Funktion keine Kennzeichen definiert, deshalb muss an den Kennzeichenparameter Null übergeben werden.
Wenn Sie mit dem Schlüsselobjekt fertig sind, müssen Sie es mit der Funktion „BCryptDestroyKey“ zerstören und dann den Schlüsselobjektpuffer freigeben.
Wenn Sie ein Schlüsselobjekt erstellt haben, möchten Sie ja wahrscheinlich Daten verschlüsseln und entschlüsseln. Das geht so: Mit den treffend bezeichneten Funktionen „BCryptEncrypt“ und „BCryptDecrypt“ werden Daten für sowohl symmetrische wie auch asymmetrische Schlüssel verschlüsselt und entschlüsselt. Mit Ausnahme von Paddingschemas und Initialisierungsvektoren werden die Funktionen auf die gleiche Art verwendet, unabhängig vom verwendeten Schlüsseltyp. Diese Funktionen können auf den ersten Blick aufgrund ihrer langen Parameterliste etwas furchterregend erscheinen, aber eigentlich ist nur wichtig, dass sie einen Satz gemeinsamer Parameter annehmen, die übereinstimmen müssen, damit Daten erfolgreich entschlüsselt werden. Absender und Empfänger in einem symmetrischen Verschlüsselungsvorgang müssen einige gemeinsame Eigenschaften haben. Sie brauchen einen Schlüssel, der mit demselben Geheimnis erstellt wurde, und übereinstimmende Eigenschaftswerte. Sie benötigen dieselben Initialisierungsvektoren, und das Padding muss identisch sein.
Die symmetrische Verschlüsselung ist erstaunlich einfach zu verwenden, schwierig wird es dann, wenn Sie all diese Eigenschaften gemeinsam mit anderen Parteien verwenden möchten. Als Erstes müssen Sie die Größe des Datenblocks für den Algorithmus definieren:
ULONG blockSize = 0;
NT_VERIFY(::BCryptGetProperty(key, BCRYPT_BLOCK_LENGTH, ...))
Die Blockgröße ist aus mehreren Gründen nützlich, wenn Sie mit Blockverschlüsselungsalgorithmen arbeiten, der am häufigsten verwendeten Form symmetrischer Algorithmen. Bei der Blockverschlüsselung wird ein Nur-Text-Block mit fester Größe in einen Block aus verschlüsseltem Text mit gleicher Größe verschlüsselt. Die Blockgröße gibt auch die Größe des Initialisierungsvektors an, falls ein solcher verwendet wird.
Wenn Sie die zu verschlüsselnde Nachricht und den Initialisierungsvektor vorbereitet haben, können Sie die Funktion „BCryptEncrypt“ aufrufen, um die Nur-Text-Nachricht zu verschlüsseln. Natürlich müssen Sie die Größe des Puffers bestimmen, der den verschlüsselten Text aufnehmen wird. Dazu rufen Sie einfach BCryptEncrypt auf und übergeben an die Ausgabe- und Ausgabegrößeparameter den Wert Null. BCryptEncrypt gibt dann die erforderliche Größe zurück.
ULONG ciphertextSize = 0;

NT_VERIFY(::BCryptEncrypt(key,
                          message.GetData(),
                          message.GetSize(),
                          0,   // padding info
                          iv.GetData(),
                          iv.GetSize(),
                          0,   // output
                          0,   // output size,
                          &ciphertextSize,
                          0)); // flags
Der erste Parameter gibt den für die Verschlüsselung zu verwendenden Schlüssel an. Der zweite und dritte Parameter identifiziert die zu verschlüsselnde Nachricht. Der vierte Parameter enthält zusätzliche Paddinginformationen für die asymmetrische Verschlüsselung. Dies kommt bei symmetrischen Algorithmen nicht zum Einsatz, deshalb müssen Sie an diesen Parameter Null übergeben. Die nächsten zwei Parameter geben einen Puffer an, der den zu verwendenden Initialisierungsvektor enthält. Der vorletzte Parameter erhält die Größe des erwarteten verschlüsselten Texts, und der letzte Parameter übernimmt optionale Parameter. Nur das Kennzeichen BCRYPT_BLOCK_PADDING wird bei symmetrischen Algorithmen verwendet und ist praktisch, wenn Sie sich nicht darum kümmern wollen, eine bestimmte Nachricht durch Padding auf ein Vielfaches der Blockgröße zu erweitern.
Sie können dann den Schlüsseltextpuffer erstellen und noch einmal BCryptEncrypt aufrufen. Dieses Mal stellen Sie den Puffer bereit, der den verschlüsselten Text aufnimmt:
Buffer ciphertext;
NT_VERIFY(ciphertext.Create(ciphertextSize));

NT_VERIFY(::BCryptEncrypt(key,
                          message.GetData(),
                          message.GetSize(),
                          0,   // padding info
                          iv.GetData(),
                          iv.GetSize(),
                          ciphertext.GetData(),
                          ciphertext.GetSize(),
                          &ciphertextSize,
                          0)); // flags
Der Entschlüsselungsprozess ist ähnlich. Wenn Sie die Größe des Nur-Texts nicht kennen, rufen Sie die Funktion „BCryptDecrypt“ genau so auf, wie das vorher zum Bestimmen der Größe des verschlüsselten Texts gezeigt wurde. Schließlich rufen Sie BCryptDecrypt mit dem verschlüsselten Text, dem Initialisierungsvektor und dem Nur-Text-Puffer auf, um die sich ergebende entschlüsselte Nachricht zu erhalten:
Buffer plaintext;
NT_VERIFY(plaintext.Create(plaintextSize));

NT_VERIFY(::BCryptDecrypt(newKey,
                          ciphertext.GetData(),
                          ciphertext.GetSize(),
                          0,   // padding info
                          iv.GetData(),
                          iv.GetSize(),
                          plaintext.GetData(),
                          plaintext.GetSize(),
                          &plaintextSize,
                          0)); // flags
Achten Sie darauf, beim Entschlüsseln einer Nachricht die gleichen Kennzeichen anzugeben, die Sie beim Verschlüsseln definiert haben.

Asymmetrische Verschlüsselung
Bei der asymmetrischen Verschlüsselung werden durch die Verwendung öffentlicher Schlüssel die Probleme gelöst, die durch das Weitergeben von Geheimnissen und Initialisierungsvektoren auftreten. Bei diesem Verfahren verwenden beide Parteien je einen privaten und einen öffentlichen Schlüssel. Die öffentlichen Schlüssel stehen allen zur Verfügung. Die Schlüssel sind so miteinander verknüpft, dass nur mit dem privaten Schlüssel Daten entschlüsselt werden können, die zuvor mit dem öffentlichen Schlüssel verschlüsselt wurden. Natürlich hat diese zusätzliche Leistung ihren Preis, und die asymmetrische Verschlüsselung ist vom rechnerischen Standpunkt aus wesentlich teurer als die symmetrische Verschlüsselung. Sie ist dennoch unbezahlbar, wenn einfach eine Kommunikation durch einen Mechanismus ermöglicht werden soll, bei dem Parteien auf sichere Weise wichtige, symmetrisch verschlüsselte Daten weitergeben. Abbildung 7 zeigt den Prozess zur Erstellung asymmetrischer Schlüssel.
Abbildung 7 Erstellen eines asymmetrischen Schlüssels 
Im folgenden Beispiel wird die Funktion „BCryptGenerateKeyPair“ aufgerufen, um ein öffentliches und privates Schlüsselpaar zu erstellen:
NT_VERIFY(::BCryptGenerateKeyPair(algorithmProvider,
                                  &key, keySize,
                                  0)); // flags
Der erste Parameter gibt den Algorithmusanbieter an, der einen asymmetrischen Verschlüsselungsalgorithmus implementiert. Der zweite Parameter erhält das Handle zum Schlüsselobjekt. Der dritte Parameter gibt die Schlüsselgröße an, die der Algorithmus verwenden soll. Dieser Wert wird in Bits und nicht in Bytes ausgedrückt und ändert sich von einem Algorithmus zum anderen. Zum Beispiel unterstützt der RSA-Algorithmus Schlüsselgrößen, die ein Vielfaches von 64 sind, und zwar zwischen 512 und einschließlich 16384 Bits. Allgemeinen gesagt wirkt sich die Größe des Schlüssels direkt auf die Leistung des Algorithmus aus, aber noch wichtiger ist, dass sie sich auf die Kosten auswirkt, die Verschlüsselung durch einen Angriff mit roher Gewalt zu brechen. Die Schlüsselgröße ist auch wegen eines anderen, weniger ominösen Grunds wichtig: sie gibt die Blockgröße an, die der Algorithmus verwenden wird. Um die Blockgröße zu bestimmen, teilen Sie die Schlüsselgröße einfach durch 8.
Sobald Sie das Schlüsselpaar mit BCryptGenerateKeyPair erzeugt haben, müssen Sie ggf. verschiedene algorithmusspezifische Schlüsseleigenschaften mit der Funktion „BCryptSetProperty“ festlegen. Bevor Sie aber das Schlüsselpaar verwenden können, müssen Sie die Funktion „BCryptFinalizeKeyPair“ aufrufen, um das Erstellen des Schlüsselobjekts wie folgt zu finalisieren:
   NT_VERIFY(::BCryptFinalizeKeyPair(key, 0));
Jetzt können Sie endlich mit den Funktionen „BCryptEncrypt“ und „BCryptDecrypt“ Daten wie oben beschrieben verschlüsseln und entschlüsseln. Der einzige Unterschied in diesem Fall besteht darin, dass asymmetrische Schlüssel den Initialisierungsvektor ignorieren und dass das Paddingschema, je nach gewählten Kennzeichen etwas komplexer sein kann. Angenommen, die zu verschlüsselnde Nachricht ist in einem Puffer gespeichert, der einem Vielfachen der Blockgröße entspricht. Dann geben Sie einfach das Kennzeichen BCRYPT_PAD_NONE an, da ja kein Padding notwendig ist. Wenn Sie aber andererseits eine Nachricht verschlüsseln wollen, ohne sie zuerst durch Padding auf ein Vielfaches der Blockgröße erweitern zu müssen, dann haben Sie mehrere Optionen. Die einfachste Option ist, das Kennzeichen BCRYPT_PAD_PKCS1 anzugeben, das den Algorithmusanbieter anweist, den Eingabepuffer durch Padding mittels einer Zufallszahl auf Basis des PKCS-1-Standards auf ein Vielfaches der Blockgröße zu erweitern.
Natürlich macht eine asymmetrische Verschlüsselung wenig Sinn, wenn Sie den öffentlichen Teil eines Schlüsselpaars nicht weitergeben können. Deshalb werde ich jetzt erklären, wie das gemacht wird. Mit den Funktionen „BCryptExportKey“ und „BCryptImportKeyPair“ können Schlüssel exportiert und importiert werden. Interessanterweise kann BCryptExportKey auch verwendet werden, um symmetrische Schlüssel zu exportieren, aber Sie müssen sie mit der Funktion „BCryptImportKey“ zuerst importieren, da dem Schlüsselobjekt ein Puffer zugeordnet werden muss. Mit BCryptExportKey und BCryptImportKeyPair kann sowohl das komplette Schlüsselpaar (öffentlich und privat) als auch nur der öffentliche Schlüssel exportiert werden. Sie können zum Beispiel das Schlüsselpaar exportieren und es für eine spätere eigene Verwendung behalten und den öffentlichen Schlüssel getrennt exportieren, um ihn denjenigen anzubieten, die mit Ihnen kommunizieren wollen.
Der Code in Abbildung 8 zeigt, wie ein binäres Blob erstellt wird, der nur den öffentlichen Schlüssel enthält. Wie Sie sehen können, folgt die Funktion „BCryptExportKey“ demselben Muster, das Sie schon kennen: Sie rufen sie zuerst auf, um die Größe des Blobs zu bestimmen, und dann rufen Sie sie erneut auf, um die Informationen des öffentlichen Schlüssels zu erhalten. Der dritte Parameter der Funktion ist beachtenswert, da er den Algorithmus und auch das angibt, was genau exportiert werden soll.
ULONG publicKeyBlobSize = 0;

NT_VERIFY(::BCryptExportKey(key,
                            0,                      // reserved
                            BCRYPT_RSAPUBLIC_BLOB,
                            0,                      // output,
                            0,                      // output size
                            &publicKeyBlobSize,
                            0));                    // flags

Buffer publicKeyBlob;
NT_VERIFY(publicKeyBlob.Create(publicKeyBlobSize));

NT_VERIFY(::BCryptExportKey(key,
                            0,                      // reserved
                            BCRYPT_RSAPUBLIC_BLOB,
                            publicKeyBlob.GetData(),
                            publicKeyBlob.GetSize(),
                            &publicKeyBlobSize,
                            0));                    // flags

Im Beispiel habe ich mit BCRYPT_RSAPUBLIC_BLOB angegeben, dass ich nur den öffentlichen Schlüssel eines RSA-Schlüsselpaars exportieren möchte. Wenn Sie den öffentlichen und den privaten Schlüssel exportieren wollen, verwenden Sie stattdessen einfach BCRYPT_RSAPRIVATE_BLOB. Lassen Sie sich vom Namen nicht verwirren: Er enthält sowohl den privaten als auch den öffentlichen Schlüssel. Das ist unbedingt zu beachten, denn nur der öffentliche Schlüssel wird für die Verschlüsselung verwendet. Wenn Sie also den privaten Schlüssel exportiert und dann importiert haben, können Sie eine Nachricht zwar verschlüsseln, aber zur Verschlüsselung wird dann der öffentliche Schlüssel und nicht der private Schlüssel verwendet. Der private Schlüssel dient nur zur Entschlüsselung.
Einen Schlüssel von einem Blob zu importieren, ist einfach:
BCRYPT_HANDLE publicKey = 0;

NT_VERIFY(::BCryptImportKeyPair(algorithmProvider,
                                0,   // reserved
                                BCRYPT_RSAPUBLIC_BLOB,
                                &publicKey,
                                publicKeyBlob.GetData(),
                                publicKeyBlob.GetSize(),
                                0)); // flags
Der dritte Parameter ist auch hier wieder beachtenswert, denn er gibt den Aufbau des Blobs an, der importiert wird. Stellen Sie nur sicher, dass der Algorithmusanbieter den zu importierenden Algorithmus implementiert, dann sollte alles problemlos funktionieren. Das resultierende Handle des öffentlichen Schlüssels kann dann sicher verwendet werden, um Nachrichten zu verschlüsseln, die nur der Inhaber des privaten Schlüssels entschlüsseln kann.

Signaturen und Überprüfung
Die asymmetrische Kryptografie wird oft zum Erstellen digitaler Signaturen verwendet. Sie werden umfassend von Windows wie auch von Microsoft® .NET Framework genutzt, um die Echtheit von Nachrichten, EXE-Dateien und Assemblys zu bescheinigen.
Im Gegensatz zum oben beschriebenen asymmetrischen Verschlüsselungs- und Entschlüsselungsprozess werden Signaturen mit einem privaten Schlüssel erstellt und mit einem öffentlichen Schlüssel überprüft. So kann jemand mit einem öffentlichen Schlüssel überprüfen, dass eine gegebene Signatur vom Inhaber eines privaten Schlüssels erstellt wurde, ohne auf diesen privaten Schlüssel zugreifen zu müssen.
Das Erstellen und Überprüfen einer Signatur ist dank der geleisteten Vorarbeit ziemlich einfach. Identifizieren Sie zunächst die Daten, die Sie signieren wollen, und hashen Sie diese dann wie im Abschnitt über Hashfunktionen beschrieben. Die Signatur wird dann von der Funktion „BCryptSignHash“ auf Grundlage des Hashwerts sowie eines privaten Schlüssels für einen digitalen Signaturalgorithmus berechnet. Abbildung 9 zeigt ein einfaches Beispiel.
ULONG signatureSize = 0;

NT_VERIFY(::BCryptSignHash(keyPair,
                           0,                  // padding info
                           hashValue.GetData(),
                           hashValue.GetSize(),
                           0,                  // output
                           0,                  // output size
                           &signatureSize,
                           0));                // flags

Buffer signature;
NT_VERIFY(signature.Create(signatureSize));

NT_VERIFY(::BCryptSignHash(keyPair,
                           0,                  // padding info
                           hashValue.GetData(),
                           hashValue.GetSize(),
                           signature.GetData(),
                           signature.GetSize(),
                           &signatureSize,
                           0));                // flags

Wie gewöhnlich berechnet der erste Aufruf die Größe der entstehenden Signatur und der zweite die Signatur. Je nach Größe des Hashwerts und des verwendeten Algorithmus müssen Sie evtl. zusätzliche Paddinginformationen bereitstellen.
Die Signatur zu überprüfen, ist sogar noch einfacher. Die Idee ist, den Hashwert unabhängig zu berechnen und dann diesen Hashwert, den öffentlichen Schlüssel des Signaturgebers und die erhaltene Signatur zur Prüfung an die Funktion „BCryptVerifySignature“ zu übergeben. BCryptVerifySignature gibt STATUS_SUCCESS zurück, wenn die Signatur mit dem Hashwert übereinstimmt, ansonsten STATUS_INVALID_SIGNATURE. In Abbildung 10 sehen Sie, wie das aussehen kann.
NTSTATUS status = ::BCryptVerifySignature(publicKey,
                                          0,              // padding info
                                          hashValue.GetData(),
                                          hashValue.GetSize(),
                                          signature.GetData(),
                                          signature.GetSize(),
                                          0);             // flags
switch (status)
{
    case STATUS_SUCCESS:
    {
        // The signature matches the hash.
        break;
    }
    case STATUS_INVALID_SIGNATURE:
    {
        // The signature does not match the hash.
        break;
    }
    default:
    {
        // An error occurred.
    }
}


Interoperabilität mit .NET
Die in .NET Framework 2.0 und 3.0 integrierten Kryptografiebibliotheken bieten zahlreiche verwaltete Algorithmusimplementierungen sowie Wrapper um systemeigene Implementierungen aus der CryptoAPI. In vielen Fällen können Sie mit CNG-Daten problemlos zusammenarbeiten, obwohl in einigen Fällen die Kryptografieklassen von .NET Framework ungeeignete Standardeigenschaften für Algorithmen verwenden, die eine Problembehandlung nötig machen könnten. Der Trick besteht darin, jede einzelne Schlüsseleigenschaft zwischen CNG und seinem verwalteten Gegenwert abzustimmen, dann sollte es problemlos funktionieren.
In mancher Hinsicht ist das Arbeiten mit CNG erheblich einfacher als mit verwalteten Kryptografieklassen, denn es müssen weniger Abstraktionen und systemeigene Standards berücksichtigt werden. Für eine effiziente Kryptografie müssen Programmierer jedes einzelne Detail explizit definieren.
Behalten Sie bei der symmetrischen Verschlüsselung die folgenden Eigenschaften der .NET Framework-Klasse „SymmetricAlgorithm“ im Auge, wenn Sie verschlüsselten Text zwischen CNG und dem .NET Framework austauschen wollen: BlockSize, IV, Key, KeySize, Mode und Padding. Wenn nur bei einer dieser Eigenschaften etwas falsch ist, führt das zu Fehlern. Obwohl die von CNG und .NET Framework verwendeten Standards nicht immer gleich sind, finden Sie gewöhnlich gemeinsame Werte für z. B. die Blockgrößen und Paddingschemas, die in unterschiedlichen Implementierungen kompatibel sind.
Damit eine asymmetrische Verschlüsselung in unterschiedlichen Implementierungen funktioniert, müssen Sie etwas mehr Aufwand betreiben, denn unterschiedliche Algorithmen nutzen ganz verschiedene Eigenschaften. Deshalb genügt es nicht, eine gemeinsame Liste mit übereinstimmenden Eigenschaften zu finden, denn das funktioniert nicht. Stattdessen müssen Sie betrachten, wie die verschiedenen Implementierungen Schlüsseldaten importieren und exportieren, und Sie selbst müssen die notwendige Übersetzung liefern. Betrachten wir ein konkretes Beispiel, das zeigt, wie ein öffentlicher Schlüssel verschiedenen Implementierungen mitgeteilt wird.
Der RSA-Algorithmus verwendet die folgenden allgemeinen Parameter: einen öffentlichen und privaten Exponenten, ein Modulus, ein Paar Primzahlen, ein anderes Paar Exponenten und einen Koeffizienten. Obwohl das sicher für Kryptographen und Mathematiker interessant ist, interessiert die meisten Entwickler nur, wie der öffentliche Teil des Schlüsselpaars freigegeben wird, und beim RSA-Algorithmus müssen Sie einfach den öffentlichen Exponenten und die Moduluswerte freigeben.
CNG stellt die Funktion „BCryptExportKey“ bereit, um das Exportieren eines Verschlüsselungsschlüssels in ein binäres Blob zu unterstützen, der nach Bedarf beibehalten oder freigegeben werden kann. Beachten Sie, dass wieder das gleiche Muster verwendet wird, das Sie schon öfter gesehen haben: Beim ersten Aufruf bestimmen Sie die Größe des Blobs, und dann wiederholen Sie den Aufruf mit dem neu zugewiesenen Puffer, um die exportierten Schlüsseldaten zu erhalten. Das Einzige, das hier beachtet werden sollte, ist der dritte Parameter:
NT_VERIFY(::BCryptExportKey(key,
                            0,   // reserved
                            BCRYPT_RSAPUBLIC_BLOB,
                            blob.GetData(),
                            blob.GetSize(),
                            &blobSize,
                            0)); // flags
Dieser Parameter gibt den zu erstellenden Blobtyp an. In diesem Beispiel zeigt die Kennung BCRYPT_RSAPUBLIC_BLOB an, dass die Funktion nur den öffentlichen Teil des RSA-Schlüssels exportieren soll.
Nun kommen wir zum interessanten Teil. Sie müssen dieses Blob aufspalten, um herauszufinden, was darin steckt. Denn die RSA-Klassen von .NET Framework verwenden für das Importieren von Schlüsseldaten ein anderes Format. Das Blob ist in Wirklichkeit ein einfaches Format, das mit der BCRYPT_RSAKEY_BLOB-Struktur als Kopfzeile beginnt. Diese Kopfzeile gibt den Blobtyp sowie den Umfang der verschiedenen Schlüsselparameter an. Abbildung 11 zeigt das Speicherlayout eines Blobs eines öffentlichen Schlüssels.
Abbildung 11 Speicherlayout eines Blobs eines öffentlichen Schlüssels 
Mit diesen Informationen zur Hand müssen einfach die notwendigen Speicherbereiche kopiert und eine RSAParameters-Struktur aufgefüllt werden, denn das erwarten die RSA-Klassen von .NET Framework beim Importieren von Schlüsseldaten.
Wie Sie in Abbildung 12 sehen können, habe ich mit C++/CLI ein systemeigenes RSA-Schlüsselblob direkt in eine verwaltete RSAParameters-Struktur konvertiert. Den Feldern Exponent und Modulus der Struktur werden neu erstellte, verwaltete Bytearrays zugewiesen, und die Werte werden dann durch Befestigen und anschließende Verwendung der Funktion „memcpy_s“ direkt in diese Bytearrays kopiert. Nehmen wir an, Sie haben das binäre Blob zum Beispiel in C# erhalten, dann könnten Sie die gleichen Ergebnisse mit der .NET Framework-Klasse „BinaryReader“ erhalten. Der Rest ist einfach:
BCRYPT_RSAKEY_BLOB* rsaBlob = 
    reinterpret_cast<BCRYPT_RSAKEY_BLOB*>(blob.GetData());
RSAParameters rsaParams;

rsaParams.Exponent = gcnew array<BYTE>(rsaBlob->cbPublicExp);
{
    pin_ptr<BYTE> destination = &rsaParams.Exponent[0];
    const BYTE* source = reinterpret_cast<BYTE*>(rsaBlob);

    memcpy_s(destination,
             rsaParams.Exponent->Length,
             source + sizeof(BCRYPT_RSAKEY_BLOB),
             rsaBlob->cbPublicExp);
}

rsaParams.Modulus = gcnew array<BYTE>(rsaBlob->cbModulus);
{
    pin_ptr<BYTE> destination = &rsaParams.Modulus[0];
    const BYTE* source = reinterpret_cast<BYTE*>(rsaBlob);

    memcpy_s(destination,
             rsaParams.Modulus->Length,
             source + sizeof(BCRYPT_RSAKEY_BLOB) + rsaBlob->cbPublicExp,
             rsaBlob->cbModulus);
}

RSACryptoServiceProvider rsa;
rsa.ImportParameters(rsaParams);
array<BYTE>^ ciphertext = rsa.Encrypt(plaintext, false);
Die RSACryptoServiceProvider-Klasse ist die Implementierung des RSA-Algorithmus in .NET Framework, die auf der CryptoAPI basiert. Die ImportParameters-Methode importiert die von uns vorbereitete RSAParameters-Struktur, und die Encrypt-Methode verwendet dann den öffentlichen Schlüssel zum Verschlüsseln einer Nachricht, die nur mithilfe des privaten Schlüssels entschlüsselt werden kann.

Ausblicke
Während ich diesen Artikel schreibe, arbeitet Microsoft bereits an der nächsten Version von Visual Studio (Codename „Orcas“), und zusammen mit dieser Veröffentlichung wird auch eine neue Version von .NET Framework eingeführt. Mit .NET Framework 3.5 wird es eine Reihe neuer Algorithmusimplementierungen geben, die auf CNG basieren. Von .NET Framework 3.5 können Sie eine noch größere Interoperabilität zwischen systemeigenem und verwaltetem Code erwarten, besonders im Bereich Kryptografie. Diese neuen Erweiterungen des Frameworks bieten einen Drop-In-Ersatz für viele der vorhandenen Implementierungen, da CNG der Hauptkryptografieanbieter für die Windows-Plattform wird. Es werden auch einige ganz neue Algorithmusimplementierungen eingeführt, die in .NET Framework zuvor noch nie verwendet wurden. Diese decken einige der Ellipsenkurvenalgorithmen ab, die von den Schlüsselspeicherfunktionen von CNG bereitgestellt werden. Diese Implementierungen fallen unter die als NCrypt bekannte Teilmenge von CNG, die ich in der Einführung zu diesem Artikel erwähnt habe. Weitere Informationen zu diesem Thema finden Sie in der Randleiste „CNG-Ressourcen“.
CNG-Ressourcen


Kenny Kerr ist Softwarespezialist, der auf die Softwareentwicklung für Windows spezialisiert ist. Er schreibt leidenschaftlich gern und unterrichtet Entwickler in den Bereichen Programmieren und Softwareentwurf. Sie erreichen Kenny Kerr unter weblogs.asp.net/kennykerr.

Page view tracker