org.bouncycastle.crypto.engines.ElGamalEngine.java Source code

Java tutorial

Introduction

Here is the source code for org.bouncycastle.crypto.engines.ElGamalEngine.java

Source

package org.bouncycastle.crypto.engines;

import java.math.BigInteger;
import java.security.SecureRandom;

import org.bouncycastle.crypto.AsymmetricBlockCipher;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.params.ElGamalKeyParameters;
import org.bouncycastle.crypto.params.ElGamalPrivateKeyParameters;
import org.bouncycastle.crypto.params.ElGamalPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.util.BigIntegers;

/**
 * this does your basic ElGamal algorithm.
 */
public class ElGamalEngine implements AsymmetricBlockCipher {
    private ElGamalKeyParameters key;
    private SecureRandom random;
    private boolean forEncryption;
    private int bitSize;

    private static final BigInteger ZERO = BigInteger.valueOf(0);
    private static final BigInteger ONE = BigInteger.valueOf(1);
    private static final BigInteger TWO = BigInteger.valueOf(2);

    /**
     * initialise the ElGamal engine.
     *
     * @param forEncryption true if we are encrypting, false otherwise.
     * @param param the necessary ElGamal key parameters.
     */
    public void init(boolean forEncryption, CipherParameters param) {
        if (param instanceof ParametersWithRandom) {
            ParametersWithRandom p = (ParametersWithRandom) param;

            this.key = (ElGamalKeyParameters) p.getParameters();
            this.random = p.getRandom();
        } else {
            this.key = (ElGamalKeyParameters) param;
            this.random = CryptoServicesRegistrar.getSecureRandom();
        }

        this.forEncryption = forEncryption;

        BigInteger p = key.getParameters().getP();

        bitSize = p.bitLength();

        if (forEncryption) {
            if (!(key instanceof ElGamalPublicKeyParameters)) {
                throw new IllegalArgumentException("ElGamalPublicKeyParameters are required for encryption.");
            }
        } else {
            if (!(key instanceof ElGamalPrivateKeyParameters)) {
                throw new IllegalArgumentException("ElGamalPrivateKeyParameters are required for decryption.");
            }
        }
    }

    /**
     * Return the maximum size for an input block to this engine.
     * For ElGamal this is always one byte less than the size of P on
     * encryption, and twice the length as the size of P on decryption.
     *
     * @return maximum size for an input block.
     */
    public int getInputBlockSize() {
        if (forEncryption) {
            return (bitSize - 1) / 8;
        }

        return 2 * ((bitSize + 7) / 8);
    }

    /**
     * Return the maximum size for an output block to this engine.
     * For ElGamal this is always one byte less than the size of P on
     * decryption, and twice the length as the size of P on encryption.
     *
     * @return maximum size for an output block.
     */
    public int getOutputBlockSize() {
        if (forEncryption) {
            return 2 * ((bitSize + 7) / 8);
        }

        return (bitSize - 1) / 8;
    }

    /**
     * Process a single block using the basic ElGamal algorithm.
     *
     * @param in the input array.
     * @param inOff the offset into the input buffer where the data starts.
     * @param inLen the length of the data to be processed.
     * @return the result of the ElGamal process.
     * @exception DataLengthException the input block is too large.
     */
    public byte[] processBlock(byte[] in, int inOff, int inLen) {
        if (key == null) {
            throw new IllegalStateException("ElGamal engine not initialised");
        }

        int maxLength = forEncryption ? (bitSize - 1 + 7) / 8 : getInputBlockSize();

        if (inLen > maxLength) {
            throw new DataLengthException("input too large for ElGamal cipher.\n");
        }

        BigInteger p = key.getParameters().getP();

        if (key instanceof ElGamalPrivateKeyParameters) // decryption
        {
            byte[] in1 = new byte[inLen / 2];
            byte[] in2 = new byte[inLen / 2];

            System.arraycopy(in, inOff, in1, 0, in1.length);
            System.arraycopy(in, inOff + in1.length, in2, 0, in2.length);

            BigInteger gamma = new BigInteger(1, in1);
            BigInteger phi = new BigInteger(1, in2);

            ElGamalPrivateKeyParameters priv = (ElGamalPrivateKeyParameters) key;
            // a shortcut, which generally relies on p being prime amongst other things.
            // if a problem with this shows up, check the p and g values!
            BigInteger m = gamma.modPow(p.subtract(ONE).subtract(priv.getX()), p).multiply(phi).mod(p);

            return BigIntegers.asUnsignedByteArray(m);
        } else // encryption
        {
            byte[] block;
            if (inOff != 0 || inLen != in.length) {
                block = new byte[inLen];

                System.arraycopy(in, inOff, block, 0, inLen);
            } else {
                block = in;
            }

            BigInteger input = new BigInteger(1, block);

            if (input.compareTo(p) >= 0) {
                throw new DataLengthException("input too large for ElGamal cipher.\n");
            }

            ElGamalPublicKeyParameters pub = (ElGamalPublicKeyParameters) key;

            int pBitLength = p.bitLength();
            BigInteger k = BigIntegers.createRandomBigInteger(pBitLength, random);

            while (k.equals(ZERO) || (k.compareTo(p.subtract(TWO)) > 0)) {
                k = BigIntegers.createRandomBigInteger(pBitLength, random);
            }

            BigInteger g = key.getParameters().getG();
            BigInteger gamma = g.modPow(k, p);
            BigInteger phi = input.multiply(pub.getY().modPow(k, p)).mod(p);

            byte[] out1 = gamma.toByteArray();
            byte[] out2 = phi.toByteArray();
            byte[] output = new byte[this.getOutputBlockSize()];

            if (out1.length > output.length / 2) {
                System.arraycopy(out1, 1, output, output.length / 2 - (out1.length - 1), out1.length - 1);
            } else {
                System.arraycopy(out1, 0, output, output.length / 2 - out1.length, out1.length);
            }

            if (out2.length > output.length / 2) {
                System.arraycopy(out2, 1, output, output.length - (out2.length - 1), out2.length - 1);
            } else {
                System.arraycopy(out2, 0, output, output.length - out2.length, out2.length);
            }

            return output;
        }
    }
}