tor.TorCrypto.java Source code

Java tutorial

Introduction

Here is the source code for tor.TorCrypto.java

Source

/*
    Tor Research Framework - easy to use tor client library/framework
    Copyright (C) 2014  Dr Gareth Owen <drgowen@gmail.com>
    www.ghowen.me / github.com/drgowen/tor-research-framework
    
    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 3 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 tor;

import org.apache.commons.lang.ArrayUtils;
import org.bouncycastle.util.Arrays;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.*;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAPublicKeySpec;

public class TorCrypto {
    public static SecureRandom rnd = new SecureRandom();
    public final static int KEY_LEN = 16;
    public final static int DH_LEN = 128;
    public final static int DH_SEC_LEN = 40;
    public final static int PK_ENC_LEN = 128;
    public final static int PK_PAD_LEN = 42;
    public final static int HASH_LEN = 20;
    public static BigInteger DH_G = new BigInteger("2");
    public static BigInteger DH_P = new BigInteger(
            "179769313486231590770839156793787453197860296048756011706444423684197180216158519368947833795864925541502180565485980503646440548199239100050792877003355816639229553136239076508735759914822574862575007425302077447712589550957937778424442426617334727629299387668709205606050270810842907692932019128194467627007");

    public TorCrypto() throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException {

    }

    /**
     * BigInteger to byte array, stripping leading zero if applicable (e.g. unsigned data only)
     *
     * @param in BigInteger
     * @return
     */
    public static byte[] BNtoByte(BigInteger in) {
        byte[] intmp = in.toByteArray();
        if (intmp[0] != 0)
            return intmp;

        byte intmp2[] = new byte[intmp.length - 1];
        System.arraycopy(intmp, 1, intmp2, 0, intmp2.length);
        return intmp2;
    }

    // add zero sign byte so always positive

    /**
     * Converts byte array to BigInteger, adding zero sign byte to make unsigned.
     *
     * @param in BigInteger
     * @return
     */
    public static BigInteger byteToBN(byte in[]) {
        byte tmp[] = new byte[in.length + 1];
        tmp[0] = 0;
        System.arraycopy(in, 0, tmp, 1, in.length);
        return new BigInteger(tmp);
    }

    public static MessageDigest getSHA1() {
        MessageDigest md;
        try {
            md = MessageDigest.getInstance("SHA-1");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        return md;
    }

    /**
     * Tor Key derivation function
     *
     * @param secret A secret
     * @param length Length of key data to generate
     * @return Key data
     */
    public static byte[] torKDF(byte[] secret, int length) {
        byte data[] = new byte[(int) Math.ceil(length / (double) HASH_LEN) * HASH_LEN];
        byte hashdata[] = new byte[secret.length + 1];

        assert secret.length == DH_LEN; // checks if secret is length of diffie-hellman - might not be applicable in some cases

        //System.out.println("sec len " + secret.length);

        System.arraycopy(secret, 0, hashdata, 0, secret.length);

        for (int i = 0; i < data.length / HASH_LEN; i++) {
            hashdata[secret.length] = (byte) i;
            MessageDigest md;
            try {
                md = MessageDigest.getInstance("SHA-1");
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            }
            System.arraycopy(md.digest(hashdata), 0, data, i * HASH_LEN, HASH_LEN);
        }
        return data;
    }

    /**
     * Tor Hybrid Encrypt function
     *
     * @param in Data to encrypt
     * @param pk Onion Router public key to encrypt to
     * @return Encrypted data
     */
    public static byte[] hybridEncrypt(byte[] in, PublicKey pk) {
        try {
            Cipher rsa = Cipher.getInstance("RSA/None/OAEPWithSHA1AndMGF1Padding", "BC");
            rsa.init(Cipher.ENCRYPT_MODE, pk);
            if (in.length < PK_ENC_LEN - PK_PAD_LEN) {
                return rsa.doFinal(in);
            } else {
                // prep key and IV
                byte[] key = new byte[KEY_LEN];
                rnd.nextBytes(key);
                byte[] iv = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
                SecretKeySpec keysp = new SecretKeySpec(key, "AES");
                IvParameterSpec ivSpec = new IvParameterSpec(iv);

                // prepare m1
                byte m1a[] = Arrays.copyOfRange(in, 0, PK_ENC_LEN - PK_PAD_LEN - KEY_LEN);
                byte m1[] = ArrayUtils.addAll(key, m1a);
                byte rsaciphertext[] = rsa.doFinal(m1);

                // prepare m2
                byte m2[] = Arrays.copyOfRange(in, m1a.length, in.length);
                Cipher aes = Cipher.getInstance("AES/CTR/NoPadding");
                aes.init(Cipher.ENCRYPT_MODE, keysp, ivSpec);
                byte aesciphertext[] = aes.doFinal(m2);

                // merge
                return ArrayUtils.addAll(rsaciphertext, aesciphertext);
            }
        } catch (BadPaddingException | NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException
                | InvalidKeyException | IllegalBlockSizeException | InvalidAlgorithmParameterException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Parses a public key encoded as ASN.1
     *
     * @param rsapublickey ASN.1 Encoded public key
     * @return PublicKey
     */
    public static PublicKey asn1GetPublicKey(byte[] rsapublickey) {
        int blobsize = rsapublickey.length;
        DataInputStream dis = null;
        int jint = 0; // int to represent unsigned byte or unsigned short
        int datacount = 0;

        try {
            // --- Try to read the ANS.1 encoded RSAPublicKey blob -------------
            ByteArrayInputStream bis = new ByteArrayInputStream(rsapublickey);
            dis = new DataInputStream(bis);

            if (dis.readByte() != 0x30) // asn.1 encoded starts with 0x30
                return null;

            jint = dis.readUnsignedByte(); // asn.1 is 0x80 plus number of bytes
            // representing data count
            if (jint == 0x81)
                datacount = dis.readUnsignedByte(); // datalength is specified
            // in next byte.
            else if (jint == 0x82) // bytes count for any supported keysize
                // would be at most 2 bytes
                datacount = dis.readUnsignedShort(); // datalength is specified
            // in next 2 bytes
            else
                return null; // all supported publickey byte-sizes can be
            // specified in at most 2 bytes

            if ((jint - 0x80 + 2 + datacount) != blobsize) // sanity check for
                // correct number of
                // remaining bytes
                return null;

            //      System.out
            //         .println("\nRead outer sequence bytes; validated outer asn.1 consistency ");

            // ------- Next attempt to read Integer sequence for modulus ------
            if (dis.readUnsignedByte() != 0x02) // next byte read must be
                // Integer asn.1 specifier
                return null;
            jint = dis.readUnsignedByte(); // asn.1 is 0x80 plus number of bytes
            // representing data count
            if (jint == 0x81)
                datacount = dis.readUnsignedByte(); // datalength is specified
            // in next byte.
            else if (jint == 0x82) // bytes count for any supported keysize
                // would be at most 2 bytes
                datacount = dis.readUnsignedShort(); // datalength is specified
            // in next 2 bytes
            else
                return null; // all supported publickey modulus byte-sizes can
            // be specified in at most 2 bytes

            // ---- next bytes are big-endian ordered modulus -----
            byte[] modulus = new byte[datacount];
            int modbytes = dis.read(modulus);
            if (modbytes != datacount) // if we can read enought modulus bytes
                // ...
                return null;

            //System.out.println("Read modulus");

            // ------- Next attempt to read Integer sequence for public exponent
            // ------
            if (dis.readUnsignedByte() != 0x02) // next byte read must be
                // Integer asn.1 specifier
                return null;
            datacount = dis.readUnsignedByte(); // size of modulus is specified
            // in one byte
            byte[] exponent = new byte[datacount];
            int expbytes = dis.read(exponent);
            if (expbytes != datacount)
                return null;
            //System.out.println("Read exponent");

            // ----- Finally, create the PublicKey object from modulus and
            // public exponent --------
            RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger(1, modulus),
                    new BigInteger(1, exponent));
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey pubKey = keyFactory.generatePublic(pubKeySpec);
            return pubKey;
        } catch (Exception exc) {
            return null;
        } finally {
            try {
                dis.close();
            } catch (Exception exc) {
                /* ignore */
                ;
            }
        }
    }

    public static byte[] publicKeyToASN1(RSAPublicKey pk) throws IOException {
        byte[] modulus = pk.getModulus().toByteArray();
        byte[] pubexp = pk.getPublicExponent().toByteArray();

        ByteBuffer inner = ByteBuffer.allocate(1024);
        inner.put((byte) 2); // Integer
        inner.put((byte) 0x81); // one byte size
        inner.put((byte) modulus.length); // one byte size
        inner.put(modulus);

        inner.put((byte) 2); // Integer
        //inner.put((byte)0x81); // one byte size
        inner.put((byte) pubexp.length); // one byte size
        inner.put(pubexp);
        inner.flip();

        ByteBuffer outer = ByteBuffer.allocate(1024);
        outer.put((byte) 0x30); // SEQUENCE
        outer.put((byte) 0x81); // one byte size
        outer.put((byte) inner.limit()); // one byte size
        outer.put(inner);
        outer.flip();

        byte asn[] = new byte[outer.limit()];
        outer.get(asn);
        return asn;
    }

    //    public static byte [] publicKeyToPKCS1(RSAPublicKey pk) throws IOException {
    //        byte pkbits[] = publicKeyToASN1(pk);
    //
    //        ByteBuffer buf = ByteBuffer.allocate(1024);
    //        buf.put(new byte[] {0x30, 0x0d, //<- a SEQUENCE with 0d bytes
    //                0x06, 0x09, 0x2a, (byte)0x86, 0x48, (byte)0x86, (byte)0xf7, 0x0d, 0x01, 0x01, 0x01,  //<- OID 1.2.840.113549.1.1.1
    //                0x05, 0x00}); // NULL
    //        buf.put(new byte[] {0x03, (byte)0x81, (byte)pkbits.length, 0x00} ); // bit string
    //        buf.put(pkbits); // public key bits as a sequence
    //
    //        buf.flip();
    //
    //        //overall sequence header
    //        ByteBuffer outer = ByteBuffer.allocate(256);
    //        outer.put((byte)0x30);
    //        outer.put((byte)0x81);
    //        outer.put((byte)buf.limit());
    //        outer.put(buf);
    //        outer.flip();
    //
    //        byte out[] = new byte[outer.limit()];
    //        outer.get(out);
    //        return out;
    //
    //
    //
    //    }
}