com.goodvikings.cryptim.api.KeyRing.java Source code

Java tutorial

Introduction

Here is the source code for com.goodvikings.cryptim.api.KeyRing.java

Source

/*
 * ------------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Ramo's Revision):
 * <ramo@goodvikings.com> wrote this file. As long as you retain this notice on
 * this and any derivative works you can do whatever you want with this stuff.
 * If we meet some day, and you think this stuff is worth it, you can buy me a
 * beer in return - Ramo
 * ------------------------------------------------------------------------------
 */

package com.goodvikings.cryptim.api;

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.text.ParseException;
import java.util.Date;
import java.util.TreeMap;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1UTCTime;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERUTCTime;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
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.PGPKeyPair;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPOnePassSignature;
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter;
import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
import org.bouncycastle.util.io.Streams;
import sun.security.rsa.RSAPublicKeyImpl;

class KeyRing {

    public KeyRing() {
        keys = new TreeMap<>();
        nicks = new TreeMap<>();
        salt = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
        rand = new SecureRandom();
    }

    public void open(String filename, CallbackHandler cbh, boolean createIfNotExists)
            throws BadPaddingException, IOException, IllegalBlockSizeException, InvalidAlgorithmParameterException,
            InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException,
            NoSuchProviderException, PGPException, ParseException, CryptimException {
        this.filename = filename;

        File f = new File(filename);

        if (f.exists()) {
            openExisting(cbh);
        } else {
            if (createIfNotExists) {
                genOwnKeys();
            } else {
                throw new CryptimException("Key db doesn't exist, not creating one");
            }
        }
    }

    public void saveKDB(CallbackHandler cbh) throws BadPaddingException, IOException, IllegalBlockSizeException,
            InvalidAlgorithmParameterException, InvalidKeyException, InvalidKeySpecException,
            NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, PGPException {
        saveKDB(filename, cbh);
    }

    public void saveKDB(String filename, CallbackHandler cbh) throws IOException, NoSuchAlgorithmException,
            NoSuchProviderException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException,
            InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, PGPException {
        PasswordCallback pcb = new PasswordCallback("Password", false);
        try {
            cbh.handle(new Callback[] { pcb });
        } catch (UnsupportedCallbackException e) {
            // I just set the callback, this exception will never be thrown
        }

        byte data[] = ASN1EncodeKeys();

        SecretKeyFactory skf = SecretKeyFactory.getInstance(KEY_ALG, PROVIDER);
        SecretKey key = skf.generateSecret(new PBEKeySpec(pcb.getPassword(), salt, ITERATION_COUNT, SYMM_KEY_SIZE));

        pcb.clearPassword();

        Cipher c = Cipher.getInstance(KEY_ALG, PROVIDER);
        c.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(salt, ITERATION_COUNT));

        byte[] ciphertext = c.doFinal(data);

        FileOutputStream fos = new FileOutputStream(filename);
        fos.write(ciphertext);
        fos.close();
    }

    public void addPublicKey(String jid, String nick, PGPPublicKey key) {
        keys.put(jid, key);
        nicks.put(jid, nick);
    }

    public PGPPublicKey getMyPublic() {
        return kp.getPublicKey();
    }

    public void signEncryptMessage(InputStream in, OutputStream out, String jid)
            throws IOException, PGPException, SignatureException {
        out = new ArmoredOutputStream(out);

        PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(new JcePGPDataEncryptorBuilder(SYMM_ALG)
                .setWithIntegrityPacket(true).setSecureRandom(rand).setProvider(PROVIDER));
        encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(keys.get(jid)).setProvider(PROVIDER));

        OutputStream encryptedOut = encGen.open(out, new byte[BUFFER_SIZE]);
        OutputStream compressedData = new PGPCompressedDataGenerator(COMP_ALG).open(encryptedOut);

        PGPSignatureGenerator sGen = new PGPSignatureGenerator(
                new JcaPGPContentSignerBuilder(kp.getPrivateKey().getPublicKeyPacket().getAlgorithm(), HASH_ALG)
                        .setProvider(PROVIDER));
        sGen.init(PGPSignature.BINARY_DOCUMENT, kp.getPrivateKey());
        sGen.generateOnePassVersion(false).encode(compressedData);

        OutputStream finalOut = new PGPLiteralDataGenerator().open(compressedData, PGPLiteralData.BINARY, "",
                new Date(), new byte[BUFFER_SIZE]);

        byte[] buf = new byte[BUFFER_SIZE];
        int len;
        while ((len = in.read(buf)) > 0) {
            finalOut.write(buf, 0, len);
            sGen.update(buf, 0, len);
        }

        in.close();

        finalOut.close();
        sGen.generate().encode(compressedData);
        compressedData.close();
        encryptedOut.close();
        out.close();
    }

    public boolean decryptVerifyMessage(InputStream in, OutputStream out, String jid)
            throws IOException, PGPException, SignatureException {
        in = new ArmoredInputStream(in);

        PGPObjectFactory plainFact = new PGPObjectFactory(
                ((PGPPublicKeyEncryptedData) ((PGPEncryptedDataList) new PGPObjectFactory(in).nextObject())
                        .getEncryptedDataObjects().next())
                                .getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider(PROVIDER)
                                        .build(kp.getPrivateKey())));

        PGPOnePassSignatureList onePassSignatureList = null;
        PGPSignatureList signatureList = null;
        PGPCompressedData compressedData = null;

        Object obj = plainFact.nextObject();
        ByteArrayOutputStream actualOutput = new ByteArrayOutputStream();

        while (obj != null) {
            if (obj instanceof PGPCompressedData) {
                compressedData = (PGPCompressedData) obj;
                plainFact = new PGPObjectFactory(compressedData.getDataStream());
                obj = plainFact.nextObject();
            }
            if (obj instanceof PGPLiteralData) {
                Streams.pipeAll(((PGPLiteralData) obj).getInputStream(), actualOutput);
            } else if (obj instanceof PGPOnePassSignatureList) {
                onePassSignatureList = (PGPOnePassSignatureList) obj;
            } else if (obj instanceof PGPSignatureList) {
                signatureList = (PGPSignatureList) obj;
            } else {
                throw new PGPException("message unknown message type.");
            }
            obj = plainFact.nextObject();
        }

        actualOutput.close();
        byte[] output = actualOutput.toByteArray();

        PGPOnePassSignature ops = onePassSignatureList.get(0);
        ops.init(new JcaPGPContentVerifierBuilderProvider().setProvider(PROVIDER), keys.get(jid));
        ops.update(output);

        out.write(output);
        out.flush();
        out.close();

        return ops.verify(signatureList.get(0));
    }

    public boolean hasKeyForUser(String user) {
        if (keys.containsKey(user)) {
            return true;
        }

        for (String str : nicks.keySet()) {
            if (nicks.get(str).equals(user)) {
                return keys.containsKey(str);
            }
        }

        return false;
    }

    private boolean openExisting(CallbackHandler cbh)
            throws IOException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException,
            NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException,
            IllegalBlockSizeException, BadPaddingException, PGPException, ParseException {
        PasswordCallback pcb = new PasswordCallback("Password", false);
        try {
            cbh.handle(new Callback[] { pcb });
        } catch (UnsupportedCallbackException e) {
            // I just set the callback, this exception will never be thrown
        }

        byte[] ciphertext = Files.readAllBytes(Paths.get(filename));

        SecretKeyFactory skf = SecretKeyFactory.getInstance(KEY_ALG, PROVIDER);
        SecretKey key = skf.generateSecret(new PBEKeySpec(pcb.getPassword(), salt, ITERATION_COUNT, SYMM_KEY_SIZE));

        pcb.clearPassword();

        Cipher c = Cipher.getInstance(KEY_ALG, PROVIDER);
        c.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(salt, ITERATION_COUNT));

        byte[] plain = c.doFinal(ciphertext);

        ASN1DecodeKeys(plain);

        return testKeyRing();
    }

    private void genOwnKeys() throws PGPException {
        RSAKeyPairGenerator kpg = new RSAKeyPairGenerator();
        kpg.init(new RSAKeyGenerationParameters(BigInteger.valueOf(0x10001), new SecureRandom(), ASYMM_KEY_SIZE,
                90));

        kp = new BcPGPKeyPair(PGPPublicKey.RSA_GENERAL, kpg.generateKeyPair(), new Date());
    }

    private byte[] ASN1EncodeKeys() throws IOException, PGPException {
        JcaPGPKeyConverter converter = new JcaPGPKeyConverter();

        PrivateKey priv = converter.getPrivateKey(kp.getPrivateKey());
        PublicKey pub = converter.getPublicKey(kp.getPublicKey());

        ASN1EncodableVector pubSeq = new ASN1EncodableVector();

        for (String jid : keys.keySet()) {
            pubSeq.add(new DERSequence(new ASN1Encodable[] { new DERUTF8String(jid),
                    new DERUTF8String(nicks.get(jid)), new DERUTCTime(keys.get(jid).getCreationTime()),
                    new DEROctetString(converter.getPublicKey(keys.get(jid)).getEncoded()) }));
        }

        DERSequence seq = new DERSequence(new ASN1Encodable[] {
                new DERSequence(new ASN1Encodable[] { new DERUTCTime(kp.getPublicKey().getCreationTime()),
                        new DEROctetString(pub.getEncoded()) }),
                new DEROctetString(priv.getEncoded()), new DERSequence(pubSeq) });

        return seq.getEncoded();
    }

    private void ASN1DecodeKeys(byte[] plain) throws IOException, PGPException, NoSuchProviderException,
            ParseException, InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException {
        JcaPGPKeyConverter converter = new JcaPGPKeyConverter();

        ASN1Sequence seq = (ASN1Sequence) new ASN1InputStream(new ByteArrayInputStream(plain)).readObject();

        PGPPublicKey pub = converter.getPGPPublicKey(PGPPublicKey.RSA_GENERAL,
                new RSAPublicKeyImpl(
                        ((ASN1OctetString) ((ASN1Sequence) seq.getObjectAt(0)).getObjectAt(1)).getOctets()),
                ((ASN1UTCTime) ((ASN1Sequence) seq.getObjectAt(0)).getObjectAt(0)).getAdjustedDate());
        kp = new PGPKeyPair(pub, converter.getPGPPrivateKey(pub, KeyFactory.getInstance("RSA")
                .generatePrivate(new PKCS8EncodedKeySpec(((ASN1OctetString) seq.getObjectAt(1)).getOctets()))));

        ASN1Sequence keySeq = (ASN1Sequence) seq.getObjectAt(2);

        for (int i = 0; i < keySeq.size(); i++) {
            keys.put(((DERUTF8String) ((ASN1Sequence) keySeq.getObjectAt(i)).getObjectAt(0)).getString(),
                    converter.getPGPPublicKey(PGPPublicKey.RSA_GENERAL, new RSAPublicKeyImpl(
                            ((ASN1OctetString) ((ASN1Sequence) keySeq.getObjectAt(i)).getObjectAt(3)).getOctets()),
                            ((ASN1UTCTime) ((ASN1Sequence) keySeq.getObjectAt(i)).getObjectAt(2))
                                    .getAdjustedDate()));
            nicks.put(((DERUTF8String) ((ASN1Sequence) keySeq.getObjectAt(i)).getObjectAt(0)).getString(),
                    ((DERUTF8String) ((ASN1Sequence) keySeq.getObjectAt(i)).getObjectAt(1)).getString());
        }
    }

    private boolean testKeyRing() throws BadPaddingException, IllegalBlockSizeException, InvalidKeyException,
            NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, PGPException {
        JcaPGPKeyConverter conv = new JcaPGPKeyConverter();

        PublicKey pub = conv.getPublicKey(kp.getPublicKey());
        PrivateKey priv = conv.getPrivateKey(kp.getPrivateKey());

        String testData = "The quick brown fox jumps over the lazy dog.";

        Cipher enc = Cipher.getInstance(pub.getAlgorithm(), PROVIDER);
        enc.init(Cipher.ENCRYPT_MODE, pub);
        byte[] ciphertext = enc.doFinal(testData.getBytes());

        Cipher dec = Cipher.getInstance(pub.getAlgorithm(), PROVIDER);
        dec.init(Cipher.DECRYPT_MODE, priv);
        return new String(dec.doFinal(ciphertext)).equals(testData);
    }

    private TreeMap<String, PGPPublicKey> keys;
    private TreeMap<String, String> nicks;
    private PGPKeyPair kp;
    private String filename;
    private byte[] salt;
    private SecureRandom rand;
    // Constants
    private static final String KEY_ALG = "PBEWITHSHA256AND256BITAES-CBC-BC";
    private static final int SYMM_KEY_SIZE = 256;
    private static final int ASYMM_KEY_SIZE = 4096;
    private static final int ITERATION_COUNT = 10000;
    private static final String PROVIDER = "BC";
    private static final int BUFFER_SIZE = 4096;
    private static final int SYMM_ALG = PGPEncryptedData.AES_256;
    private static final int COMP_ALG = PGPCompressedData.ZIP;
    private static final int HASH_ALG = PGPUtil.SHA512;
}