Wir vertrauen auf Java

Allen vertrauen? Traue niemandem? Klingt ein bisschen wie die X-Files, aber wenn es um vertrauliche Informationen geht, ist es genauso wichtig zu wissen, wem Sie vertrauen, wie zu wissen, wem Sie vertrauen. Dieses Konzept ist für Anwendungen ebenso wichtig wie für Menschen. Schließlich haben wir Anträge zum Verwalter unserer Informationen und zum Verwalter unserer Ressourcen gemacht. Dies gilt unternehmensweit - Anwendungen enthalten wichtige Informationen über unser Unternehmen und unsere Kunden - und auf dem Desktop. Ich kann Ihnen nicht sagen, wie oft ich gefragt wurde, wie ein Applet geschrieben werden soll, das das Laufwerk eines Benutzers scannt, damit ein Benutzer den Browser eines anderen Benutzers steuern oder private Informationen erfassen kann.

Java, die Netzwerkentwicklungsplattform, die es ist, musste das Problem des Vertrauens direkt angehen. Das Ergebnis ist die Java Security API und die Java Cryptography Architecture.

Ein kurzer Blick zurück

Bevor ich mich kopfüber mit APIs, Code und Kommentaren befasse, möchte ich die Diskussion des letzten Monats noch einmal kurz betrachten. Wenn Sie zum ersten Mal bei uns sind, möchten Sie möglicherweise einen Monat sichern und "Signiert und zugestellt: Eine Einführung in Sicherheit und Authentifizierung" lesen. Diese Spalte bietet eine gründliche Einführung in alle Begriffe und Konzepte, die ich diesen Monat verwenden werde.

Sicherheit und Authentifizierung befassen sich mit zwei entscheidenden Aspekten: Der Nachweis, dass eine Nachricht von einer bestimmten Entität erstellt wurde, und der Nachweis, dass eine Nachricht nach ihrer Erstellung nicht manipuliert wurde. Eine Möglichkeit, diese beiden Ziele zu erreichen, ist die Verwendung digitaler Signaturen.

Digitale Signaturen hängen stark von einem Zweig der Kryptographie ab, der als Kryptographie mit öffentlichem Schlüssel bekannt ist. Algorithmen mit öffentlichem Schlüssel zeichnen sich dadurch aus, dass sie auf einem übereinstimmenden Schlüsselpaar (einem privaten und einem öffentlichen) und nicht auf einem einzelnen Schlüssel beruhen. Eine Entität hält ihren privaten Schlüssel geheim, stellt jedoch ihren öffentlichen Schlüssel zur Verfügung.

Ein Algorithmus für digitale Signaturen verwendet als Eingabe eine Nachricht und den privaten Schlüssel einer Entität und generiert eine digitale Signatur. Die digitale Signatur wird so erstellt, dass jeder den öffentlichen Schlüssel der Entität verwenden kann, um zu überprüfen, ob die Entität die betreffende Nachricht tatsächlich signiert hat. Wenn die ursprüngliche Nachricht manipuliert wurde, kann die Signatur nicht mehr überprüft werden. Digitale Signaturen bieten einen zusätzlichen Vorteil: Sobald eine Entität eine Nachricht signiert und verteilt hat, kann der Urheber nicht mehr leugnen, dass er die Nachricht signiert hat (ohne zu behaupten, dass sein privater Schlüssel gestohlen wurde).

Von Motoren und Anbietern

Die Java Cryptography API definiert das Java Toolkit für Sicherheit und Authentifizierung. Die Java Cryptography Architecture (JCA) beschreibt die Verwendung der API. Um sowohl für den Entwickler als auch für den Endbenutzer ein Höchstmaß an Flexibilität zu gewährleisten, verfolgt die JCA zwei Leitprinzipien:

  1. Die Architektur sollte die Unabhängigkeit und Erweiterbarkeit des Algorithmus unterstützen. Ein Entwickler muss in der Lage sein, Anwendungen zu schreiben, ohne sie zu eng an einen bestimmten Algorithmus zu binden. Darüber hinaus müssen neue Algorithmen bei der Entwicklung problemlos in vorhandene Algorithmen integriert werden können.

  2. Die Architektur sollte die Unabhängigkeit und Interoperabilität der Implementierung unterstützen. Ein Entwickler muss in der Lage sein, Anwendungen zu schreiben, ohne sie an die Implementierung eines Algorithmus durch einen bestimmten Anbieter zu binden. Darüber hinaus müssen Implementierungen eines Algorithmus, der von verschiedenen Anbietern bereitgestellt wird, zusammenarbeiten.

Um diese beiden Anforderungen zu erfüllen, basierten die Entwickler der Java Cryptography API ihr Design auf einem System von Engines und Anbietern.

Engines erzeugen Instanzen von Message-Digest-Generatoren, Generatoren für digitale Signaturen und Schlüsselpaar-Generatoren. Jede Instanz wird verwendet, um ihre entsprechende Funktion auszuführen.

Die kanonische Engine in der JCA ist eine Klasse, die eine statische Methode (oder Methoden) mit dem Namen bereitstellt getInstance(), die eine Instanz einer Klasse zurückgibt, die einen kryptografisch signifikanten Algorithmus implementiert. Die getInstance()Methode ist sowohl mit einem Argument als auch mit zwei Argumenten erhältlich. In beiden Fällen ist das erste Argument der Name des Algorithmus. Die JCA bietet eine Liste von Standardnamen, obwohl nicht alle in einer bestimmten Version bereitgestellt werden. Das zweite Argument wählt einen Anbieter aus.

Der SUN-Anbieter

In JDK 1.1 wird nur ein Anbieter - SUN - bereitgestellt. SUN bietet sowohl eine Implementierung des NIST Digital Signature Algorithm (DSA) als auch eine Implementierung der MD5- und NIST SHA-1-Message-Digest-Algorithmen.

Klasse MessageDigest

Zunächst betrachten wir den Code, der aus einer Nachricht einen Nachrichtenauszug generiert.

MessageDigest messagedigest = MessageDigest.getInstance ("SHA");

MessageDigest messagedigest = MessageDigest.getInstance ("SHA", "SUN");

Wie ich gerade erwähnte, gibt es die getInstance()Methode in zwei Varianten. Für den ersten muss nur der Algorithmus angegeben werden. Für den zweiten müssen sowohl der Algorithmus als auch der Anbieter angegeben werden. Beide geben eine Instanz einer Klasse zurück, die den SHA-Algorithmus implementiert.

Als nächstes leiten wir die Nachricht durch den Message-Digest-Generator.

int n = 0; Byte [] rgb = neues Byte [1000]; while ((n = inputstreamMessage.read (rgb))> -1) {messagedigest.update (rgb, 0, n); }}

Hier nehmen wir an, dass die Nachricht als Eingabestream verfügbar ist. Dieser Code eignet sich gut für große Nachrichten unbekannter Länge. Die update()Methode akzeptiert auch ein einzelnes Byte als Argument für Nachrichten mit einer Länge von wenigen Bytes und ein Byte-Array für Nachrichten mit einer festen oder vorhersagbaren Größe.

rgb = messagedigest.digest ();

Der letzte Schritt besteht darin, den Message Digest selbst zu generieren. Der resultierende Digest wird in einem Array von Bytes codiert.

Wie Sie sehen können, verbirgt der JCA bequem alle Implementierungs- und algorithmenspezifischen Details auf niedriger Ebene, sodass Sie auf einer höheren, abstrakteren Ebene arbeiten können.

Eines der Risiken eines solchen abstrakten Ansatzes ist natürlich die erhöhte Wahrscheinlichkeit, dass wir fehlerhafte Ausgaben aufgrund von Fehlern nicht erkennen. Angesichts der Rolle der Kryptographie kann dies ein erhebliches Problem sein.

Betrachten Sie den "Off-by-One" -Fehler in der folgenden Update-Zeile:

int n = 0; Byte [] rgb = neues Byte [1000]; while ((n = inputstreamMessage.read (rgb))> -1) {messagedigest.update (rgb, 0, n - 1); }}

C-, C ++ - und Java-Programmierer verwenden das Limit-minus-Eins-Idiom so häufig, dass die Eingabe fast automatisch erfolgt - auch wenn es nicht angemessen ist. Der obige Code wird kompiliert und die ausführbare Datei wird ohne Fehler oder Warnung ausgeführt, aber der resultierende Message Digest ist falsch.

Glücklicherweise ist der JCA gut durchdacht und gut gestaltet, was potenzielle Fallstricke wie die oben genannte relativ selten macht.

Bevor wir zu Schlüsselpaargeneratoren übergehen, werfen Sie einen Blick darauf

MessageDigestGenerator, der vollständige Quellcode für ein Programm, das einen Message Digest generiert.

Klasse KeyPairGenerator

Um eine digitale Signatur zu generieren (und Daten zu verschlüsseln), benötigen wir Schlüssel.

Key generation, in its algorithm-independent form, is not substantially more difficult than creating and using a message digest.

KeyPairGenerator keypairgenerator = KeyPairGenerator.getInstance("DSA");

As in the message digest example above, this code creates an instance of a class that generates DSA-compatible keys. A second (if necessary) argument specifies the provider.

After a key-pair generator instance is created, it must be initialized. We can initialize key-pair generators in one of two ways: algorithm-independent or algorithm-dependent. Which method you use depends on the amount of control you want over the final result.

keypairgenerator.initialize(1024, new SecureRandom());

Keys based on different algorithms differ in how they're generated, but they have one parameter in common -- the key's strength. Strength is a relative term that corresponds roughly to how hard the key will be to "break." If you use the algorithm-independent initializer, you can specify only the strength -- any algorithm-dependent values assume reasonable defaults.

DSAKeyPairGenerator dsakeypairgenerator = (DSAKeyPairGenerator) keypairgenerator; DSAParams dsaparams = new DSAParams() { private BigInteger p = BigInteger(...); private BigInteger q = BigInteger(...); private BigInteger g = BigInteger(...); public BigInteger getP() { return p; } public BigInteger getQ() { return q; } public BigInteger getG() { return g; } }; dsakeypairgenerator.initialize(dsaparams, new SecureRandom());

While the defaults are usually good enough, if you need more control, it is available. Let's assume you used the engine to create a generator of DSA-compatible keys, as in the code above. Behind the scenes, the engine loaded and instantiated an instance of a class that implements the DSAKeyPairGenerator interface. If we cast the generic key-pair generator we received to DSAKeyPairGenerator, we then gain access to the algorithm-dependent method of initialization.

To initialize a DSA key-pair generator, we need three values: the prime P, the subprime Q, and the base G. These values are captured in an inner class instance that is passed to the initialize() method.

The SecureRandom class provides a secure source of random numbers used in the key-pair generation.

return keypairgenerator.generateKeyPair();

The final step involves generating the key pair itself.

Before we move on to digital signatures, take a look at KeyTools, the complete source code for a program that generates a key pair.

Class Signature

The creation and use of an instance of the Signature class is not substantially different from either of the two previous examples. The differences lie in how the instance is used -- either to sign or to verify a message.

Signature signature = Signature.getInstance("DSA");

Just as before, we use the engine to get an instance of the appropriate type. What we do next depends on whether or not we are signing or verifying a message.

signature.initSign(privatekey);

In order to sign a message, we must first initialize the signature instance with the private key of the entity that is signing the message.

signature.initVerify(publickey);

In order to verify a message, we must initialize the signature instance with the public key of the entity that claims it signed the message.

int n = 0; byte [] rgb = new byte [1000]; while ((n = inputstreamMessage.read(rgb)) > -1) { signature.update(rgb, 0, n); }

Next, regardless of whether or not we are signing or verifying, we must pass the message through the signature generator. You'll notice how similar the process is to the earlier example of generating a message digest.

The final step consists of generating the signature or verifying a signature.

rgb = signature.sign();

If we are signing a message, the sign() method returns the signature.

signature.verify(rgbSignature);

If we are verifying the signature previously generated from a message, we must use the verify() method. It takes as a parameter the previously generated signature and determines whether or not it is still valid.

Before we wrap things up, take a look at Sign.java, the complete source code for a program that signs a message, and Verify.java, the complete source code for a program that verifies a message.

Conclusion

If you arm yourself with the tools and techniques I've presented this month, you'll be more than ready to secure your applications. The Java Cryptography API makes the process almost effortless. Release 1.2 of the Java Developers Kit promises even more. Stay tuned.

Nächsten Monat gehe ich zurück in das Gebiet der Middleware. Ich werde ein wenig RMI, etwas Threading und einen Haufen Code nehmen und Ihnen zeigen, wie Sie Ihre eigene nachrichtenorientierte Middleware erstellen.

Todd Sundsted schreibt Programme, seit Computer in praktischen Desktop-Modellen verfügbar sind. Obwohl Todd ursprünglich daran interessiert war, verteilte Objektanwendungen in C ++ zu erstellen, wechselte er zur Java-Programmiersprache, als dies die offensichtliche Wahl für solche Dinge wurde. Neben dem Schreiben ist Todd Präsident von Etcee, das Schulungs-, Mentoring-, Beratungs- und Softwareentwicklungsdienste anbietet.

Erfahren Sie mehr über dieses Thema

  • Laden Sie den vollständigen Quellcode herunter //www.javaworld.com/jw-01-1999/howto/jw-01-howto.zip
  • Übersicht über die Java-Sicherheits-API //www.javasoft.com/products/jdk/1.1/docs/guide/security/JavaSecurityOverview.html
  • Java Cryptography Architecture //www.javasoft.com/products/jdk/1.1/docs/guide/security/CryptoSpec.html
  • Suns Java-Sicherheitsseite //java.sun.com/security/index.html
  • RSAs FAQ zur Kryptographie //www.rsa.com/rsalabs/faq/
  • Kryptografische Richtlinien und Informationen //www.crypto.com/
  • Lesen Sie die vorherigen Java-Spalten von Todd unter //www.javaworld.com/topicalindex/jw-ti-howto.html

Diese Geschichte "In Java vertrauen wir" wurde ursprünglich von JavaWorld veröffentlicht.