Nel post di oggi scrivo la mia esperienza per utilizzare la crittografia assimetrica per l’invio di un file su un canale sicuro.
Crittografia
La crittografia deve consentire di:
- codificare il file affinchè solo il destinatario sia in grado di leggerlo
- autenticare il file inviato affinchè il destinatario sia inequivocabilmente sicuro dell’identità del mittente
- autenticare sia il mittente sia il file affinchè il destinatario sia certo che il messaggio non è stato alterato
Per far si di utilizzare un meccanismo che fosse abbastanza diffuso e alla portata di tutti mi sono ricondotto a pgp (pretty good privacy) o alle versioni open (openpgp) quali GnuPG.
Per fare questo ho utilizzato la libreria del “Legion of Bouncing Castle” (per vincoli non funzionali ho scaricato la versione per jdk 1.4). In generale BC è un implementazione della Java Cryptography Architecture (JCA).
JCA è la definizione di una architettura per la sicurezza indipendente dala sua implementazione con gli obiettivi di essere interoperabile (a livello di algoritmi) ed estendibile. La scelta di Bouncing Castle è ricaduta per la sua licenza (MIT) e per il fatto che essendo un progetto Australiano ha utilizzi meno restrittivi rispetto ad eguali prodotti Statunitensi (leggi sulle esportazioni della crittografia).
Utilizzo
Il mio scopo è di utilizzare la crittografia in un portale per generare un file crittato, esportarlo via mail e permettere a chi lo riceve (se in possesso di chiave) di leggerlo. Per la lettura non volevo fornire al cliente un software ad hoc, ma ho preferito fargli usare i diffusissimi pgp (pretty good privacy) o la sua versione open gpg (Gnu Protection Guard).
La documentazione di BC è alquanto scarna, ma - grazie al cielo - gli esempi sono chiarissimi. Di seguito riporto i due file (leggermente modificati) che mi permettono:
- il primo di generare una coppia di chaivi assimetriche e di esportarle in file ASCII
- il secondo di usare la coppia di chiavi per crittare decrittare un file
A questo punto customiizzare questi due file, per renderli disponibili su un portale, sarà un lavoro banale
RSAKeyPairGenerator
/*
* Creato il 28-ago-2008
*
*
*/
package com.giampierogranatella.lbc.examples;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.SignatureException;
import java.util.Date;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSignature;
/**
* A simple utility class that generates a RSA PGPPublicKey/PGPSecretKey pair.
* <p>
* usage: RSAKeyPairGenerator [-a] identity passPhrase
* <p>
* Where identity is the name to be associated with the public key. The keys are
* placed in the files pub.[asc|bpg] and secret.[asc|bpg].
*/
public class RSAKeyPairGenerator {
private static void exportKeyPair(OutputStream secretOut,
OutputStream publicOut, PublicKey publicKey, PrivateKey privateKey,
String identity, char[] passPhrase, boolean armor)
throws IOException, InvalidKeyException, NoSuchProviderException,
SignatureException, PGPException {
if (armor) {
secretOut = new ArmoredOutputStream(secretOut);
}
PGPSecretKey secretKey = new PGPSecretKey(
PGPSignature.DEFAULT_CERTIFICATION, PGPPublicKey.RSA_GENERAL,
publicKey, privateKey, new Date(), identity,
PGPEncryptedData.CAST5, passPhrase, null, null,
new SecureRandom(), “BC”);
secretKey.encode(secretOut);
secretOut.close();
if (armor) {
publicOut = new ArmoredOutputStream(publicOut);
}
PGPPublicKey key = secretKey.getPublicKey();
key.encode(publicOut);
publicOut.close();
}
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
KeyPairGenerator kpg = KeyPairGenerator.getInstance(”RSA”, “BC”);
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
FileOutputStream out1 = new FileOutputStream(”c:\\keys\\secret.asc”);
FileOutputStream out2 = new FileOutputStream(”c:\\keys\\pub.asc”);
exportKeyPair(out1, out2, kp.getPublic(), kp.getPrivate(), “Giampiero Granatella”,
“pippo”.toCharArray(), true);
}
}
KeyBasedLargeFileProcessor.java
/*
* Creato il 29-ago-2008
*
*/
package com.giampierogranatella.lbc.examples;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPUtil;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Iterator;
/**
* A simple utility class that encrypts/decrypts public key based encryption
* large files.
* <p>
* To encrypt a file: KeyBasedLargeFileProcessor -e [-a|-ai] fileName
* publicKeyFile. <br>
* If -a is specified the output file will be “ascii-armored”. If -i is
* specified the output file will be have integrity checking added.
* <p>
* To decrypt: KeyBasedLargeFileProcessor -d fileName secretKeyFile passPhrase.
* <p>
* Note 1: this example will silently overwrite files, nor does it pay any
* attention to the specification of “_CONSOLE” in the filename. It also expects
* that a single pass phrase will have been used.
* <p>
* Note 2: this example generates partial packets to encode the file, the output
* it generates will not be readable by older PGP products or products that
* don’t support partial packet encoding.
* <p>
* Note 3: if an empty file name has been specified in the literal data object
* contained in the encrypted packet a file with the name filename.out will be
* generated in the current working directory.
*/
public class KeyBasedLargeFileProcessor {
/**
* A simple routine that opens a key ring file and loads the first available
* key suitable for encryption.
*
* @param in
* @return
* @throws IOException
* @throws PGPException
*/
private static PGPPublicKey readPublicKey(InputStream in)
throws IOException, PGPException {
in = PGPUtil.getDecoderStream(in);
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(in);
PGPPublicKey key = null;
Iterator rIt = pgpPub.getKeyRings();
while (key == null && rIt.hasNext()) {
PGPPublicKeyRing kRing = (PGPPublicKeyRing) rIt.next();
Iterator kIt = kRing.getPublicKeys();
while (key == null && kIt.hasNext()) {
PGPPublicKey k = (PGPPublicKey) kIt.next();
if (k.isEncryptionKey()) {
key = k;
}
}
}
if (key == null) {
throw new IllegalArgumentException(
“Can’t find encryption key in key ring.”);
}
return key;
}
/**
* Search a secret key ring collection for a secret key corresponding to
* keyID if it exists.
*
* @param pgpSec
* a secret key ring collection.
* @param keyID
* keyID we want.
* @param pass
* passphrase to decrypt secret key with.
* @return
* @throws PGPException
* @throws NoSuchProviderException
*/
private static PGPPrivateKey findSecretKey(
PGPSecretKeyRingCollection pgpSec, long keyID, char[] pass)
throws PGPException, NoSuchProviderException {
PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID);
if (pgpSecKey == null) {
return null;
}
return pgpSecKey.extractPrivateKey(pass, “BC”);
}
/**
* decrypt the passed in message stream
*/
private static void decryptFile(InputStream in, InputStream keyIn,
char[] passwd, String fileName) throws Exception {
in = PGPUtil.getDecoderStream(in);
try {
PGPObjectFactory pgpF = new PGPObjectFactory(in);
PGPEncryptedDataList enc;
Object o = pgpF.nextObject();
//
// the first object might be a PGP marker packet.
//
if (o instanceof PGPEncryptedDataList) {
enc = (PGPEncryptedDataList) o;
} else {
enc = (PGPEncryptedDataList) pgpF.nextObject();
}
//
// find the secret key
//
Iterator it = enc.getEncryptedDataObjects();
PGPPrivateKey sKey = null;
PGPPublicKeyEncryptedData pbe = null;
PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(
PGPUtil.getDecoderStream(keyIn));
while (sKey == null && it.hasNext()) {
pbe = (PGPPublicKeyEncryptedData) it.next();
sKey = findSecretKey(pgpSec, pbe.getKeyID(), passwd);
}
if (sKey == null) {
throw new IllegalArgumentException(
“secret key for message not found.”);
}
InputStream clear = pbe.getDataStream(sKey, “BC”);
PGPObjectFactory plainFact = new PGPObjectFactory(clear);
PGPCompressedData cData = (PGPCompressedData) plainFact
.nextObject();
InputStream compressedStream = new BufferedInputStream(cData
.getDataStream());
PGPObjectFactory pgpFact = new PGPObjectFactory(compressedStream);
Object message = pgpFact.nextObject();
if (message instanceof PGPLiteralData) {
PGPLiteralData ld = (PGPLiteralData) message;
FileOutputStream fOut = new FileOutputStream(fileName);
BufferedOutputStream bOut = new BufferedOutputStream(fOut);
InputStream unc = ld.getInputStream();
int ch;
while ((ch = unc.read()) >= 0) {
bOut.write(ch);
}
bOut.close();
} else if (message instanceof PGPOnePassSignatureList) {
throw new PGPException(
“encrypted message contains a signed message - not literal data.”);
} else {
throw new PGPException(
“message is not a simple encrypted file - type unknown.”);
}
if (pbe.isIntegrityProtected()) {
if (!pbe.verify()) {
System.err.println(”message failed integrity check”);
} else {
System.err.println(”message integrity check passed”);
}
} else {
System.err.println(”no message integrity check”);
}
} catch (PGPException e) {
System.err.println(e);
if (e.getUnderlyingException() != null) {
e.getUnderlyingException().printStackTrace();
}
}
}
private static void encryptFile(OutputStream out, String fileName,
PGPPublicKey encKey, boolean armor, boolean withIntegrityCheck)
throws IOException, NoSuchProviderException {
if (armor) {
out = new ArmoredOutputStream(out);
}
try {
PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(
PGPEncryptedData.CAST5, withIntegrityCheck,
new SecureRandom(), “BC”);
cPk.addMethod(encKey);
OutputStream cOut = cPk.open(out, new byte[1 << 16]);
PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(
PGPCompressedData.ZIP);
PGPUtil.writeFileToLiteralData(comData.open(cOut),
PGPLiteralData.BINARY, new File(fileName),
new byte[1 << 16]);
comData.close();
cOut.close();
out.close();
} catch (PGPException e) {
System.err.println(e);
if (e.getUnderlyingException() != null) {
e.getUnderlyingException().printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
try {
Security.addProvider(new BouncyCastleProvider());
// encript
FileInputStream keyInSec = new FileInputStream(”C:\\keys\\pub.asc”);
FileOutputStream out = new FileOutputStream(”C:\\crypto.crg”);
encryptFile(out, “C:\\msg.txt”, readPublicKey(keyInSec), true, true);
// decript
FileInputStream in = new FileInputStream(”C:\\crypto.gpg”);
FileInputStream keyInPub = new FileInputStream(”C:\\keys\\secret.asc”);
decryptFile(in, keyInPub, “pippo”.toCharArray(), “c:/gpg.txt”);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Così a questo punto ho il mio file crypto.gpg pronto per essere decrittato da gpg. Prima di fare questo dobbiamo importare le chiavi generate in gpg (o pgp). Con gpg ho eseguito queste istruzioni da linea di comando.
C:\Program Files\Windows Privacy Tools\GnuPG>gpg --import -a c:\keys\pub.asc
gpg: key A4AEA95D: "Giampiero Granatella" not changed
gpg: Total number processed: 1
gpg: unchanged: 1
C:\Program Files\Windows Privacy Tools\GnuPG>gpg --import -a c:\keys\secret.asc
gpg: key A4AEA95D: secret key imported
gpg: Total number processed: 1
gpg: secret keys read: 1
gpg: secret keys imported: 1
Sempre da linea di comando eseguo la decrittazione del file…
C:\Program Files\Windows Privacy Tools\GnuPG>gpg -d c:\crypto.gpg
You need a passphrase to unlock the secret key for
user: "Giampiero Granatella"
1024-bit RSA key, ID A4AEA95D, created 2008-08-28
gpg: NOTE: cipher algorithm 3 not found in preferences
gpg: encrypted with 1024-bit RSA key, ID A4AEA95D, created 2008-08-28
"Giampiero Granatella"
E questo è l’obiettivo che mi ero prefissato.