xyz.jamescarroll.genipass.Crypto.ECKey.java Source code

Java tutorial

Introduction

Here is the source code for xyz.jamescarroll.genipass.Crypto.ECKey.java

Source

/*
 * Copyright (c) 2016
 *
 * This file, ECKey.java, is apart of GeniPass.
 *
 * GeniPass 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.
 *
 * GeniPass 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 GeniPass.  If not, see http://www.gnu.org/licenses/.
 */

package xyz.jamescarroll.genipass.Crypto;

import android.content.Context;
import android.util.Log;
import android.util.TimingLogger;

import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.digests.RIPEMD160Digest;
import org.bouncycastle.crypto.generators.SCrypt;
import org.bouncycastle.jcajce.provider.digest.Blake2b;
import org.bouncycastle.jcajce.provider.digest.SHA3;

import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import xyz.jamescarroll.genipass.Async.AsyncKeyGenTimeTest;

/**
 * Created by James Carroll on 1/19/16.
 *
 * A Class to define an elliptic curve extended key. It holds the normal public
 * key and the public chain code. This also has method are child key derivation
 * and testing. For more information on this crypto system, please see
 * README.md.
 */

public class ECKey extends CryptoUtil {
    private static final String TAG = "ECKey.TAG";
    private static final X9ECParameters kCurve = SECNamedCurves.getByName("secp256k1");

    private byte[] mKey;
    private byte[] mChain;
    private boolean mMaster = false;

    private ECKey(byte[] mKey, byte[] mChain) {
        this.mKey = mKey;
        this.mChain = mChain;
    }

    /**
     * A public method to derive a child key from an ECKey object. The string
     * should be either the service or tag depending on if its the 2nd or 3rd
     * generation key respectively.
     *
     * @param s Either the service or tag.
     * @return An extended public key object.
     */

    public ECKey generateChildKey(String s) {
        return calcExtPublicKey(calcChildExtPrivate(getmChain(), s));
    }

    public byte[] getmKey() {
        return mKey;
    }

    public byte[] getmChain() {
        return mChain;
    }

    public boolean ismMaster() {
        return mMaster;
    }

    private ECKey setmMaster(boolean mMaster) {
        this.mMaster = mMaster;

        return this;
    }

    /**
     * Preforms the RIPEMD-160 hash of a byte array and returns the hash as a
     * {@link BigInteger}.
     *
     * @param in input byte array to be hashed
     * @param out Preallocated byte array to be used as the output multiple
     *            times.
     * @param ripemd Preallocated {@link RIPEMD160Digest} object to be used
     *               multiple times.
     * @return a BigInteger holding the value of the hash.
     */

    private static BigInteger ripemd160ToBigInteger(byte[] in, byte[] out, RIPEMD160Digest ripemd) {
        BigInteger h;

        ripemd.update(in, 0, in.length);
        ripemd.doFinal(out, 0);
        h = new BigInteger(out);
        zeroByteArray(out);

        return h;
    }

    /**
     * Calculates a child extended private key using HMAC-SHA2-512 using a
     * parent chain code.
     *
     * @param chain parent chain code used as secret key in HMAC.
     * @param s a string to be used as data to HMAC, this is either the service
     *          or the tag.
     * @return a byte array representing the child extended private key.
     */

    private static byte[] calcChildExtPrivate(byte[] chain, String s) {
        final String hmacSHA512 = "HmacSHA512";
        Mac hmac = null;
        SecretKeySpec secret;

        try {
            hmac = Mac.getInstance(hmacSHA512);
        } catch (NoSuchAlgorithmException e) {
            Log.e(TAG, "generateChildKey: ", e);
        }

        if (hmac != null) {
            secret = new SecretKeySpec(chain, hmacSHA512);

            try {
                hmac.init(secret);
                return hmac.doFinal(s.getBytes());
            } catch (InvalidKeyException e) {
                Log.e(TAG, "generateChildKey: ", e);
            }
        }

        return null;
    }

    /**
     * Calculates a normal public key using the secp256k1 curve.
     *
     * @param bytes normal private key
     * @return the x coordinate point
     */

    private static byte[] calcPublicKey(byte[] bytes) {
        return kCurve.getG().multiply(new BigInteger(bytes)).getEncoded(true);
    }

    /**
     * Calculates the extended public key using calcPublicKey() for the normal
     * public and 256 iterations of SHA3-256, hashing twice per iteration.
     * It also appends the iteration number to the end before the first hash.
     *
     * @param digest the private key
     * @return an ECKey object representing the extended public key.
     */

    private static ECKey calcExtPublicKey(byte[] digest) {
        SHA3.Digest256 sha = new SHA3.Digest256();
        byte[] k = Arrays.copyOfRange(digest, 0, 32);
        byte[] c = new byte[33];

        System.arraycopy(digest, 32, c, 0, 32);

        for (int i = 0; i < 256; i++) {
            c[32] = (byte) i;
            System.arraycopy(sha.digest(sha.digest(c)), 0, c, 0, 32);
        }

        return new ECKey(calcPublicKey(k), Arrays.copyOfRange(c, 0, 32));
    }

    /**
     * Calculates the master extended private key using SCrypt.
     *
     * @param u RIPEMD-160 hash of the username.
     * @param p RIPEMD-160 hash of the master password.
     * @return the SCrypt digest
     */

    private static byte[] calcMasterExtPrivateKey(BigInteger u, BigInteger p) {
        return SCrypt.generate((u.toString() + p.toString()).getBytes(), (u.xor(p)).toByteArray(),
                (int) Math.pow(2, 16), 8, 2, 64);
    }

    /**
     * Public method that generates the master extended public key.
     *
     * @param u the string of the username
     * @param p the string of the master password.
     * @return an ECKey object representing the master extended public key.
     */

    public static ECKey genFromSeeds(String u, String p) {
        RIPEMD160Digest ripemd = new RIPEMD160Digest();
        byte[] digest = new byte[ripemd.getDigestSize()];
        BigInteger hu, hp;

        hu = ripemd160ToBigInteger(u.getBytes(), digest, ripemd);
        hp = ripemd160ToBigInteger(p.getBytes(), digest, ripemd);
        ripemd.reset();

        digest = calcMasterExtPrivateKey(hu, hp);

        return calcExtPublicKey(digest).setmMaster(true);
    }

    public static byte[] genLoginText(String u, String p) {
        RIPEMD160Digest ripemd = new RIPEMD160Digest();
        Blake2b.Blake2b256 blake = new Blake2b.Blake2b256();
        byte[] digest = new byte[ripemd.getDigestSize()];
        byte[] b = (u + p).getBytes();

        ripemd.update(b, 0, b.length);
        ripemd.doFinal(digest, 0);

        for (int i = 0; i < 256; i++) {
            digest = blake.digest(blake.digest(digest));
        }

        return digest;
    }

    /**
     * Inner static class for getting a rough estimate of how long crypto
     * operations take on different devices. This inner class isn't actually
     * in the production app, it's only used for debugging.
     */

    public static class ECTimeTest {

        /**
         * Public method to begin the time test. This method is meant to be
         * called inside a loop, see {@link AsyncKeyGenTimeTest}.
         * @param tl Already initialized {@link TimingLogger} to be used to
         *           keep track of time.
         * @param context a {@link Context} needed to load the word list to
         *                generate the passwords.
         * @param iter the iteration number of the test
         */

        public static void timeTest(TimingLogger tl, Context context, int iter) {
            RIPEMD160Digest ripemd = new RIPEMD160Digest();
            byte[] digest = new byte[ripemd.getDigestSize()];
            BigInteger hu, hp;
            String u = Password.pickRandomPassword(context, true);
            String p = Password.pickRandomPassword(context, true);

            tl.addSplit("--- Begin iteration: " + iter + " ---");

            hu = ripemd160ToBigInteger(u.getBytes(), digest, ripemd);
            hp = ripemd160ToBigInteger(p.getBytes(), digest, ripemd);
            ripemd.reset();

            tl.addSplit("RIPEMD 160");
            testSCrypt(2, tl, hu, hp);
        }

        private static void testSCrypt(int p, TimingLogger tl, BigInteger hu, BigInteger hp) {
            byte[] digest = SCrypt.generate((hu.toString() + hp.toString()).getBytes(), (hu.xor(hp)).toByteArray(),
                    (int) Math.pow(2, 16), 8, p, 64);
            tl.addSplit("SCrypt: 2^16, 8, " + p);

            byte[] k = Arrays.copyOfRange(digest, 0, 32);
            byte[] c = Arrays.copyOfRange(digest, 32, 64);
            byte[] kc = k.clone();
            byte[] cc = c.clone();

            tl.addSplit("Split array");

            splitDigestBlake(k, c);
            tl.addSplit("split digest - blake");

            splitDigestSha3(kc, cc);
            tl.addSplit("split digest - sha3");
        }

        private static ECKey splitDigestNoBlake(byte[] k, byte[] c) {
            return new ECKey(calcPublicKey(k), calcPublicKey(c));
        }

        private static ECKey splitDigestBlake(byte[] k, byte[] c) {
            Blake2b.Blake2b256 blake = new Blake2b.Blake2b256();

            for (int i = 0; i < 256; i++) {
                c = blake.digest(blake.digest(c));
            }

            return new ECKey(calcPublicKey(k), c);
        }

        private static ECKey splitDigestSha3(byte[] k, byte[] c) {
            SHA3.Digest256 sha = new SHA3.Digest256();

            for (int i = 0; i < 256; i++) {
                c = sha.digest(sha.digest(c));
            }

            return new ECKey(calcPublicKey(k), c);
        }
    }

    /**
     * Inner static class used to call static methods from ECKey one at a time
     * so that they can be check against a set of hardcoded test vectors, see
     * {@link TestManager}. This can be accessed in-app via Settings > Test
     * Vectors. See the above static method in ECKey for comments.
     */

    public static class UnitTest {

        public static String ripemd160Hex(String s) {
            RIPEMD160Digest ripe = new RIPEMD160Digest();
            byte[] in = s.getBytes();
            byte[] out = new byte[ripe.getDigestSize()];

            ripe.update(in, 0, in.length);
            ripe.doFinal(out, 0);

            return bytesToHex(out);
        }

        public static String genLoginHex(String u, String p) {
            return bytesToHex(genLoginText(u, p));
        }

        public static String genMasterExtPrivateKey(String u, String p) {
            BigInteger bu = new BigInteger(u, 16);
            BigInteger bp = new BigInteger(p, 16);

            return bytesToHex(calcMasterExtPrivateKey(bu, bp));
        }

        public static String genChildExtPrivatecKey(String par, String s) {
            byte[] p = new BigInteger(par, 16).toByteArray();
            byte[] cp = Arrays.copyOfRange(p, 32, 64);

            return bytesToHex(calcChildExtPrivate(cp, s));
        }

        public static String genExtPublicKey(String pri) {
            byte[] p = new BigInteger(pri, 16).toByteArray();
            ECKey k = calcExtPublicKey(p);

            return bytesToHex(k.getmKey()) + bytesToHex(k.getmChain());
        }
    }
}