org.bouncycastle.crypto.signers.ISO9796d2Signer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bcprov-ext-debug-jdk18on Show documentation
Show all versions of bcprov-ext-debug-jdk18on Show documentation
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;
}
}