In Java ci fidiamo

Fidarsi di tutti? Non fidarti di nessuno? Suona un po 'come X-Files, ma quando si tratta di informazioni riservate, sapere di chi ti fidi è importante quanto sapere di cosa ti fidi. Questo concetto è tanto importante per le applicazioni quanto lo è per le persone. Dopo tutto, abbiamo fatto delle applicazioni i custodi delle nostre informazioni e gli amministratori delle nostre risorse. È vero in tutta l'azienda: le applicazioni contengono informazioni critiche sulla nostra attività e sui nostri clienti, ed è vero sul desktop. Non posso dirti quante volte mi è stato chiesto come scrivere un'applet che scansiona l'unità di un utente in modo che un utente possa requisire il browser di un altro utente o acquisire informazioni private.

Java, essendo la piattaforma di sviluppo di rete che è, ha dovuto affrontare il problema della fiducia a testa alta. Il risultato è Java Security API e Java Cryptography Architecture.

Un breve sguardo all'indietro

Prima di immergermi a capofitto nelle API, nel codice e nei commenti, vorrei rivisitare brevemente la discussione del mese scorso. Se ti unisci a noi per la prima volta, potresti voler eseguire il backup di un mese e leggere "Firmato e consegnato: un'introduzione alla sicurezza e all'autenticazione". Questa colonna fornisce un'introduzione completa a tutti i termini e concetti che userò questo mese.

La sicurezza e l'autenticazione affrontano due problemi cruciali: quello di provare che un messaggio è stato creato da una particolare entità e quello di provare che un messaggio non è stato manomesso dopo che è stato creato. Un modo per raggiungere entrambi questi obiettivi è l'uso delle firme digitali.

Le firme digitali dipendono fortemente da un ramo della crittografia noto come crittografia a chiave pubblica. Gli algoritmi a chiave pubblica sono caratterizzati dal fatto che si basano su una coppia di chiavi abbinate (una privata e una pubblica) piuttosto che su una singola chiave. Un'entità mantiene segreta la sua chiave privata, ma rende disponibile la sua chiave pubblica.

Un algoritmo di firma digitale prende come input un messaggio e la chiave privata di un'entità e genera una firma digitale. La firma digitale viene creata in modo tale che chiunque possa prendere la chiave pubblica dell'entità e utilizzarla per verificare che l'entità abbia effettivamente firmato il messaggio in questione. Inoltre, se il messaggio originale è stato manomesso, la firma non può più essere verificata. Le firme digitali forniscono un ulteriore vantaggio: una volta che un'entità ha firmato e distribuito un messaggio, è impossibile per il suo autore negare di aver firmato il messaggio (senza rivendicare comunque che la sua chiave privata sia stata rubata).

Di motori e fornitori

L'API di crittografia Java definisce il toolkit Java per la sicurezza e l'autenticazione. La Java Cryptography Architecture (JCA) descrive come utilizzare l'API. Per garantire il massimo grado di flessibilità sia per lo sviluppatore che per l'utente finale, JCA abbraccia due principi guida:

  1. L'architettura dovrebbe supportare l'indipendenza e l'estensibilità dell'algoritmo. Uno sviluppatore deve essere in grado di scrivere applicazioni senza legarle troppo strettamente a un particolare algoritmo. Inoltre, man mano che vengono sviluppati nuovi algoritmi, devono essere facilmente integrati con gli algoritmi esistenti.

  2. L'architettura dovrebbe supportare l'indipendenza e l'interoperabilità dell'implementazione. Uno sviluppatore deve essere in grado di scrivere applicazioni senza vincolarle all'implementazione di un algoritmo da parte di un particolare fornitore. Inoltre, le implementazioni di un algoritmo fornito da diversi fornitori devono interagire.

Per soddisfare questi due requisiti, gli sviluppatori dell'API di crittografia Java hanno basato la loro progettazione su un sistema di motori e provider.

I motori producono istanze di generatori di digest di messaggi, generatori di firme digitali e generatori di coppie di chiavi. Ogni istanza viene utilizzata per svolgere la sua funzione corrispondente.

Il motore canonico in JCA è una classe che fornisce uno o più metodi statici denominati getInstance(), che restituisce un'istanza di una classe che implementa un algoritmo crittograficamente significativo. Il getInstance()metodo è disponibile sia in una forma a un argomento che in una a due argomenti. In entrambi i casi, il primo argomento è il nome dell'algoritmo. JCA fornisce un elenco di nomi standard, sebbene non tutti verranno forniti in una particolare versione. Il secondo argomento seleziona un provider.

Il fornitore SUN

Un solo provider - SUN - è fornito in JDK 1.1. SUN fornisce sia un'implementazione del NIST Digital Signature Algorithm (DSA), sia un'implementazione degli algoritmi MD5 e NIST SHA-1 message digest.

Classe MessageDigest

Inizieremo esaminando il codice che genera un messaggio digest da un messaggio.

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

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

Come ho detto poco fa, il getInstance()metodo è disponibile in due gusti. Il primo richiede che venga specificato solo l'algoritmo. Il secondo richiede che siano specificati sia l'algoritmo che il provider. Entrambi restituiscono un'istanza di una classe che implementa l'algoritmo SHA.

Successivamente, passiamo il messaggio attraverso il generatore di message-digest.

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

Qui, assumiamo che il messaggio sia disponibile come flusso di input. Questo codice funziona bene per messaggi di grandi dimensioni di lunghezza sconosciuta. Il update()metodo accetta anche un singolo byte come argomento per messaggi di pochi byte di lunghezza e un array di byte per messaggi di dimensioni fisse o prevedibili.

rgb = messagedigest.digest ();

Il passaggio finale prevede la generazione del digest del messaggio stesso. Il digest risultante è codificato in una matrice di byte.

Come puoi vedere, JCA nasconde comodamente tutti i dettagli di implementazione di basso livello e specifici dell'algoritmo, permettendoti di lavorare a un livello più alto e più astratto.

Naturalmente, uno dei rischi di un approccio così astratto è la maggiore probabilità di non riconoscere un output errato derivante da bug. Dato il ruolo della crittografia, questo può essere un problema significativo.

Considera il bug "off-by-one" nella riga di aggiornamento di seguito:

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

I programmatori C, C ++ e Java usano l'idioma limite meno uno così frequentemente che digitarlo diventa quasi automatico, anche quando non è appropriato. Il codice precedente verrà compilato e l'eseguibile verrà eseguito senza errori o avvisi, ma il digest del messaggio risultante sarà errato.

Fortunatamente, il JCA è ben pensato e ben progettato, rendendo relativamente rari potenziali insidie ​​come quella sopra.

Prima di passare ai generatori di coppie di chiavi, dai un'occhiata a

MessageDigestGenerator, il codice sorgente completo per un programma che genera un messaggio digest.

Classe KeyPairGenerator

Per generare una firma digitale (e crittografare i dati), abbiamo bisogno delle chiavi.

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.

Il mese prossimo tornerò nel territorio del middleware. Prenderò un po 'di RMI, un po' di threading e un mucchio di codice e ti mostrerò come creare il tuo middleware orientato ai messaggi.

Todd Sundsted scrive programmi da quando i computer sono diventati disponibili in comodi modelli desktop. Sebbene inizialmente interessato alla creazione di applicazioni a oggetti distribuiti in C ++, Todd è passato al linguaggio di programmazione Java quando è diventato la scelta più ovvia per quel genere di cose. Oltre a scrivere, Todd è presidente di Etcee che offre servizi di formazione, tutoraggio, consulenza e sviluppo software.

Ulteriori informazioni su questo argomento

  • Scarica il codice sorgente completo //www.javaworld.com/jw-01-1999/howto/jw-01-howto.zip
  • Panoramica dell'API di sicurezza Java //www.javasoft.com/products/jdk/1.1/docs/guide/security/JavaSecurityOverview.html
  • Architettura di crittografia Java //www.javasoft.com/products/jdk/1.1/docs/guide/security/CryptoSpec.html
  • Pagina di sicurezza Java di Sun //java.sun.com/security/index.html
  • Domande frequenti di RSA sulla crittografia //www.rsa.com/rsalabs/faq/
  • Politica e informazioni crittografiche //www.crypto.com/
  • Leggi le precedenti colonne How-To Java di Todd //www.javaworld.com/topicalindex/jw-ti-howto.html

Questa storia, "In Java we trust" è stata originariamente pubblicata da JavaWorld.