All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bouncycastle.crypto.signers.ISO9796d2Signer Maven / Gradle / Ivy

Go to download

The Bouncy Castle Crypto package is a Java implementation of cryptographic algorithms. This jar contains JCE provider and lightweight API for the Bouncy Castle Cryptography APIs for Java 1.8 and later with debug enabled.

The newest version!
package org.bouncycastle.crypto.signers;

import org.bouncycastle.crypto.AsymmetricBlockCipher;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.SignerWithRecovery;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.util.Arrays;

/**
 * ISO9796-2 - mechanism using a hash function with recovery (scheme 1)
 */
public class ISO9796d2Signer
    implements SignerWithRecovery
{
    /** @deprecated use ISOTrailers */
    static final public int   TRAILER_IMPLICIT    = 0xBC;
    /** @deprecated use ISOTrailers */
    static final public int   TRAILER_RIPEMD160   = 0x31CC;
    /** @deprecated use ISOTrailers */
    static final public int   TRAILER_RIPEMD128   = 0x32CC;
    /** @deprecated use ISOTrailers */
    static final public int   TRAILER_SHA1        = 0x33CC;
    /** @deprecated use ISOTrailers */
    static final public int   TRAILER_SHA256      = 0x34CC;
    /** @deprecated use ISOTrailers */
    static final public int   TRAILER_SHA512      = 0x35CC;
    /** @deprecated use ISOTrailers */
    static final public int   TRAILER_SHA384      = 0x36CC;
    /** @deprecated use ISOTrailers */
    static final public int   TRAILER_WHIRLPOOL   = 0x37CC;

    private Digest                      digest;
    private AsymmetricBlockCipher       cipher;

    private int         trailer;
    private int         keyBits;
    private byte[]      block;
    private byte[]      mBuf;
    private int         messageLength;
    private boolean     fullMessage;
    private byte[]      recoveredMessage;

    private byte[]      preSig;
    private byte[]      preBlock;

    /**
     * Generate a signer with either implicit or explicit trailers for ISO9796-2.
     * 
     * @param cipher base cipher to use for signature creation/verification
     * @param digest digest to use.
     * @param implicit whether or not the trailer is implicit or gives the hash.
     */
    public ISO9796d2Signer(
        AsymmetricBlockCipher   cipher,
        Digest                  digest,
        boolean                 implicit)
    {
        this.cipher = cipher;
        this.digest = digest;

        if (implicit)
        {
            trailer = ISOTrailers.TRAILER_IMPLICIT;
        }
        else
        {
            Integer trailerObj = ISOTrailers.getTrailer(digest);

            if (trailerObj != null)
            {
                trailer = trailerObj.intValue();
            }
            else
            {
                throw new IllegalArgumentException("no valid trailer for digest: " + digest.getAlgorithmName());
            }
        }
    }

    /**
     * Constructor for a signer with an explicit digest trailer.
     * 
     * @param cipher cipher to use.
     * @param digest digest to sign with.
     */
    public ISO9796d2Signer(
        AsymmetricBlockCipher   cipher,
        Digest                  digest)
    {
        this(cipher, digest, false);
    }
    
    public void init(
        boolean                 forSigning,
        CipherParameters        param)
    {
        RSAKeyParameters  kParam = (RSAKeyParameters)param;

        cipher.init(forSigning, kParam);

        keyBits = kParam.getModulus().bitLength();

        block = new byte[(keyBits + 7) / 8];
        
        if (trailer == ISOTrailers.TRAILER_IMPLICIT)
        {
            mBuf = new byte[block.length - digest.getDigestSize() - 2];
        }
        else
        {
            mBuf = new byte[block.length - digest.getDigestSize() - 3];
        }

        reset();
    }

    /**
     * compare two byte arrays - constant time
     */
    private boolean isSameAs(
        byte[]    a,
        byte[]    b)
    {
        boolean isOkay = true;

        if (messageLength > mBuf.length)
        {
            if (mBuf.length > b.length)
            {
                isOkay = false;
            }
            
            for (int i = 0; i != mBuf.length; i++)
            {
                if (a[i] != b[i])
                {
                    isOkay = false;
                }
            }
        }
        else
        {
            if (messageLength != b.length)
            {
                isOkay = false;
            }
            
            for (int i = 0; i != b.length; i++)
            {
                if (a[i] != b[i])
                {
                    isOkay = false;
                }
            }
        }
        
        return isOkay;
    }
    
    /**
     * clear possible sensitive data
     */
    private void clearBlock(
        byte[]  block)
    {
        for (int i = 0; i != block.length; i++)
        {
            block[i] = 0;
        }
    }

    public void updateWithRecoveredMessage(byte[] signature)
        throws InvalidCipherTextException
    {
        byte[]      block = cipher.processBlock(signature, 0, signature.length);

        if (((block[0] & 0xC0) ^ 0x40) != 0)
        {
            throw new InvalidCipherTextException("malformed signature");
        }

        if (((block[block.length - 1] & 0xF) ^ 0xC) != 0)
        {
            throw new InvalidCipherTextException("malformed signature");
        }

        int     delta = 0;

        if (((block[block.length - 1] & 0xFF) ^ 0xBC) == 0)
        {
            delta = 1;
        }
        else
        {
            int sigTrail = ((block[block.length - 2] & 0xFF) << 8) | (block[block.length - 1] & 0xFF);
            Integer trailerObj = ISOTrailers.getTrailer(digest);

            if (trailerObj != null)
            {
                int trailer = trailerObj.intValue();
                if (sigTrail != trailer)
                {
                    if (!(trailer == ISOTrailers.TRAILER_SHA512_256 && sigTrail == 0x40CC))
                    {
                        throw new IllegalStateException("signer initialised with wrong digest for trailer " + sigTrail);
                    }
                }
            }
            else
            {
                throw new IllegalArgumentException("unrecognised hash in signature");
            }

            delta = 2;
        }

        //
        // find out how much padding we've got
        //
        int mStart = 0;

        for (mStart = 0; mStart != block.length; mStart++)
        {
            if (((block[mStart] & 0x0f) ^ 0x0a) == 0)
            {
                break;
            }
        }

        mStart++;

        int off = block.length - delta - digest.getDigestSize();

        //
        // there must be at least one byte of message string
        //
        if ((off - mStart) <= 0)
        {
            throw new InvalidCipherTextException("malformed block");
        }

        //
        // if we contain the whole message as well, check the hash of that.
        //
        if ((block[0] & 0x20) == 0)
        {
            fullMessage = true;

            recoveredMessage = new byte[off - mStart];
            System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length);
        }
        else
        {
            fullMessage = false;

            recoveredMessage = new byte[off - mStart];
            System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length);
        }

        preSig = signature;
        preBlock = block;

        digest.update(recoveredMessage, 0, recoveredMessage.length);
        messageLength = recoveredMessage.length;
        System.arraycopy(recoveredMessage, 0, mBuf, 0, recoveredMessage.length);
    }
    
    /**
     * update the internal digest with the byte b
     */
    public void update(
        byte    b)
    {
        digest.update(b);

        if (messageLength < mBuf.length)
        {
            mBuf[messageLength] = b;
        }

        messageLength++;
    }

    /**
     * update the internal digest with the byte array in
     */
    public void update(
        byte[]  in,
        int     off,
        int     len)
    {
        while (len > 0 && messageLength < mBuf.length)
        {
            this.update(in[off]);
            off++;
            len--;
        }

        digest.update(in, off, len);
        messageLength += len;
    }

    /**
     * reset the internal state
     */
    public void reset()
    {
        digest.reset();
        messageLength = 0;
        clearBlock(mBuf);
        
        if (recoveredMessage != null)
        {
            clearBlock(recoveredMessage);
        }
        
        recoveredMessage = null;
        fullMessage = false;

        if (preSig != null)
        {
            preSig = null;
            clearBlock(preBlock);
            preBlock = null;
        }
    }

    /**
     * generate a signature for the loaded message using the key we were
     * initialised with.
     */
    public byte[] generateSignature()
        throws CryptoException
    {
        int     digSize = digest.getDigestSize();

        int t = 0;
        int delta = 0;

        if (trailer == ISOTrailers.TRAILER_IMPLICIT)
        {
            t = 8;
            delta = block.length - digSize - 1;
            digest.doFinal(block, delta);
            block[block.length - 1] = (byte)ISOTrailers.TRAILER_IMPLICIT;
        }
        else
        {
            t = 16;
            delta = block.length - digSize - 2;
            digest.doFinal(block, delta);
            block[block.length - 2] = (byte)(trailer >>> 8);
            block[block.length - 1] = (byte)trailer;
        }

        byte    header = 0;
        int     x = (digSize + messageLength) * 8 + t + 4 - keyBits;

        if (x > 0)
        {
            int mR = messageLength - ((x + 7) / 8);
            header = 0x60;

            delta -= mR;
            
            System.arraycopy(mBuf, 0, block, delta, mR);

            recoveredMessage = new byte[mR];
        }
        else
        {
            header = 0x40;
            delta -= messageLength;
            
            System.arraycopy(mBuf, 0, block, delta, messageLength);

            recoveredMessage = new byte[messageLength];
        }
        
        if ((delta - 1) > 0)
        {
            for (int i = delta - 1; i != 0; i--)
            {
                block[i] = (byte)0xbb;
            }
            block[delta - 1] ^= (byte)0x01;
            block[0] = (byte)0x0b;
            block[0] |= header;
        }
        else
        {
            block[0] = (byte)0x0a;
            block[0] |= header;
        }

        byte[]  b = cipher.processBlock(block, 0, block.length);

        fullMessage = (header & 0x20) == 0;
        System.arraycopy(mBuf, 0, recoveredMessage, 0, recoveredMessage.length);

        messageLength = 0;
        
        clearBlock(mBuf);
        clearBlock(block);

        return b;
    }

    /**
     * return true if the signature represents a ISO9796-2 signature
     * for the passed in message.
     */
    public boolean verifySignature(
        byte[]      signature)
    {
        byte[]      block = null;

        if (preSig == null)
        {
            try
            {
                block = cipher.processBlock(signature, 0, signature.length);
            }
            catch (Exception e)
            {
                return false;
            }
        }
        else
        {
            if (!Arrays.areEqual(preSig, signature))
            {
                throw new IllegalStateException("updateWithRecoveredMessage called on different signature");
            }

            block = preBlock;

            preSig = null;
            preBlock = null;
        }

        if (((block[0] & 0xC0) ^ 0x40) != 0)
        {
            return returnFalse(block);
        }

        if (((block[block.length - 1] & 0xF) ^ 0xC) != 0)
        {
            return returnFalse(block);
        }

        int     delta = 0;

        if (((block[block.length - 1] & 0xFF) ^ 0xBC) == 0)
        {
            delta = 1;
        }
        else
        {
            int sigTrail = ((block[block.length - 2] & 0xFF) << 8) | (block[block.length - 1] & 0xFF);
            Integer trailerObj = ISOTrailers.getTrailer(digest);

            if (trailerObj != null)
            {
                int trailer = trailerObj.intValue();
                if (sigTrail != trailer)
                {
                    if (!(trailer == ISOTrailers.TRAILER_SHA512_256 && sigTrail == 0x40CC))
                    {
                        throw new IllegalStateException("signer initialised with wrong digest for trailer " + sigTrail);
                    }
                }
            }
            else
            {
                throw new IllegalArgumentException("unrecognised hash in signature");
            }

            delta = 2;
        }

        //
        // find out how much padding we've got
        //
        int mStart = 0;

        for (mStart = 0; mStart != block.length; mStart++)
        {
            if (((block[mStart] & 0x0f) ^ 0x0a) == 0)
            {
                break;
            }
        }

        mStart++;

        //
        // check the hashes
        //
        byte[]  hash = new byte[digest.getDigestSize()];

        int off = block.length - delta - hash.length;

        //
        // there must be at least one byte of message string
        //
        if ((off - mStart) <= 0)
        {
            return returnFalse(block);
        }

        //
        // if we contain the whole message as well, check the hash of that.
        //
        if ((block[0] & 0x20) == 0)
        {
            fullMessage = true;

            // check right number of bytes passed in.
            if (messageLength > off - mStart)
            {
                return returnFalse(block);
            }
            
            digest.reset();
            digest.update(block, mStart, off - mStart);
            digest.doFinal(hash, 0);

            boolean isOkay = true;

            for (int i = 0; i != hash.length; i++)
            {
                block[off + i] ^= hash[i];
                if (block[off + i] != 0)
                {
                    isOkay = false;
                }
            }

            if (!isOkay)
            {
                return returnFalse(block);
            }

            recoveredMessage = new byte[off - mStart];
            System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length);
        }
        else
        {
            fullMessage = false;
            
            digest.doFinal(hash, 0);

            boolean isOkay = true;

            for (int i = 0; i != hash.length; i++)
            {
                block[off + i] ^= hash[i];
                if (block[off + i] != 0)
                {
                    isOkay = false;
                }
            }

            if (!isOkay)
            {
                return returnFalse(block);
            }

            recoveredMessage = new byte[off - mStart];
            System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length);
        }

        //
        // if they've input a message check what we've recovered against
        // what was input.
        //
        if (messageLength != 0)
        {
            if (!isSameAs(mBuf, recoveredMessage))
            {
                return returnFalse(block);
            }
        }
        
        clearBlock(mBuf);
        clearBlock(block);

        messageLength = 0;

        return true;
    }

    private boolean returnFalse(byte[] block)
    {
        messageLength = 0;

        clearBlock(mBuf);
        clearBlock(block);

        return false;
    }

    /**
     * Return true if the full message was recoveredMessage.
     * 
     * @return true on full message recovery, false otherwise.
     * @see org.bouncycastle.crypto.SignerWithRecovery#hasFullMessage()
     */
    public boolean hasFullMessage()
    {
        return fullMessage;
    }

    /**
     * Return a reference to the recoveredMessage message, either as it was added
     * to a just generated signature, or extracted from a verified one.
     * 
     * @return the full/partial recoveredMessage message.
     * @see org.bouncycastle.crypto.SignerWithRecovery#getRecoveredMessage()
     */
    public byte[] getRecoveredMessage()
    {
        return recoveredMessage;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy