diff options
Diffstat (limited to 'core/src/main/java/org/bouncycastle/crypto/signers')
12 files changed, 3108 insertions, 0 deletions
diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/DSADigestSigner.java b/core/src/main/java/org/bouncycastle/crypto/signers/DSADigestSigner.java new file mode 100644 index 00000000..2e4c48d3 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/DSADigestSigner.java @@ -0,0 +1,163 @@ +package org.bouncycastle.crypto.signers; + +import java.io.IOException; +import java.math.BigInteger; + +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1Encoding; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.DERInteger; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DSA; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.ParametersWithRandom; + +public class DSADigestSigner + implements Signer +{ + private final Digest digest; + private final DSA dsaSigner; + private boolean forSigning; + + public DSADigestSigner( + DSA signer, + Digest digest) + { + this.digest = digest; + this.dsaSigner = signer; + } + + public void init( + boolean forSigning, + CipherParameters parameters) + { + this.forSigning = forSigning; + + AsymmetricKeyParameter k; + + if (parameters instanceof ParametersWithRandom) + { + k = (AsymmetricKeyParameter)((ParametersWithRandom)parameters).getParameters(); + } + else + { + k = (AsymmetricKeyParameter)parameters; + } + + if (forSigning && !k.isPrivate()) + { + throw new IllegalArgumentException("Signing Requires Private Key."); + } + + if (!forSigning && k.isPrivate()) + { + throw new IllegalArgumentException("Verification Requires Public Key."); + } + + reset(); + + dsaSigner.init(forSigning, parameters); + } + + /** + * update the internal digest with the byte b + */ + public void update( + byte input) + { + digest.update(input); + } + + /** + * update the internal digest with the byte array in + */ + public void update( + byte[] input, + int inOff, + int length) + { + digest.update(input, inOff, length); + } + + /** + * Generate a signature for the message we've been loaded with using + * the key we were initialised with. + */ + public byte[] generateSignature() + { + if (!forSigning) + { + throw new IllegalStateException("DSADigestSigner not initialised for signature generation."); + } + + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + + BigInteger[] sig = dsaSigner.generateSignature(hash); + + try + { + return derEncode(sig[0], sig[1]); + } + catch (IOException e) + { + throw new IllegalStateException("unable to encode signature"); + } + } + + public boolean verifySignature( + byte[] signature) + { + if (forSigning) + { + throw new IllegalStateException("DSADigestSigner not initialised for verification"); + } + + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + + try + { + BigInteger[] sig = derDecode(signature); + return dsaSigner.verifySignature(hash, sig[0], sig[1]); + } + catch (IOException e) + { + return false; + } + } + + public void reset() + { + digest.reset(); + } + + private byte[] derEncode( + BigInteger r, + BigInteger s) + throws IOException + { + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(new DERInteger(r)); + v.add(new DERInteger(s)); + + return new DERSequence(v).getEncoded(ASN1Encoding.DER); + } + + private BigInteger[] derDecode( + byte[] encoding) + throws IOException + { + ASN1Sequence s = (ASN1Sequence)ASN1Primitive.fromByteArray(encoding); + + return new BigInteger[] + { + ((DERInteger)s.getObjectAt(0)).getValue(), + ((DERInteger)s.getObjectAt(1)).getValue() + }; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/DSASigner.java b/core/src/main/java/org/bouncycastle/crypto/signers/DSASigner.java new file mode 100644 index 00000000..a96cef07 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/DSASigner.java @@ -0,0 +1,138 @@ +package org.bouncycastle.crypto.signers; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DSA; +import org.bouncycastle.crypto.params.DSAKeyParameters; +import org.bouncycastle.crypto.params.DSAParameters; +import org.bouncycastle.crypto.params.DSAPrivateKeyParameters; +import org.bouncycastle.crypto.params.DSAPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * The Digital Signature Algorithm - as described in "Handbook of Applied + * Cryptography", pages 452 - 453. + */ +public class DSASigner + implements DSA +{ + DSAKeyParameters key; + + SecureRandom random; + + public void init( + boolean forSigning, + CipherParameters param) + { + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + this.key = (DSAPrivateKeyParameters)rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + this.key = (DSAPrivateKeyParameters)param; + } + } + else + { + this.key = (DSAPublicKeyParameters)param; + } + } + + /** + * generate a signature for the given message using the key we were + * initialised with. For conventional DSA the message should be a SHA-1 + * hash of the message of interest. + * + * @param message the message that will be verified later. + */ + public BigInteger[] generateSignature( + byte[] message) + { + DSAParameters params = key.getParameters(); + BigInteger m = calculateE(params.getQ(), message); + BigInteger k; + int qBitLength = params.getQ().bitLength(); + + do + { + k = new BigInteger(qBitLength, random); + } + while (k.compareTo(params.getQ()) >= 0); + + BigInteger r = params.getG().modPow(k, params.getP()).mod(params.getQ()); + + k = k.modInverse(params.getQ()).multiply( + m.add(((DSAPrivateKeyParameters)key).getX().multiply(r))); + + BigInteger s = k.mod(params.getQ()); + + BigInteger[] res = new BigInteger[2]; + + res[0] = r; + res[1] = s; + + return res; + } + + /** + * return true if the value r and s represent a DSA signature for + * the passed in message for standard DSA the message should be a + * SHA-1 hash of the real message to be verified. + */ + public boolean verifySignature( + byte[] message, + BigInteger r, + BigInteger s) + { + DSAParameters params = key.getParameters(); + BigInteger m = calculateE(params.getQ(), message); + BigInteger zero = BigInteger.valueOf(0); + + if (zero.compareTo(r) >= 0 || params.getQ().compareTo(r) <= 0) + { + return false; + } + + if (zero.compareTo(s) >= 0 || params.getQ().compareTo(s) <= 0) + { + return false; + } + + BigInteger w = s.modInverse(params.getQ()); + + BigInteger u1 = m.multiply(w).mod(params.getQ()); + BigInteger u2 = r.multiply(w).mod(params.getQ()); + + u1 = params.getG().modPow(u1, params.getP()); + u2 = ((DSAPublicKeyParameters)key).getY().modPow(u2, params.getP()); + + BigInteger v = u1.multiply(u2).mod(params.getP()).mod(params.getQ()); + + return v.equals(r); + } + + private BigInteger calculateE(BigInteger n, byte[] message) + { + if (n.bitLength() >= message.length * 8) + { + return new BigInteger(1, message); + } + else + { + byte[] trunc = new byte[n.bitLength() / 8]; + + System.arraycopy(message, 0, trunc, 0, trunc.length); + + return new BigInteger(1, trunc); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/DSTU4145Signer.java b/core/src/main/java/org/bouncycastle/crypto/signers/DSTU4145Signer.java new file mode 100644 index 00000000..a8fc194e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/DSTU4145Signer.java @@ -0,0 +1,163 @@ +package org.bouncycastle.crypto.signers; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DSA; +import org.bouncycastle.crypto.params.ECKeyParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.math.ec.ECAlgorithms; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECFieldElement; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.Arrays; + +/** + * DSTU 4145-2002 + * <p> + * National Ukrainian standard of digital signature based on elliptic curves (DSTU 4145-2002). + * </p> + */ +public class DSTU4145Signer + implements DSA +{ + private static final BigInteger ONE = BigInteger.valueOf(1); + + private ECKeyParameters key; + private SecureRandom random; + + public void init(boolean forSigning, CipherParameters param) + { + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + param = rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + } + + this.key = (ECPrivateKeyParameters)param; + } + else + { + this.key = (ECPublicKeyParameters)param; + } + + } + + public BigInteger[] generateSignature(byte[] message) + { + ECFieldElement h = hash2FieldElement(key.getParameters().getCurve(), message); + if (h.toBigInteger().signum() == 0) + { + h = key.getParameters().getCurve().fromBigInteger(ONE); + } + + BigInteger e, r, s; + ECFieldElement Fe, y; + + do + { + do + { + do + { + e = generateRandomInteger(key.getParameters().getN(), random); + Fe = key.getParameters().getG().multiply(e).getX(); + } + while (Fe.toBigInteger().signum() == 0); + + y = h.multiply(Fe); + r = fieldElement2Integer(key.getParameters().getN(), y); + } + while (r.signum() == 0); + + s = r.multiply(((ECPrivateKeyParameters)key).getD()).add(e).mod(key.getParameters().getN()); + } + while (s.signum() == 0); + + return new BigInteger[]{r, s}; + } + + public boolean verifySignature(byte[] message, BigInteger r, BigInteger s) + { + if (r.signum() == 0 || s.signum() == 0) + { + return false; + } + if (r.compareTo(key.getParameters().getN()) >= 0 || s.compareTo(key.getParameters().getN()) >= 0) + { + return false; + } + + ECFieldElement h = hash2FieldElement(key.getParameters().getCurve(), message); + if (h.toBigInteger().signum() == 0) + { + h = key.getParameters().getCurve().fromBigInteger(ONE); + } + + ECPoint R = ECAlgorithms.sumOfTwoMultiplies(key.getParameters().getG(), s, ((ECPublicKeyParameters)key).getQ(), r); + + // components must be bogus. + if (R.isInfinity()) + { + return false; + } + + ECFieldElement y = h.multiply(R.getX()); + return fieldElement2Integer(key.getParameters().getN(), y).compareTo(r) == 0; + } + + /** + * Generates random integer such, than its bit length is less than that of n + */ + private static BigInteger generateRandomInteger(BigInteger n, SecureRandom random) + { + return new BigInteger(n.bitLength() - 1, random); + } + + private static void reverseBytes(byte[] bytes) + { + byte tmp; + + for (int i=0; i<bytes.length/2; i++) + { + tmp=bytes[i]; + bytes[i]=bytes[bytes.length-1-i]; + bytes[bytes.length-1-i]=tmp; + } + } + + private static ECFieldElement hash2FieldElement(ECCurve curve, byte[] hash) + { + byte[] data = Arrays.clone(hash); + reverseBytes(data); + BigInteger num = new BigInteger(1, data); + while (num.bitLength() >= curve.getFieldSize()) + { + num = num.clearBit(num.bitLength() - 1); + } + + return curve.fromBigInteger(num); + } + + private static BigInteger fieldElement2Integer(BigInteger n, ECFieldElement fieldElement) + { + BigInteger num = fieldElement.toBigInteger(); + while (num.bitLength() >= n.bitLength()) + { + num = num.clearBit(num.bitLength() - 1); + } + + return num; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/ECDSASigner.java b/core/src/main/java/org/bouncycastle/crypto/signers/ECDSASigner.java new file mode 100644 index 00000000..a80c574b --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/ECDSASigner.java @@ -0,0 +1,169 @@ +package org.bouncycastle.crypto.signers; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DSA; +import org.bouncycastle.crypto.params.ECKeyParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.math.ec.ECAlgorithms; +import org.bouncycastle.math.ec.ECConstants; +import org.bouncycastle.math.ec.ECPoint; + +/** + * EC-DSA as described in X9.62 + */ +public class ECDSASigner + implements ECConstants, DSA +{ + ECKeyParameters key; + + SecureRandom random; + + public void init( + boolean forSigning, + CipherParameters param) + { + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + this.key = (ECPrivateKeyParameters)rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + this.key = (ECPrivateKeyParameters)param; + } + } + else + { + this.key = (ECPublicKeyParameters)param; + } + } + + // 5.3 pg 28 + /** + * generate a signature for the given message using the key we were + * initialised with. For conventional DSA the message should be a SHA-1 + * hash of the message of interest. + * + * @param message the message that will be verified later. + */ + public BigInteger[] generateSignature( + byte[] message) + { + BigInteger n = key.getParameters().getN(); + BigInteger e = calculateE(n, message); + BigInteger r = null; + BigInteger s = null; + + // 5.3.2 + do // generate s + { + BigInteger k = null; + int nBitLength = n.bitLength(); + + do // generate r + { + do + { + k = new BigInteger(nBitLength, random); + } + while (k.equals(ZERO) || k.compareTo(n) >= 0); + + ECPoint p = key.getParameters().getG().multiply(k); + + // 5.3.3 + BigInteger x = p.getX().toBigInteger(); + + r = x.mod(n); + } + while (r.equals(ZERO)); + + BigInteger d = ((ECPrivateKeyParameters)key).getD(); + + s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n); + } + while (s.equals(ZERO)); + + BigInteger[] res = new BigInteger[2]; + + res[0] = r; + res[1] = s; + + return res; + } + + // 5.4 pg 29 + /** + * return true if the value r and s represent a DSA signature for + * the passed in message (for standard DSA the message should be + * a SHA-1 hash of the real message to be verified). + */ + public boolean verifySignature( + byte[] message, + BigInteger r, + BigInteger s) + { + BigInteger n = key.getParameters().getN(); + BigInteger e = calculateE(n, message); + + // r in the range [1,n-1] + if (r.compareTo(ONE) < 0 || r.compareTo(n) >= 0) + { + return false; + } + + // s in the range [1,n-1] + if (s.compareTo(ONE) < 0 || s.compareTo(n) >= 0) + { + return false; + } + + BigInteger c = s.modInverse(n); + + BigInteger u1 = e.multiply(c).mod(n); + BigInteger u2 = r.multiply(c).mod(n); + + ECPoint G = key.getParameters().getG(); + ECPoint Q = ((ECPublicKeyParameters)key).getQ(); + + ECPoint point = ECAlgorithms.sumOfTwoMultiplies(G, u1, Q, u2); + + // components must be bogus. + if (point.isInfinity()) + { + return false; + } + + BigInteger v = point.getX().toBigInteger().mod(n); + + return v.equals(r); + } + + private BigInteger calculateE(BigInteger n, byte[] message) + { + int log2n = n.bitLength(); + int messageBitLength = message.length * 8; + + if (log2n >= messageBitLength) + { + return new BigInteger(1, message); + } + else + { + BigInteger trunc = new BigInteger(1, message); + + trunc = trunc.shiftRight(messageBitLength - log2n); + + return trunc; + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/ECGOST3410Signer.java b/core/src/main/java/org/bouncycastle/crypto/signers/ECGOST3410Signer.java new file mode 100644 index 00000000..7256d353 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/ECGOST3410Signer.java @@ -0,0 +1,158 @@ +package org.bouncycastle.crypto.signers; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DSA; +import org.bouncycastle.crypto.params.ECKeyParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.math.ec.ECAlgorithms; +import org.bouncycastle.math.ec.ECConstants; +import org.bouncycastle.math.ec.ECPoint; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * GOST R 34.10-2001 Signature Algorithm + */ +public class ECGOST3410Signer + implements DSA +{ + ECKeyParameters key; + + SecureRandom random; + + public void init( + boolean forSigning, + CipherParameters param) + { + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + this.key = (ECPrivateKeyParameters)rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + this.key = (ECPrivateKeyParameters)param; + } + } + else + { + this.key = (ECPublicKeyParameters)param; + } + } + + /** + * generate a signature for the given message using the key we were + * initialised with. For conventional GOST3410 the message should be a GOST3411 + * hash of the message of interest. + * + * @param message the message that will be verified later. + */ + public BigInteger[] generateSignature( + byte[] message) + { + byte[] mRev = new byte[message.length]; // conversion is little-endian + for (int i = 0; i != mRev.length; i++) + { + mRev[i] = message[mRev.length - 1 - i]; + } + + BigInteger e = new BigInteger(1, mRev); + BigInteger n = key.getParameters().getN(); + + BigInteger r = null; + BigInteger s = null; + + do // generate s + { + BigInteger k = null; + + do // generate r + { + do + { + k = new BigInteger(n.bitLength(), random); + } + while (k.equals(ECConstants.ZERO)); + + ECPoint p = key.getParameters().getG().multiply(k); + + BigInteger x = p.getX().toBigInteger(); + + r = x.mod(n); + } + while (r.equals(ECConstants.ZERO)); + + BigInteger d = ((ECPrivateKeyParameters)key).getD(); + + s = (k.multiply(e)).add(d.multiply(r)).mod(n); + } + while (s.equals(ECConstants.ZERO)); + + BigInteger[] res = new BigInteger[2]; + + res[0] = r; + res[1] = s; + + return res; + } + + /** + * return true if the value r and s represent a GOST3410 signature for + * the passed in message (for standard GOST3410 the message should be + * a GOST3411 hash of the real message to be verified). + */ + public boolean verifySignature( + byte[] message, + BigInteger r, + BigInteger s) + { + byte[] mRev = new byte[message.length]; // conversion is little-endian + for (int i = 0; i != mRev.length; i++) + { + mRev[i] = message[mRev.length - 1 - i]; + } + + BigInteger e = new BigInteger(1, mRev); + BigInteger n = key.getParameters().getN(); + + // r in the range [1,n-1] + if (r.compareTo(ECConstants.ONE) < 0 || r.compareTo(n) >= 0) + { + return false; + } + + // s in the range [1,n-1] + if (s.compareTo(ECConstants.ONE) < 0 || s.compareTo(n) >= 0) + { + return false; + } + + BigInteger v = e.modInverse(n); + + BigInteger z1 = s.multiply(v).mod(n); + BigInteger z2 = (n.subtract(r)).multiply(v).mod(n); + + ECPoint G = key.getParameters().getG(); // P + ECPoint Q = ((ECPublicKeyParameters)key).getQ(); + + ECPoint point = ECAlgorithms.sumOfTwoMultiplies(G, z1, Q, z2); + + // components must be bogus. + if (point.isInfinity()) + { + return false; + } + + BigInteger R = point.getX().toBigInteger().mod(n); + + return R.equals(r); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/ECNRSigner.java b/core/src/main/java/org/bouncycastle/crypto/signers/ECNRSigner.java new file mode 100644 index 00000000..07e8ca7b --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/ECNRSigner.java @@ -0,0 +1,188 @@ +package org.bouncycastle.crypto.signers; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DSA; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.generators.ECKeyPairGenerator; +import org.bouncycastle.crypto.params.ECKeyGenerationParameters; +import org.bouncycastle.crypto.params.ECKeyParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.math.ec.ECAlgorithms; +import org.bouncycastle.math.ec.ECConstants; +import org.bouncycastle.math.ec.ECPoint; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * EC-NR as described in IEEE 1363-2000 + */ +public class ECNRSigner + implements DSA +{ + private boolean forSigning; + private ECKeyParameters key; + private SecureRandom random; + + public void init( + boolean forSigning, + CipherParameters param) + { + this.forSigning = forSigning; + + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + this.key = (ECPrivateKeyParameters)rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + this.key = (ECPrivateKeyParameters)param; + } + } + else + { + this.key = (ECPublicKeyParameters)param; + } + } + + // Section 7.2.5 ECSP-NR, pg 34 + /** + * generate a signature for the given message using the key we were + * initialised with. Generally, the order of the curve should be at + * least as long as the hash of the message of interest, and with + * ECNR it *must* be at least as long. + * + * @param digest the digest to be signed. + * @exception DataLengthException if the digest is longer than the key allows + */ + public BigInteger[] generateSignature( + byte[] digest) + { + if (! this.forSigning) + { + throw new IllegalStateException("not initialised for signing"); + } + + BigInteger n = ((ECPrivateKeyParameters)this.key).getParameters().getN(); + int nBitLength = n.bitLength(); + + BigInteger e = new BigInteger(1, digest); + int eBitLength = e.bitLength(); + + ECPrivateKeyParameters privKey = (ECPrivateKeyParameters)key; + + if (eBitLength > nBitLength) + { + throw new DataLengthException("input too large for ECNR key."); + } + + BigInteger r = null; + BigInteger s = null; + + AsymmetricCipherKeyPair tempPair; + do // generate r + { + // generate another, but very temporary, key pair using + // the same EC parameters + ECKeyPairGenerator keyGen = new ECKeyPairGenerator(); + + keyGen.init(new ECKeyGenerationParameters(privKey.getParameters(), this.random)); + + tempPair = keyGen.generateKeyPair(); + + // BigInteger Vx = tempPair.getPublic().getW().getAffineX(); + ECPublicKeyParameters V = (ECPublicKeyParameters)tempPair.getPublic(); // get temp's public key + BigInteger Vx = V.getQ().getX().toBigInteger(); // get the point's x coordinate + + r = Vx.add(e).mod(n); + } + while (r.equals(ECConstants.ZERO)); + + // generate s + BigInteger x = privKey.getD(); // private key value + BigInteger u = ((ECPrivateKeyParameters)tempPair.getPrivate()).getD(); // temp's private key value + s = u.subtract(r.multiply(x)).mod(n); + + BigInteger[] res = new BigInteger[2]; + res[0] = r; + res[1] = s; + + return res; + } + + // Section 7.2.6 ECVP-NR, pg 35 + /** + * return true if the value r and s represent a signature for the + * message passed in. Generally, the order of the curve should be at + * least as long as the hash of the message of interest, and with + * ECNR, it *must* be at least as long. But just in case the signer + * applied mod(n) to the longer digest, this implementation will + * apply mod(n) during verification. + * + * @param digest the digest to be verified. + * @param r the r value of the signature. + * @param s the s value of the signature. + * @exception DataLengthException if the digest is longer than the key allows + */ + public boolean verifySignature( + byte[] digest, + BigInteger r, + BigInteger s) + { + if (this.forSigning) + { + throw new IllegalStateException("not initialised for verifying"); + } + + ECPublicKeyParameters pubKey = (ECPublicKeyParameters)key; + BigInteger n = pubKey.getParameters().getN(); + int nBitLength = n.bitLength(); + + BigInteger e = new BigInteger(1, digest); + int eBitLength = e.bitLength(); + + if (eBitLength > nBitLength) + { + throw new DataLengthException("input too large for ECNR key."); + } + + // r in the range [1,n-1] + if (r.compareTo(ECConstants.ONE) < 0 || r.compareTo(n) >= 0) + { + return false; + } + + // s in the range [0,n-1] NB: ECNR spec says 0 + if (s.compareTo(ECConstants.ZERO) < 0 || s.compareTo(n) >= 0) + { + return false; + } + + // compute P = sG + rW + + ECPoint G = pubKey.getParameters().getG(); + ECPoint W = pubKey.getQ(); + // calculate P using Bouncy math + ECPoint P = ECAlgorithms.sumOfTwoMultiplies(G, s, W, r); + + // components must be bogus. + if (P.isInfinity()) + { + return false; + } + + BigInteger x = P.getX().toBigInteger(); + BigInteger t = r.subtract(x).mod(n); + + return t.equals(e); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/GOST3410Signer.java b/core/src/main/java/org/bouncycastle/crypto/signers/GOST3410Signer.java new file mode 100644 index 00000000..9fcc41b9 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/GOST3410Signer.java @@ -0,0 +1,127 @@ +package org.bouncycastle.crypto.signers; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DSA; +import org.bouncycastle.crypto.params.*; + +import java.security.SecureRandom; +import java.math.BigInteger; + +/** + * GOST R 34.10-94 Signature Algorithm + */ +public class GOST3410Signer + implements DSA +{ + GOST3410KeyParameters key; + + SecureRandom random; + + public void init( + boolean forSigning, + CipherParameters param) + { + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + this.key = (GOST3410PrivateKeyParameters)rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + this.key = (GOST3410PrivateKeyParameters)param; + } + } + else + { + this.key = (GOST3410PublicKeyParameters)param; + } + } + + /** + * generate a signature for the given message using the key we were + * initialised with. For conventional GOST3410 the message should be a GOST3411 + * hash of the message of interest. + * + * @param message the message that will be verified later. + */ + public BigInteger[] generateSignature( + byte[] message) + { + byte[] mRev = new byte[message.length]; // conversion is little-endian + for (int i = 0; i != mRev.length; i++) + { + mRev[i] = message[mRev.length - 1 - i]; + } + + BigInteger m = new BigInteger(1, mRev); + GOST3410Parameters params = key.getParameters(); + BigInteger k; + + do + { + k = new BigInteger(params.getQ().bitLength(), random); + } + while (k.compareTo(params.getQ()) >= 0); + + BigInteger r = params.getA().modPow(k, params.getP()).mod(params.getQ()); + + BigInteger s = k.multiply(m). + add(((GOST3410PrivateKeyParameters)key).getX().multiply(r)). + mod(params.getQ()); + + BigInteger[] res = new BigInteger[2]; + + res[0] = r; + res[1] = s; + + return res; + } + + /** + * return true if the value r and s represent a GOST3410 signature for + * the passed in message for standard GOST3410 the message should be a + * GOST3411 hash of the real message to be verified. + */ + public boolean verifySignature( + byte[] message, + BigInteger r, + BigInteger s) + { + byte[] mRev = new byte[message.length]; // conversion is little-endian + for (int i = 0; i != mRev.length; i++) + { + mRev[i] = message[mRev.length - 1 - i]; + } + + BigInteger m = new BigInteger(1, mRev); + GOST3410Parameters params = key.getParameters(); + BigInteger zero = BigInteger.valueOf(0); + + if (zero.compareTo(r) >= 0 || params.getQ().compareTo(r) <= 0) + { + return false; + } + + if (zero.compareTo(s) >= 0 || params.getQ().compareTo(s) <= 0) + { + return false; + } + + BigInteger v = m.modPow(params.getQ().subtract(new BigInteger("2")),params.getQ()); + + BigInteger z1 = s.multiply(v).mod(params.getQ()); + BigInteger z2 = (params.getQ().subtract(r)).multiply(v).mod(params.getQ()); + + z1 = params.getA().modPow(z1, params.getP()); + z2 = ((GOST3410PublicKeyParameters)key).getY().modPow(z2, params.getP()); + + BigInteger u = z1.multiply(z2).mod(params.getP()).mod(params.getQ()); + + return u.equals(r); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/GenericSigner.java b/core/src/main/java/org/bouncycastle/crypto/signers/GenericSigner.java new file mode 100644 index 00000000..6819e141 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/GenericSigner.java @@ -0,0 +1,136 @@ +package org.bouncycastle.crypto.signers; + +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.util.Arrays; + +public class GenericSigner + implements Signer +{ + private final AsymmetricBlockCipher engine; + private final Digest digest; + private boolean forSigning; + + public GenericSigner( + AsymmetricBlockCipher engine, + Digest digest) + { + this.engine = engine; + this.digest = digest; + } + + /** + * initialise the signer for signing or verification. + * + * @param forSigning + * true if for signing, false otherwise + * @param parameters + * necessary parameters. + */ + public void init( + boolean forSigning, + CipherParameters parameters) + { + this.forSigning = forSigning; + AsymmetricKeyParameter k; + + if (parameters instanceof ParametersWithRandom) + { + k = (AsymmetricKeyParameter)((ParametersWithRandom)parameters).getParameters(); + } + else + { + k = (AsymmetricKeyParameter)parameters; + } + + if (forSigning && !k.isPrivate()) + { + throw new IllegalArgumentException("signing requires private key"); + } + + if (!forSigning && k.isPrivate()) + { + throw new IllegalArgumentException("verification requires public key"); + } + + reset(); + + engine.init(forSigning, parameters); + } + + /** + * update the internal digest with the byte b + */ + public void update( + byte input) + { + digest.update(input); + } + + /** + * update the internal digest with the byte array in + */ + public void update( + byte[] input, + int inOff, + int length) + { + digest.update(input, inOff, length); + } + + /** + * Generate a signature for the message we've been loaded with using the key + * we were initialised with. + */ + public byte[] generateSignature() + throws CryptoException, DataLengthException + { + if (!forSigning) + { + throw new IllegalStateException("GenericSigner not initialised for signature generation."); + } + + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + + return engine.processBlock(hash, 0, hash.length); + } + + /** + * return true if the internal state represents the signature described in + * the passed in array. + */ + public boolean verifySignature( + byte[] signature) + { + if (forSigning) + { + throw new IllegalStateException("GenericSigner not initialised for verification"); + } + + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + + try + { + byte[] sig = engine.processBlock(signature, 0, signature.length); + + return Arrays.constantTimeAreEqual(sig, hash); + } + catch (Exception e) + { + return false; + } + } + + public void reset() + { + digest.reset(); + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/ISO9796d2PSSSigner.java b/core/src/main/java/org/bouncycastle/crypto/signers/ISO9796d2PSSSigner.java new file mode 100644 index 00000000..e3dcc08e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/ISO9796d2PSSSigner.java @@ -0,0 +1,668 @@ +package org.bouncycastle.crypto.signers; + +import java.security.SecureRandom; +import java.util.Hashtable; + +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.ParametersWithRandom; +import org.bouncycastle.crypto.params.ParametersWithSalt; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Integers; + +/** + * ISO9796-2 - mechanism using a hash function with recovery (scheme 2 and 3). + * <p/> + * Note: the usual length for the salt is the length of the hash + * function used in bytes. + */ +public class ISO9796d2PSSSigner + implements SignerWithRecovery +{ + static final public int TRAILER_IMPLICIT = 0xBC; + static final public int TRAILER_RIPEMD160 = 0x31CC; + static final public int TRAILER_RIPEMD128 = 0x32CC; + static final public int TRAILER_SHA1 = 0x33CC; + static final public int TRAILER_SHA256 = 0x34CC; + static final public int TRAILER_SHA512 = 0x35CC; + static final public int TRAILER_SHA384 = 0x36CC; + static final public int TRAILER_WHIRLPOOL = 0x37CC; + + private static Hashtable trailerMap = new Hashtable(); + + static + { + trailerMap.put("RIPEMD128", Integers.valueOf(TRAILER_RIPEMD128)); + trailerMap.put("RIPEMD160", Integers.valueOf(TRAILER_RIPEMD160)); + + trailerMap.put("SHA-1", Integers.valueOf(TRAILER_SHA1)); + trailerMap.put("SHA-256", Integers.valueOf(TRAILER_SHA256)); + trailerMap.put("SHA-384", Integers.valueOf(TRAILER_SHA384)); + trailerMap.put("SHA-512", Integers.valueOf(TRAILER_SHA512)); + + trailerMap.put("Whirlpool", Integers.valueOf(TRAILER_WHIRLPOOL)); + } + + private Digest digest; + private AsymmetricBlockCipher cipher; + + private SecureRandom random; + private byte[] standardSalt; + + private int hLen; + private int trailer; + private int keyBits; + private byte[] block; + private byte[] mBuf; + private int messageLength; + private int saltLength; + private boolean fullMessage; + private byte[] recoveredMessage; + + private byte[] preSig; + private byte[] preBlock; + private int preMStart; + private int preTLength; + + /** + * Generate a signer for the with either implicit or explicit trailers + * for ISO9796-2, scheme 2 or 3. + * + * @param cipher base cipher to use for signature creation/verification + * @param digest digest to use. + * @param saltLength length of salt in bytes. + * @param implicit whether or not the trailer is implicit or gives the hash. + */ + public ISO9796d2PSSSigner( + AsymmetricBlockCipher cipher, + Digest digest, + int saltLength, + boolean implicit) + { + this.cipher = cipher; + this.digest = digest; + this.hLen = digest.getDigestSize(); + this.saltLength = saltLength; + + if (implicit) + { + trailer = TRAILER_IMPLICIT; + } + else + { + Integer trailerObj = (Integer)trailerMap.get(digest.getAlgorithmName()); + + if (trailerObj != null) + { + trailer = trailerObj.intValue(); + } + else + { + throw new IllegalArgumentException("no valid trailer for digest"); + } + } + } + + /** + * Constructor for a signer with an explicit digest trailer. + * + * @param cipher cipher to use. + * @param digest digest to sign with. + * @param saltLength length of salt in bytes. + */ + public ISO9796d2PSSSigner( + AsymmetricBlockCipher cipher, + Digest digest, + int saltLength) + { + this(cipher, digest, saltLength, false); + } + + /** + * Initialise the signer. + * + * @param forSigning true if for signing, false if for verification. + * @param param parameters for signature generation/verification. If the + * parameters are for generation they should be a ParametersWithRandom, + * a ParametersWithSalt, or just an RSAKeyParameters object. If RSAKeyParameters + * are passed in a SecureRandom will be created. + * @throws IllegalArgumentException if wrong parameter type or a fixed + * salt is passed in which is the wrong length. + */ + public void init( + boolean forSigning, + CipherParameters param) + { + RSAKeyParameters kParam; + int lengthOfSalt = saltLength; + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom)param; + + kParam = (RSAKeyParameters)p.getParameters(); + if (forSigning) + { + random = p.getRandom(); + } + } + else if (param instanceof ParametersWithSalt) + { + ParametersWithSalt p = (ParametersWithSalt)param; + + kParam = (RSAKeyParameters)p.getParameters(); + standardSalt = p.getSalt(); + lengthOfSalt = standardSalt.length; + if (standardSalt.length != saltLength) + { + throw new IllegalArgumentException("Fixed salt is of wrong length"); + } + } + else + { + kParam = (RSAKeyParameters)param; + if (forSigning) + { + random = new SecureRandom(); + } + } + + cipher.init(forSigning, kParam); + + keyBits = kParam.getModulus().bitLength(); + + block = new byte[(keyBits + 7) / 8]; + + if (trailer == TRAILER_IMPLICIT) + { + mBuf = new byte[block.length - digest.getDigestSize() - lengthOfSalt - 1 - 1]; + } + else + { + mBuf = new byte[block.length - digest.getDigestSize() - lengthOfSalt - 1 - 2]; + } + + reset(); + } + + /** + * compare two byte arrays - constant time + */ + private boolean isSameAs( + byte[] a, + byte[] b) + { + boolean isOkay = true; + + 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); + + // + // adjust block size for leading zeroes if necessary + // + if (block.length < (keyBits + 7) / 8) + { + byte[] tmp = new byte[(keyBits + 7) / 8]; + + System.arraycopy(block, 0, tmp, tmp.length - block.length, block.length); + clearBlock(block); + block = tmp; + } + + int tLength; + + if (((block[block.length - 1] & 0xFF) ^ 0xBC) == 0) + { + tLength = 1; + } + else + { + int sigTrail = ((block[block.length - 2] & 0xFF) << 8) | (block[block.length - 1] & 0xFF); + + Integer trailerObj = (Integer)trailerMap.get(digest.getAlgorithmName()); + + if (trailerObj != null) + { + if (sigTrail != trailerObj.intValue()) + { + throw new IllegalStateException("signer initialised with wrong digest for trailer " + sigTrail); + } + } + else + { + throw new IllegalArgumentException("unrecognised hash in signature"); + } + + tLength = 2; + } + + // + // calculate H(m2) + // + byte[] m2Hash = new byte[hLen]; + digest.doFinal(m2Hash, 0); + + // + // remove the mask + // + byte[] dbMask = maskGeneratorFunction1(block, block.length - hLen - tLength, hLen, block.length - hLen - tLength); + for (int i = 0; i != dbMask.length; i++) + { + block[i] ^= dbMask[i]; + } + + block[0] &= 0x7f; + + // + // find out how much padding we've got + // + int mStart = 0; + for (; mStart != block.length; mStart++) + { + if (block[mStart] == 0x01) + { + break; + } + } + + mStart++; + + if (mStart >= block.length) + { + clearBlock(block); + } + + fullMessage = (mStart > 1); + + recoveredMessage = new byte[dbMask.length - mStart - saltLength]; + + System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length); + System.arraycopy(recoveredMessage, 0, mBuf, 0, recoveredMessage.length); + + preSig = signature; + preBlock = block; + preMStart = mStart; + preTLength = tLength; + } + + /** + * update the internal digest with the byte b + */ + public void update( + byte b) + { + if (preSig == null && messageLength < mBuf.length) + { + mBuf[messageLength++] = b; + } + else + { + digest.update(b); + } + } + + /** + * update the internal digest with the byte array in + */ + public void update( + byte[] in, + int off, + int len) + { + if (preSig == null) + { + while (len > 0 && messageLength < mBuf.length) + { + this.update(in[off]); + off++; + len--; + } + } + + if (len > 0) + { + digest.update(in, off, len); + } + } + + /** + * reset the internal state + */ + public void reset() + { + digest.reset(); + messageLength = 0; + if (mBuf != null) + { + 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(); + + byte[] m2Hash = new byte[digSize]; + + digest.doFinal(m2Hash, 0); + + byte[] C = new byte[8]; + LtoOSP(messageLength * 8, C); + + digest.update(C, 0, C.length); + + digest.update(mBuf, 0, messageLength); + + digest.update(m2Hash, 0, m2Hash.length); + + byte[] salt; + + if (standardSalt != null) + { + salt = standardSalt; + } + else + { + salt = new byte[saltLength]; + random.nextBytes(salt); + } + + digest.update(salt, 0, salt.length); + + byte[] hash = new byte[digest.getDigestSize()]; + + digest.doFinal(hash, 0); + + int tLength = 2; + if (trailer == TRAILER_IMPLICIT) + { + tLength = 1; + } + + int off = block.length - messageLength - salt.length - hLen - tLength - 1; + + block[off] = 0x01; + + System.arraycopy(mBuf, 0, block, off + 1, messageLength); + System.arraycopy(salt, 0, block, off + 1 + messageLength, salt.length); + + byte[] dbMask = maskGeneratorFunction1(hash, 0, hash.length, block.length - hLen - tLength); + for (int i = 0; i != dbMask.length; i++) + { + block[i] ^= dbMask[i]; + } + + System.arraycopy(hash, 0, block, block.length - hLen - tLength, hLen); + + if (trailer == TRAILER_IMPLICIT) + { + block[block.length - 1] = (byte)TRAILER_IMPLICIT; + } + else + { + block[block.length - 2] = (byte)(trailer >>> 8); + block[block.length - 1] = (byte)trailer; + } + + block[0] &= 0x7f; + + byte[] b = cipher.processBlock(block, 0, block.length); + + clearBlock(mBuf); + clearBlock(block); + messageLength = 0; + + return b; + } + + /** + * return true if the signature represents a ISO9796-2 signature + * for the passed in message. + */ + public boolean verifySignature( + byte[] signature) + { + // + // calculate H(m2) + // + byte[] m2Hash = new byte[hLen]; + digest.doFinal(m2Hash, 0); + + byte[] block; + int tLength; + int mStart = 0; + + if (preSig == null) + { + try + { + updateWithRecoveredMessage(signature); + } + catch (Exception e) + { + return false; + } + } + else + { + if (!Arrays.areEqual(preSig, signature)) + { + throw new IllegalStateException("updateWithRecoveredMessage called on different signature"); + } + } + + block = preBlock; + mStart = preMStart; + tLength = preTLength; + + preSig = null; + preBlock = null; + + // + // check the hashes + // + byte[] C = new byte[8]; + LtoOSP(recoveredMessage.length * 8, C); + + digest.update(C, 0, C.length); + + if (recoveredMessage.length != 0) + { + digest.update(recoveredMessage, 0, recoveredMessage.length); + } + + digest.update(m2Hash, 0, m2Hash.length); + + // Update for the salt + digest.update(block, mStart + recoveredMessage.length, saltLength); + + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + + int off = block.length - tLength - hash.length; + + boolean isOkay = true; + + for (int i = 0; i != hash.length; i++) + { + if (hash[i] != block[off + i]) + { + isOkay = false; + } + } + + clearBlock(block); + clearBlock(hash); + + if (!isOkay) + { + fullMessage = false; + clearBlock(recoveredMessage); + return false; + } + + // + // if they've input a message check what we've recovered against + // what was input. + // + if (messageLength != 0) + { + if (!isSameAs(mBuf, recoveredMessage)) + { + clearBlock(mBuf); + return false; + } + messageLength = 0; + } + + clearBlock(mBuf); + return true; + } + + /** + * Return true if the full message was recoveredMessage. + * + * @return true on full message recovery, false otherwise, or if not sure. + * @see org.bouncycastle.crypto.SignerWithRecovery#hasFullMessage() + */ + public boolean hasFullMessage() + { + return fullMessage; + } + + /** + * Return a reference to the recoveredMessage message. + * + * @return the full/partial recoveredMessage message. + * @see org.bouncycastle.crypto.SignerWithRecovery#getRecoveredMessage() + */ + public byte[] getRecoveredMessage() + { + return recoveredMessage; + } + + /** + * int to octet string. + */ + private void ItoOSP( + int i, + byte[] sp) + { + sp[0] = (byte)(i >>> 24); + sp[1] = (byte)(i >>> 16); + sp[2] = (byte)(i >>> 8); + sp[3] = (byte)(i >>> 0); + } + + /** + * long to octet string. + */ + private void LtoOSP( + long l, + byte[] sp) + { + sp[0] = (byte)(l >>> 56); + sp[1] = (byte)(l >>> 48); + sp[2] = (byte)(l >>> 40); + sp[3] = (byte)(l >>> 32); + sp[4] = (byte)(l >>> 24); + sp[5] = (byte)(l >>> 16); + sp[6] = (byte)(l >>> 8); + sp[7] = (byte)(l >>> 0); + } + + /** + * mask generator function, as described in PKCS1v2. + */ + private byte[] maskGeneratorFunction1( + byte[] Z, + int zOff, + int zLen, + int length) + { + byte[] mask = new byte[length]; + byte[] hashBuf = new byte[hLen]; + byte[] C = new byte[4]; + int counter = 0; + + digest.reset(); + + while (counter < (length / hLen)) + { + ItoOSP(counter, C); + + digest.update(Z, zOff, zLen); + digest.update(C, 0, C.length); + digest.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, mask, counter * hLen, hLen); + + counter++; + } + + if ((counter * hLen) < length) + { + ItoOSP(counter, C); + + digest.update(Z, zOff, zLen); + digest.update(C, 0, C.length); + digest.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, mask, counter * hLen, mask.length - (counter * hLen)); + } + + return mask; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/ISO9796d2Signer.java b/core/src/main/java/org/bouncycastle/crypto/signers/ISO9796d2Signer.java new file mode 100644 index 00000000..563fae63 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/ISO9796d2Signer.java @@ -0,0 +1,618 @@ +package org.bouncycastle.crypto.signers; + +import java.util.Hashtable; + +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; +import org.bouncycastle.util.Integers; + +/** + * ISO9796-2 - mechanism using a hash function with recovery (scheme 1) + */ +public class ISO9796d2Signer + implements SignerWithRecovery +{ + static final public int TRAILER_IMPLICIT = 0xBC; + static final public int TRAILER_RIPEMD160 = 0x31CC; + static final public int TRAILER_RIPEMD128 = 0x32CC; + static final public int TRAILER_SHA1 = 0x33CC; + static final public int TRAILER_SHA256 = 0x34CC; + static final public int TRAILER_SHA512 = 0x35CC; + static final public int TRAILER_SHA384 = 0x36CC; + static final public int TRAILER_WHIRLPOOL = 0x37CC; + + private static Hashtable trailerMap = new Hashtable(); + + static + { + trailerMap.put("RIPEMD128", Integers.valueOf(TRAILER_RIPEMD128)); + trailerMap.put("RIPEMD160", Integers.valueOf(TRAILER_RIPEMD160)); + + trailerMap.put("SHA-1", Integers.valueOf(TRAILER_SHA1)); + trailerMap.put("SHA-256", Integers.valueOf(TRAILER_SHA256)); + trailerMap.put("SHA-384", Integers.valueOf(TRAILER_SHA384)); + trailerMap.put("SHA-512", Integers.valueOf(TRAILER_SHA512)); + + trailerMap.put("Whirlpool", Integers.valueOf(TRAILER_WHIRLPOOL)); + } + + 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 for the 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 = TRAILER_IMPLICIT; + } + else + { + Integer trailerObj = (Integer)trailerMap.get(digest.getAlgorithmName()); + + if (trailerObj != null) + { + trailer = trailerObj.intValue(); + } + else + { + throw new IllegalArgumentException("no valid trailer for digest"); + } + } + } + + /** + * 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 == 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 = (Integer)trailerMap.get(digest.getAlgorithmName()); + + if (trailerObj != null) + { + if (sigTrail != trailerObj.intValue()) + { + 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 == TRAILER_IMPLICIT) + { + t = 8; + delta = block.length - digSize - 1; + digest.doFinal(block, delta); + block[block.length - 1] = (byte)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); + } + else + { + header = 0x40; + delta -= messageLength; + + System.arraycopy(mBuf, 0, block, delta, 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); + + 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 = (Integer)trailerMap.get(digest.getAlgorithmName()); + + if (trailerObj != null) + { + if (sigTrail != trailerObj.intValue()) + { + 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); + + return true; + } + + private boolean returnFalse(byte[] block) + { + 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. + * + * @return the full/partial recoveredMessage message. + * @see org.bouncycastle.crypto.SignerWithRecovery#getRecoveredMessage() + */ + public byte[] getRecoveredMessage() + { + return recoveredMessage; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/PSSSigner.java b/core/src/main/java/org/bouncycastle/crypto/signers/PSSSigner.java new file mode 100644 index 00000000..8c9eb940 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/PSSSigner.java @@ -0,0 +1,348 @@ +package org.bouncycastle.crypto.signers; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.params.RSABlindingParameters; +import org.bouncycastle.crypto.params.RSAKeyParameters; + +/** + * RSA-PSS as described in PKCS# 1 v 2.1. + * <p> + * Note: the usual value for the salt length is the number of + * bytes in the hash function. + */ +public class PSSSigner + implements Signer +{ + static final public byte TRAILER_IMPLICIT = (byte)0xBC; + + private Digest contentDigest; + private Digest mgfDigest; + private AsymmetricBlockCipher cipher; + private SecureRandom random; + + private int hLen; + private int mgfhLen; + private int sLen; + private int emBits; + private byte[] salt; + private byte[] mDash; + private byte[] block; + private byte trailer; + + /** + * basic constructor + * + * @param cipher the asymmetric cipher to use. + * @param digest the digest to use. + * @param sLen the length of the salt to use (in bytes). + */ + public PSSSigner( + AsymmetricBlockCipher cipher, + Digest digest, + int sLen) + { + this(cipher, digest, sLen, TRAILER_IMPLICIT); + } + + public PSSSigner( + AsymmetricBlockCipher cipher, + Digest contentDigest, + Digest mgfDigest, + int sLen) + { + this(cipher, contentDigest, mgfDigest, sLen, TRAILER_IMPLICIT); + } + + public PSSSigner( + AsymmetricBlockCipher cipher, + Digest digest, + int sLen, + byte trailer) + { + this(cipher, digest, digest, sLen, trailer); + } + + public PSSSigner( + AsymmetricBlockCipher cipher, + Digest contentDigest, + Digest mgfDigest, + int sLen, + byte trailer) + { + this.cipher = cipher; + this.contentDigest = contentDigest; + this.mgfDigest = mgfDigest; + this.hLen = contentDigest.getDigestSize(); + this.mgfhLen = mgfDigest.getDigestSize(); + this.sLen = sLen; + this.salt = new byte[sLen]; + this.mDash = new byte[8 + sLen + hLen]; + this.trailer = trailer; + } + + public void init( + boolean forSigning, + CipherParameters param) + { + CipherParameters params; + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom)param; + + params = p.getParameters(); + random = p.getRandom(); + } + else + { + params = param; + if (forSigning) + { + random = new SecureRandom(); + } + } + + cipher.init(forSigning, params); + + RSAKeyParameters kParam; + + if (params instanceof RSABlindingParameters) + { + kParam = ((RSABlindingParameters)params).getPublicKey(); + } + else + { + kParam = (RSAKeyParameters)params; + } + + emBits = kParam.getModulus().bitLength() - 1; + + if (emBits < (8 * hLen + 8 * sLen + 9)) + { + throw new IllegalArgumentException("key too small for specified hash and salt lengths"); + } + + block = new byte[(emBits + 7) / 8]; + + reset(); + } + + /** + * clear possible sensitive data + */ + private void clearBlock( + byte[] block) + { + for (int i = 0; i != block.length; i++) + { + block[i] = 0; + } + } + + /** + * update the internal digest with the byte b + */ + public void update( + byte b) + { + contentDigest.update(b); + } + + /** + * update the internal digest with the byte array in + */ + public void update( + byte[] in, + int off, + int len) + { + contentDigest.update(in, off, len); + } + + /** + * reset the internal state + */ + public void reset() + { + contentDigest.reset(); + } + + /** + * generate a signature for the message we've been loaded with using + * the key we were initialised with. + */ + public byte[] generateSignature() + throws CryptoException, DataLengthException + { + contentDigest.doFinal(mDash, mDash.length - hLen - sLen); + + if (sLen != 0) + { + random.nextBytes(salt); + + System.arraycopy(salt, 0, mDash, mDash.length - sLen, sLen); + } + + byte[] h = new byte[hLen]; + + contentDigest.update(mDash, 0, mDash.length); + + contentDigest.doFinal(h, 0); + + block[block.length - sLen - 1 - hLen - 1] = 0x01; + System.arraycopy(salt, 0, block, block.length - sLen - hLen - 1, sLen); + + byte[] dbMask = maskGeneratorFunction1(h, 0, h.length, block.length - hLen - 1); + for (int i = 0; i != dbMask.length; i++) + { + block[i] ^= dbMask[i]; + } + + block[0] &= (0xff >> ((block.length * 8) - emBits)); + + System.arraycopy(h, 0, block, block.length - hLen - 1, hLen); + + block[block.length - 1] = trailer; + + byte[] b = cipher.processBlock(block, 0, block.length); + + clearBlock(block); + + return b; + } + + /** + * return true if the internal state represents the signature described + * in the passed in array. + */ + public boolean verifySignature( + byte[] signature) + { + contentDigest.doFinal(mDash, mDash.length - hLen - sLen); + + try + { + byte[] b = cipher.processBlock(signature, 0, signature.length); + System.arraycopy(b, 0, block, block.length - b.length, b.length); + } + catch (Exception e) + { + return false; + } + + if (block[block.length - 1] != trailer) + { + clearBlock(block); + return false; + } + + byte[] dbMask = maskGeneratorFunction1(block, block.length - hLen - 1, hLen, block.length - hLen - 1); + + for (int i = 0; i != dbMask.length; i++) + { + block[i] ^= dbMask[i]; + } + + block[0] &= (0xff >> ((block.length * 8) - emBits)); + + for (int i = 0; i != block.length - hLen - sLen - 2; i++) + { + if (block[i] != 0) + { + clearBlock(block); + return false; + } + } + + if (block[block.length - hLen - sLen - 2] != 0x01) + { + clearBlock(block); + return false; + } + + System.arraycopy(block, block.length - sLen - hLen - 1, mDash, mDash.length - sLen, sLen); + + contentDigest.update(mDash, 0, mDash.length); + contentDigest.doFinal(mDash, mDash.length - hLen); + + for (int i = block.length - hLen - 1, j = mDash.length - hLen; + j != mDash.length; i++, j++) + { + if ((block[i] ^ mDash[j]) != 0) + { + clearBlock(mDash); + clearBlock(block); + return false; + } + } + + clearBlock(mDash); + clearBlock(block); + + return true; + } + + /** + * int to octet string. + */ + private void ItoOSP( + int i, + byte[] sp) + { + sp[0] = (byte)(i >>> 24); + sp[1] = (byte)(i >>> 16); + sp[2] = (byte)(i >>> 8); + sp[3] = (byte)(i >>> 0); + } + + /** + * mask generator function, as described in PKCS1v2. + */ + private byte[] maskGeneratorFunction1( + byte[] Z, + int zOff, + int zLen, + int length) + { + byte[] mask = new byte[length]; + byte[] hashBuf = new byte[mgfhLen]; + byte[] C = new byte[4]; + int counter = 0; + + mgfDigest.reset(); + + while (counter < (length / mgfhLen)) + { + ItoOSP(counter, C); + + mgfDigest.update(Z, zOff, zLen); + mgfDigest.update(C, 0, C.length); + mgfDigest.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, mask, counter * mgfhLen, mgfhLen); + + counter++; + } + + if ((counter * mgfhLen) < length) + { + ItoOSP(counter, C); + + mgfDigest.update(Z, zOff, zLen); + mgfDigest.update(C, 0, C.length); + mgfDigest.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, mask, counter * mgfhLen, mask.length - (counter * mgfhLen)); + } + + return mask; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/signers/RSADigestSigner.java b/core/src/main/java/org/bouncycastle/crypto/signers/RSADigestSigner.java new file mode 100644 index 00000000..f33ed317 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/signers/RSADigestSigner.java @@ -0,0 +1,232 @@ +package org.bouncycastle.crypto.signers; + +import java.io.IOException; +import java.util.Hashtable; + +import org.bouncycastle.asn1.ASN1Encoding; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.DERNull; +import org.bouncycastle.asn1.nist.NISTObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x509.DigestInfo; +import org.bouncycastle.asn1.x509.X509ObjectIdentifiers; +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.encodings.PKCS1Encoding; +import org.bouncycastle.crypto.engines.RSABlindedEngine; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.util.Arrays; + +public class RSADigestSigner + implements Signer +{ + private final AsymmetricBlockCipher rsaEngine = new PKCS1Encoding(new RSABlindedEngine()); + private final AlgorithmIdentifier algId; + private final Digest digest; + private boolean forSigning; + + private static final Hashtable oidMap = new Hashtable(); + + /* + * Load OID table. + */ + static + { + oidMap.put("RIPEMD128", TeleTrusTObjectIdentifiers.ripemd128); + oidMap.put("RIPEMD160", TeleTrusTObjectIdentifiers.ripemd160); + oidMap.put("RIPEMD256", TeleTrusTObjectIdentifiers.ripemd256); + + oidMap.put("SHA-1", X509ObjectIdentifiers.id_SHA1); + oidMap.put("SHA-224", NISTObjectIdentifiers.id_sha224); + oidMap.put("SHA-256", NISTObjectIdentifiers.id_sha256); + oidMap.put("SHA-384", NISTObjectIdentifiers.id_sha384); + oidMap.put("SHA-512", NISTObjectIdentifiers.id_sha512); + + oidMap.put("MD2", PKCSObjectIdentifiers.md2); + oidMap.put("MD4", PKCSObjectIdentifiers.md4); + oidMap.put("MD5", PKCSObjectIdentifiers.md5); + } + + public RSADigestSigner( + Digest digest) + { + this.digest = digest; + + algId = new AlgorithmIdentifier((ASN1ObjectIdentifier)oidMap.get(digest.getAlgorithmName()), DERNull.INSTANCE); + } + + /** + * @deprecated + */ + public String getAlgorithmName() + { + return digest.getAlgorithmName() + "withRSA"; + } + + /** + * initialise the signer for signing or verification. + * + * @param forSigning + * true if for signing, false otherwise + * @param parameters + * necessary parameters. + */ + public void init( + boolean forSigning, + CipherParameters parameters) + { + this.forSigning = forSigning; + AsymmetricKeyParameter k; + + if (parameters instanceof ParametersWithRandom) + { + k = (AsymmetricKeyParameter)((ParametersWithRandom)parameters).getParameters(); + } + else + { + k = (AsymmetricKeyParameter)parameters; + } + + if (forSigning && !k.isPrivate()) + { + throw new IllegalArgumentException("signing requires private key"); + } + + if (!forSigning && k.isPrivate()) + { + throw new IllegalArgumentException("verification requires public key"); + } + + reset(); + + rsaEngine.init(forSigning, parameters); + } + + /** + * update the internal digest with the byte b + */ + public void update( + byte input) + { + digest.update(input); + } + + /** + * update the internal digest with the byte array in + */ + public void update( + byte[] input, + int inOff, + int length) + { + digest.update(input, inOff, length); + } + + /** + * Generate a signature for the message we've been loaded with using the key + * we were initialised with. + */ + public byte[] generateSignature() + throws CryptoException, DataLengthException + { + if (!forSigning) + { + throw new IllegalStateException("RSADigestSigner not initialised for signature generation."); + } + + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + + try + { + byte[] data = derEncode(hash); + return rsaEngine.processBlock(data, 0, data.length); + } + catch (IOException e) + { + throw new CryptoException("unable to encode signature: " + e.getMessage(), e); + } + } + + /** + * return true if the internal state represents the signature described in + * the passed in array. + */ + public boolean verifySignature( + byte[] signature) + { + if (forSigning) + { + throw new IllegalStateException("RSADigestSigner not initialised for verification"); + } + + byte[] hash = new byte[digest.getDigestSize()]; + + digest.doFinal(hash, 0); + + byte[] sig; + byte[] expected; + + try + { + sig = rsaEngine.processBlock(signature, 0, signature.length); + expected = derEncode(hash); + } + catch (Exception e) + { + return false; + } + + if (sig.length == expected.length) + { + return Arrays.constantTimeAreEqual(sig, expected); + } + else if (sig.length == expected.length - 2) // NULL left out + { + int sigOffset = sig.length - hash.length - 2; + int expectedOffset = expected.length - hash.length - 2; + + expected[1] -= 2; // adjust lengths + expected[3] -= 2; + + int nonEqual = 0; + + for (int i = 0; i < hash.length; i++) + { + nonEqual |= (sig[sigOffset + i] ^ expected[expectedOffset + i]); + } + + for (int i = 0; i < sigOffset; i++) + { + nonEqual |= (sig[i] ^ expected[i]); // check header less NULL + } + + return nonEqual == 0; + } + else + { + return false; + } + } + + public void reset() + { + digest.reset(); + } + + private byte[] derEncode( + byte[] hash) + throws IOException + { + DigestInfo dInfo = new DigestInfo(algId, hash); + + return dInfo.getEncoded(ASN1Encoding.DER); + } +} |