cf.monteux.silvertunnel.netlib.layer.tor.util.Encryption.java Source code

Java tutorial

Introduction

Here is the source code for cf.monteux.silvertunnel.netlib.layer.tor.util.Encryption.java

Source

/*
 * OnionCoffee - Anonymous Communication through TOR Network
 * Copyright (C) 2005-2007 RWTH Aachen University, Informatik IV
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */
/*
 * SilverTunnel-Monteux Netlib - Java library to easily access anonymity networks
 * Copyright (c) 2009-2012 silvertunnel.org
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, see <http://www.gnu.org/licenses/>.
 */
/*
 * SilverTunnel-Monteux Netlib - Java library to easily access anonymity networks
 * Copyright (c) 2013 silvertunnel-ng.org
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, see <http://www.gnu.org/licenses/>.
 */
/*
 * SilverTunnel-Monteux Netlib - Java library to easily access anonymity networks
 * Copyright (c) 2014 Rove Monteux
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, see <http://www.gnu.org/licenses/>.
 */

package cf.monteux.silvertunnel.netlib.layer.tor.util;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1OutputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;
//import org.bouncycastle.asn1.pkcs.RSAPublicKey;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.encodings.OAEPEncoding;
import org.bouncycastle.crypto.engines.RSAEngine;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.jce.provider.JCERSAPublicKey;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.util.encoders.Base64;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.crypto.Cipher;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Arrays;

/**
 * this class contains utility functions concerning encryption.
 *
 * @author Lexi Pimenidis
 * @author Andriy Panchenko
 * @author Michael Koellejan
 * @author hapke
 * @author Tobias Boese
 * @author Rove Monteux
 */
public class Encryption {
    /** */
    private static final Logger logger = LogManager.getLogger(Encryption.class);

    /**
     * digest algorithm used.
     */
    public static final String DIGEST_ALGORITHM = "SHA-1";
    /**
     * asymetric algorithm.
     */
    private static final String PK_ALGORITHM = "RSA";

    static {
        try {
            // install BC, if not already done
            if (Security.getProvider("BC") == null) // TODO : get rid of bouncycastle...
            {
                Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
            }
        } catch (final Throwable t) {
            logger.error("Cannot initialize class Encryption", t);
        }
    }

    /**
     * returns the SHA-1 of the input.
     *
     * @param input byte array
     * @return digest value
     */
    public static byte[] getDigest(final byte[] input) {
        return getDigest(DIGEST_ALGORITHM, input);
    }

    /**
     * returns the digest of the input.
     *
     * @param algorithm e.g. "SHA-1"
     * @param input
     * @return digest value
     */
    public static byte[] getDigest(final String algorithm, final byte[] input) {
        try {
            final MessageDigest md = MessageDigest.getInstance(algorithm);
            md.update(input);
            return md.digest();

        } catch (final GeneralSecurityException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @return implementation of the SHA-1 message digest; reset() already
     * called
     */
    public static MessageDigest getMessagesDigest() {
        try {
            return MessageDigest.getInstance(DIGEST_ALGORITHM);
        } catch (final GeneralSecurityException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Calculate the digest but do not touch md.
     *
     * @param md the {@link MessageDigest} to be cloned
     * @return the digest, calculated with a clone of md
     */
    public static byte[] intermediateDigest(final MessageDigest md) {
        try {
            // Make a clone because #digest() will reset the MessageDigest
            // instance
            // and we want to be able to use this class for running digests on
            // circuits
            final MessageDigest mdClone = (MessageDigest) md.clone();
            return mdClone.digest();
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * checks row signature.
     *
     * @param signature  signature to check
     * @param signingKey public key from signing
     * @param data       byte array, signature is made over
     * @return true, if the signature is correct
     */

    public static boolean verifySignature(final byte[] signature, final PublicKey signingKey, final byte[] data) {
        return verifySignatureWithHash(signature, signingKey, getDigest(data));
    }

    /**
     * checks row signature.
     *
     * @param signature  signature to check
     * @param signingKey public key from signing
     * @param dataDigest byte array, the already calculated digest
     * @return true, if the signature is correct
     */

    public static boolean verifySignatureWithHash(final byte[] signature, final PublicKey signingKey,
            final byte[] dataDigest) {
        try {
            final Cipher cipher = Cipher.getInstance(PK_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, signingKey);
            byte[] decryptedDigest = cipher.doFinal(signature);

            if (decryptedDigest != null && dataDigest != null && decryptedDigest.length > dataDigest.length) {
                // try to fix bug in security calculation with OpenJDK-6 java
                // web start (ticket #59)
                logger.warn(
                        "verifySignature(): try to fix bug in security calculation with OpenJDK-6 java web start (ticket #59)");
                logger.warn("verifySignature(): original decryptedDigest=" + Encoding.toHexString(decryptedDigest));
                logger.warn("verifySignature(): dataDigest              =" + Encoding.toHexString(dataDigest));
                final byte[] fixedDecryptedDigest = new byte[dataDigest.length];
                System.arraycopy(decryptedDigest, decryptedDigest.length - dataDigest.length, fixedDecryptedDigest,
                        0, dataDigest.length);
                decryptedDigest = fixedDecryptedDigest;
            }

            final boolean verificationSuccessful = Arrays.equals(decryptedDigest, dataDigest);
            if (!verificationSuccessful) {
                logger.info("verifySignature(): decryptedDigest=" + Encoding.toHexString(decryptedDigest));
                logger.info("verifySignature(): dataDigest     =" + Encoding.toHexString(dataDigest));
            }

            return verificationSuccessful;

        } catch (final GeneralSecurityException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * sign some data using a private key and PKCS#1 v1.5 padding.
     *
     * @param data       the data to be signed
     * @param signingKey the key to sign the data
     * @return a signature
     */
    public static byte[] signData(final byte[] data, final PrivateKey signingKey) {
        try {
            final Cipher cipher = Cipher.getInstance(PK_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, signingKey);
            return cipher.doFinal(getDigest(DIGEST_ALGORITHM, data));
        } catch (final GeneralSecurityException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * makes RSA public key from PEM string.
     *
     * @param s PEM string that contains the key
     * @return
     * @see JCERSAPublicKey
     */
    public static RSAPublicKey extractPublicRSAKey(final String s) {
        RSAPublicKey theKey;
        try {
            theKey = new RSAKeyEncoder().parsePEMPublicKey(s);
        } catch (final Exception e) {
            logger.warn("Encryption.extractPublicRSAKey: Caught exception:" + e.getMessage(), e);
            theKey = null;
        }

        return theKey;
    }

    /**
     * makes RSA private key from PEM string.
     *
     * @param s PEM string that contains the key
     * @return
     * @see JCERSAPublicKey
     */
    public static RSAKeyPair extractRSAKeyPair(final String s) {
        RSAKeyPair rsaKeyPair;
        try {
            // parse
            final PEMParser parser = new PEMParser(new StringReader(s));
            final Object o = parser.readObject();
            parser.close();
            // check types
            if (!(o instanceof PEMKeyPair)) {
                throw new IOException("Encryption.extractRSAKeyPair: no private key found in string '" + s + "'");
            }
            final PEMKeyPair keyPair = (PEMKeyPair) o;
            if (keyPair.getPrivateKeyInfo() == null) {
                throw new IOException(
                        "Encryption.extractRSAKeyPair: no private key found in key pair of string '" + s + "'");
            }
            if (keyPair.getPublicKeyInfo() == null) {
                throw new IOException(
                        "Encryption.extractRSAKeyPair: no public key found in key pair of string '" + s + "'");
            }

            // convert keys and pack them together into a key pair
            final RSAPrivateCrtKey privateKey = new TempJCERSAPrivateCrtKey(keyPair.getPrivateKeyInfo());
            logger.debug("JCEPrivateKey={}", privateKey);
            final RSAPublicKey publicKey = new TempJCERSAPublicKey(keyPair.getPublicKeyInfo());
            rsaKeyPair = new RSAKeyPair(publicKey, privateKey);

        } catch (final Exception e) {
            logger.warn("Encryption.extractPrivateRSAKey: Caught exception:" + e.getMessage());
            rsaKeyPair = null;
        }

        return rsaKeyPair;
    }

    /**
     * Converts RSA private key to PEM string.
     *
     * @param rsaKeyPair
     * @return PEM string
     */
    public static String getPEMStringFromRSAKeyPair(final RSAKeyPair rsaKeyPair) {
        final StringWriter pemStrWriter = new StringWriter();
        final JcaPEMWriter pemWriter = new JcaPEMWriter(pemStrWriter);
        try {
            final KeyPair keyPair = new KeyPair(rsaKeyPair.getPublic(), rsaKeyPair.getPrivate());
            pemWriter.writeObject(keyPair.getPrivate());
            pemWriter.close();

        } catch (final IOException e) {
            logger.warn("Caught exception:" + e.getMessage());
            return "";
        }

        return pemStrWriter.toString();
    }

    /**
     * Create a key based on the parameters.
     *
     * @param modulus
     * @param publicExponent
     * @return the key
     */
    public static RSAPublicKey getRSAPublicKey(final BigInteger modulus, final BigInteger publicExponent) {
        try {
            return (RSAPublicKey) KeyFactory.getInstance("RSA")
                    .generatePublic(new RSAPublicKeySpec(modulus, publicExponent));
        } catch (final GeneralSecurityException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Create a key based on the parameters.
     *
     * @param modulus
     * @param privateExponent
     * @return the key
     */
    public static RSAPrivateKey getRSAPrivateKey(final BigInteger modulus, final BigInteger privateExponent) {
        try {
            return (RSAPrivateKey) KeyFactory.getInstance("RSA")
                    .generatePrivate(new RSAPrivateKeySpec(modulus, privateExponent));
        } catch (final GeneralSecurityException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * makes RSA public key from bin byte array.
     *
     * @param b byte array that contains the key
     * @return
     * @see JCERSAPublicKey
     */
    public static RSAPublicKey extractBinaryRSAKey(final byte[] b) {
        RSAPublicKey theKey;

        try {
            final ASN1InputStream ais = new ASN1InputStream(b);
            final Object asnObject = ais.readObject();
            final ASN1Sequence sequence = (ASN1Sequence) asnObject;
            final RSAPublicKeyStructure tempKey = new RSAPublicKeyStructure(sequence);
            theKey = getRSAPublicKey(tempKey.getModulus(), tempKey.getPublicExponent());
            ais.close();
        } catch (final IOException e) {
            logger.warn("Caught exception:" + e.getMessage());
            theKey = null;
        }

        return theKey;
    }

    /**
     * converts a RSAPublicKey into PKCS1-encoding (ASN.1).
     *
     * @param pubKeyStruct
     * @return PKCS1-encoded RSA PUBLIC KEY
     * @see JCERSAPublicKey
     */
    public static byte[] getPKCS1EncodingFromRSAPublicKey(final RSAPublicKey pubKeyStruct) {
        try {
            final RSAPublicKeyStructure myKey = new RSAPublicKeyStructure(pubKeyStruct.getModulus(),
                    pubKeyStruct.getPublicExponent());
            final ByteArrayOutputStream bOut = new ByteArrayOutputStream();
            final ASN1OutputStream aOut = new ASN1OutputStream(bOut);
            aOut.writeObject(myKey.toASN1Object());
            aOut.close();
            return bOut.toByteArray();
        } catch (final Exception e) {
            return null;
        }
    }

    /**
     * converts a JCERSAPublicKey into PEM/PKCS1-encoding.
     *
     * @param rsaPublicKey
     * @return PEM-encoded RSA PUBLIC KEY
     */
    public static String getPEMStringFromRSAPublicKey(final RSAPublicKey rsaPublicKey) {

        // mrk: this was awful to program. Remeber: There are two entirely
        // different
        // standard formats for rsa public keys. Bouncy castle does only support
        // the
        // one we can't use for TOR directories.

        final StringBuffer tmpDirSigningKey = new StringBuffer();

        try {

            tmpDirSigningKey.append("-----BEGIN RSA PUBLIC KEY-----\n");

            final byte[] base64Encoding = Base64.encode(getPKCS1EncodingFromRSAPublicKey(rsaPublicKey));
            for (int i = 0; i < base64Encoding.length; i++) {
                tmpDirSigningKey.append((char) base64Encoding[i]);
                if (((i + 1) % 64) == 0) {
                    tmpDirSigningKey.append("\n");
                }
            }
            tmpDirSigningKey.append("\n");

            tmpDirSigningKey.append("-----END RSA PUBLIC KEY-----\n");
        } catch (final Exception e) {
            return null;
        }

        return tmpDirSigningKey.toString();
    }

    /**
     * encrypt data with asymmetric key. create asymmetrical encrypted data:<br>
     * <ul>
     * <li>OAEP padding [42 bytes] (RSA-encrypted)</li>
     * <li>Symmetric key [16 bytes] FIXME: we assume that we ALWAYS need this</li>
     * <li>First part of data [70 bytes]</li>
     * <li>Second part of data [x-70 bytes] (Symmetrically encrypted)</li>
     * </ul>
     * encrypt and store in result
     *
     * @param pub
     * @param symmetricKey AES key
     * @param data         to be encrypted, needs currently to be at least 70 bytes long
     * @return the first half of the key exchange, ready to be send to the other
     * partner
     */
    public static byte[] asymEncrypt(final RSAPublicKey pub, final byte[] symmetricKey, final byte[] data)
            throws TorException {
        if (data == null) {
            throw new NullPointerException("can't encrypt NULL data");
        }

        HybridEncryption hybridEncryption = new HybridEncryption();

        return hybridEncryption.encrypt(data, pub, symmetricKey);
    }

    /**
     * decrypt data with asymmetric key. create asymmetrically encrypted data:<br>
     * <ul>
     * <li>OAEP padding [42 bytes] (RSA-encrypted)</li>
     * <li>Symmetric key [16 bytes]</li>
     * <li>First part of data [70 bytes]</li>
     * <li>Second part of data [x-70 bytes] (Symmetrically encrypted)</li>
     * </ul>
     * encrypt and store in result
     *
     * @param priv key to use for decryption
     * @param data to be decrypted, needs currently to be at least 70 bytes long
     * @return raw data
     */
    public static byte[] asymDecrypt(final RSAPrivateKey priv, final byte[] data) throws TorException {

        if (data == null) {
            throw new NullPointerException("can't encrypt NULL data");
        }
        if (data.length < 70) {
            throw new TorException("input array too short");
        }

        try {
            int encryptedBytes = 0;

            // init OAEP
            final OAEPEncoding oaep = new OAEPEncoding(new RSAEngine());
            oaep.init(false, new RSAKeyParameters(true, priv.getModulus(), priv.getPrivateExponent()));
            // apply RSA+OAEP
            encryptedBytes = oaep.getInputBlockSize();
            final byte[] oaepInput = new byte[encryptedBytes];
            System.arraycopy(data, 0, oaepInput, 0, encryptedBytes);
            final byte[] part1 = oaep.decodeBlock(oaepInput, 0, encryptedBytes);

            // extract symmetric key
            final byte[] symmetricKey = new byte[16];
            System.arraycopy(part1, 0, symmetricKey, 0, 16);
            // init AES
            final AESCounterMode aes = new AESCounterMode(symmetricKey);
            // apply AES
            final byte[] aesInput = new byte[data.length - encryptedBytes];
            System.arraycopy(data, encryptedBytes, aesInput, 0, aesInput.length);
            final byte[] part2 = aes.processStream(aesInput);

            // replace unencrypted data
            final byte[] result = new byte[part1.length - 16 + part2.length];
            System.arraycopy(part1, 16, result, 0, part1.length - 16);
            System.arraycopy(part2, 0, result, part1.length - 16, part2.length);

            return result;

        } catch (final InvalidCipherTextException e) {
            logger.error("Encryption.asymDecrypt(): can't decrypt cipher text:" + e.getMessage());
            throw new TorException("Encryption.asymDecrypt(): InvalidCipherTextException:" + e.getMessage());
        }
    }

    private static final int KEY_STRENGTH = 1024;
    private static final int KEY_CERTAINTY = 80; // use 112 for strength=2048

    /**
     * Create a fresh RSA key pair.
     *
     * @return a new RSAKeyPair
     */
    public static RSAKeyPair createNewRSAKeyPair() {
        try {
            // Generate a 1024-bit RSA key pair
            final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
            keyGen.initialize(KEY_STRENGTH);
            final KeyPair keypair = keyGen.genKeyPair();
            final RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey) keypair.getPrivate();
            final RSAPublicKey publicKey = (RSAPublicKey) keypair.getPublic();

            return new RSAKeyPair(publicKey, privateKey);

        } catch (final NoSuchAlgorithmException e) {
            logger.error("Could not create new key pair", e);
            throw new RuntimeException(e);
        }
    }
}