Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/quite/humla-spongycastle.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoberto Tyley <roberto.tyley@gmail.com>2014-07-15 01:38:01 +0400
committerRoberto Tyley <roberto.tyley@gmail.com>2014-07-26 11:23:17 +0400
commit7cb752aaf746dc0b473afeb9e892b7fbc12666c5 (patch)
treecc4f91ddc18332b5adbe82e3fcb040d976c90105 /core/src/main/java/org/spongycastle/crypto/signers
parent551830f8ea5177042af2c7dd1fc90888bc67387d (diff)
Execute become-spongy.sh
https://github.com/rtyley/spongycastle/blob/3040af/become-spongy.sh
Diffstat (limited to 'core/src/main/java/org/spongycastle/crypto/signers')
-rw-r--r--core/src/main/java/org/spongycastle/crypto/signers/DSADigestSigner.java163
-rw-r--r--core/src/main/java/org/spongycastle/crypto/signers/DSAKCalculator.java41
-rw-r--r--core/src/main/java/org/spongycastle/crypto/signers/DSASigner.java168
-rw-r--r--core/src/main/java/org/spongycastle/crypto/signers/DSTU4145Signer.java170
-rw-r--r--core/src/main/java/org/spongycastle/crypto/signers/ECDSASigner.java197
-rw-r--r--core/src/main/java/org/spongycastle/crypto/signers/ECGOST3410Signer.java160
-rw-r--r--core/src/main/java/org/spongycastle/crypto/signers/ECNRSigner.java188
-rw-r--r--core/src/main/java/org/spongycastle/crypto/signers/GOST3410Signer.java131
-rw-r--r--core/src/main/java/org/spongycastle/crypto/signers/GenericSigner.java136
-rw-r--r--core/src/main/java/org/spongycastle/crypto/signers/HMacDSAKCalculator.java152
-rw-r--r--core/src/main/java/org/spongycastle/crypto/signers/ISO9796d2PSSSigner.java668
-rw-r--r--core/src/main/java/org/spongycastle/crypto/signers/ISO9796d2Signer.java618
-rw-r--r--core/src/main/java/org/spongycastle/crypto/signers/PSSSigner.java348
-rw-r--r--core/src/main/java/org/spongycastle/crypto/signers/RSADigestSigner.java240
-rw-r--r--core/src/main/java/org/spongycastle/crypto/signers/RandomDSAKCalculator.java43
15 files changed, 3423 insertions, 0 deletions
diff --git a/core/src/main/java/org/spongycastle/crypto/signers/DSADigestSigner.java b/core/src/main/java/org/spongycastle/crypto/signers/DSADigestSigner.java
new file mode 100644
index 00000000..243752e5
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/signers/DSADigestSigner.java
@@ -0,0 +1,163 @@
+package org.spongycastle.crypto.signers;
+
+import java.io.IOException;
+import java.math.BigInteger;
+
+import org.spongycastle.asn1.ASN1EncodableVector;
+import org.spongycastle.asn1.ASN1Encoding;
+import org.spongycastle.asn1.ASN1Integer;
+import org.spongycastle.asn1.ASN1Primitive;
+import org.spongycastle.asn1.ASN1Sequence;
+import org.spongycastle.asn1.DERSequence;
+import org.spongycastle.crypto.CipherParameters;
+import org.spongycastle.crypto.DSA;
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.Signer;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.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 ASN1Integer(r));
+ v.add(new ASN1Integer(s));
+
+ return new DERSequence(v).getEncoded(ASN1Encoding.DER);
+ }
+
+ private BigInteger[] derDecode(
+ byte[] encoding)
+ throws IOException
+ {
+ ASN1Sequence s = (ASN1Sequence)ASN1Primitive.fromByteArray(encoding);
+
+ return new BigInteger[]
+ {
+ ((ASN1Integer)s.getObjectAt(0)).getValue(),
+ ((ASN1Integer)s.getObjectAt(1)).getValue()
+ };
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/signers/DSAKCalculator.java b/core/src/main/java/org/spongycastle/crypto/signers/DSAKCalculator.java
new file mode 100644
index 00000000..6c8e1dc8
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/signers/DSAKCalculator.java
@@ -0,0 +1,41 @@
+package org.spongycastle.crypto.signers;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+/**
+ * Interface define calculators of K values for DSA/ECDSA.
+ */
+public interface DSAKCalculator
+{
+ /**
+ * Return true if this calculator is deterministic, false otherwise.
+ *
+ * @return true if deterministic, otherwise false.
+ */
+ boolean isDeterministic();
+
+ /**
+ * Non-deterministic initialiser.
+ *
+ * @param n the order of the DSA group.
+ * @param random a source of randomness.
+ */
+ void init(BigInteger n, SecureRandom random);
+
+ /**
+ * Deterministic initialiser.
+ *
+ * @param n the order of the DSA group.
+ * @param d the DSA private value.
+ * @param message the message being signed.
+ */
+ void init(BigInteger n, BigInteger d, byte[] message);
+
+ /**
+ * Return the next valid value of K.
+ *
+ * @return a K value.
+ */
+ BigInteger nextK();
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/signers/DSASigner.java b/core/src/main/java/org/spongycastle/crypto/signers/DSASigner.java
new file mode 100644
index 00000000..c4988d2b
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/signers/DSASigner.java
@@ -0,0 +1,168 @@
+package org.spongycastle.crypto.signers;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.spongycastle.crypto.CipherParameters;
+import org.spongycastle.crypto.DSA;
+import org.spongycastle.crypto.params.DSAKeyParameters;
+import org.spongycastle.crypto.params.DSAParameters;
+import org.spongycastle.crypto.params.DSAPrivateKeyParameters;
+import org.spongycastle.crypto.params.DSAPublicKeyParameters;
+import org.spongycastle.crypto.params.ParametersWithRandom;
+
+/**
+ * The Digital Signature Algorithm - as described in "Handbook of Applied
+ * Cryptography", pages 452 - 453.
+ */
+public class DSASigner
+ implements DSA
+{
+ private final DSAKCalculator kCalculator;
+
+ private DSAKeyParameters key;
+ private SecureRandom random;
+
+ /**
+ * Default configuration, random K values.
+ */
+ public DSASigner()
+ {
+ this.kCalculator = new RandomDSAKCalculator();
+ }
+
+ /**
+ * Configuration with an alternate, possibly deterministic calculator of K.
+ *
+ * @param kCalculator a K value calculator.
+ */
+ public DSASigner(DSAKCalculator kCalculator)
+ {
+ this.kCalculator = kCalculator;
+ }
+
+ public void init(
+ boolean forSigning,
+ CipherParameters param)
+ {
+ SecureRandom providedRandom = null;
+
+ if (forSigning)
+ {
+ if (param instanceof ParametersWithRandom)
+ {
+ ParametersWithRandom rParam = (ParametersWithRandom)param;
+
+ this.key = (DSAPrivateKeyParameters)rParam.getParameters();
+ providedRandom = rParam.getRandom();
+ }
+ else
+ {
+ this.key = (DSAPrivateKeyParameters)param;
+ }
+ }
+ else
+ {
+ this.key = (DSAPublicKeyParameters)param;
+ }
+
+ this.random = initSecureRandom(forSigning && !kCalculator.isDeterministic(), providedRandom);
+ }
+
+ /**
+ * 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);
+
+ if (kCalculator.isDeterministic())
+ {
+ kCalculator.init(params.getQ(), ((DSAPrivateKeyParameters)key).getX(), message);
+ }
+ else
+ {
+ kCalculator.init(params.getQ(), random);
+ }
+
+ BigInteger k = kCalculator.nextK();
+
+ 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);
+ }
+ }
+
+ protected SecureRandom initSecureRandom(boolean needed, SecureRandom provided)
+ {
+ return !needed ? null : (provided != null) ? provided : new SecureRandom();
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/signers/DSTU4145Signer.java b/core/src/main/java/org/spongycastle/crypto/signers/DSTU4145Signer.java
new file mode 100644
index 00000000..f938f5cf
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/signers/DSTU4145Signer.java
@@ -0,0 +1,170 @@
+package org.spongycastle.crypto.signers;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.spongycastle.crypto.CipherParameters;
+import org.spongycastle.crypto.DSA;
+import org.spongycastle.crypto.params.ECDomainParameters;
+import org.spongycastle.crypto.params.ECKeyParameters;
+import org.spongycastle.crypto.params.ECPrivateKeyParameters;
+import org.spongycastle.crypto.params.ECPublicKeyParameters;
+import org.spongycastle.crypto.params.ParametersWithRandom;
+import org.spongycastle.math.ec.ECAlgorithms;
+import org.spongycastle.math.ec.ECCurve;
+import org.spongycastle.math.ec.ECFieldElement;
+import org.spongycastle.math.ec.ECMultiplier;
+import org.spongycastle.math.ec.ECPoint;
+import org.spongycastle.math.ec.FixedPointCombMultiplier;
+import org.spongycastle.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)
+ {
+ ECDomainParameters ec = key.getParameters();
+
+ ECCurve curve = ec.getCurve();
+
+ ECFieldElement h = hash2FieldElement(curve, message);
+ if (h.isZero())
+ {
+ h = curve.fromBigInteger(ONE);
+ }
+
+ BigInteger n = ec.getN();
+ BigInteger e, r, s;
+ ECFieldElement Fe, y;
+
+ BigInteger d = ((ECPrivateKeyParameters)key).getD();
+
+ ECMultiplier basePointMultiplier = createBasePointMultiplier();
+
+ do
+ {
+ do
+ {
+ do
+ {
+ e = generateRandomInteger(n, random);
+ Fe = basePointMultiplier.multiply(ec.getG(), e).normalize().getAffineXCoord();
+ }
+ while (Fe.isZero());
+
+ y = h.multiply(Fe);
+ r = fieldElement2Integer(n, y);
+ }
+ while (r.signum() == 0);
+
+ s = r.multiply(d).add(e).mod(n);
+ }
+ 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;
+ }
+
+ ECDomainParameters parameters = key.getParameters();
+
+ BigInteger n = parameters.getN();
+ if (r.compareTo(n) >= 0 || s.compareTo(n) >= 0)
+ {
+ return false;
+ }
+
+ ECCurve curve = parameters.getCurve();
+
+ ECFieldElement h = hash2FieldElement(curve, message);
+ if (h.isZero())
+ {
+ h = curve.fromBigInteger(ONE);
+ }
+
+ ECPoint R = ECAlgorithms.sumOfTwoMultiplies(parameters.getG(), s, ((ECPublicKeyParameters)key).getQ(), r).normalize();
+
+ // components must be bogus.
+ if (R.isInfinity())
+ {
+ return false;
+ }
+
+ ECFieldElement y = h.multiply(R.getAffineXCoord());
+ return fieldElement2Integer(n, y).compareTo(r) == 0;
+ }
+
+ protected ECMultiplier createBasePointMultiplier()
+ {
+ return new FixedPointCombMultiplier();
+ }
+
+ /**
+ * 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 ECFieldElement hash2FieldElement(ECCurve curve, byte[] hash)
+ {
+ byte[] data = Arrays.reverse(hash);
+ return curve.fromBigInteger(truncate(new BigInteger(1, data), curve.getFieldSize()));
+ }
+
+ private static BigInteger fieldElement2Integer(BigInteger n, ECFieldElement fe)
+ {
+ return truncate(fe.toBigInteger(), n.bitLength() - 1);
+ }
+
+ private static BigInteger truncate(BigInteger x, int bitLength)
+ {
+ if (x.bitLength() > bitLength)
+ {
+ x = x.mod(ONE.shiftLeft(bitLength));
+ }
+ return x;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/signers/ECDSASigner.java b/core/src/main/java/org/spongycastle/crypto/signers/ECDSASigner.java
new file mode 100644
index 00000000..b0cfdf6c
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/signers/ECDSASigner.java
@@ -0,0 +1,197 @@
+package org.spongycastle.crypto.signers;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.spongycastle.crypto.CipherParameters;
+import org.spongycastle.crypto.DSA;
+import org.spongycastle.crypto.params.ECDomainParameters;
+import org.spongycastle.crypto.params.ECKeyParameters;
+import org.spongycastle.crypto.params.ECPrivateKeyParameters;
+import org.spongycastle.crypto.params.ECPublicKeyParameters;
+import org.spongycastle.crypto.params.ParametersWithRandom;
+import org.spongycastle.math.ec.ECAlgorithms;
+import org.spongycastle.math.ec.ECConstants;
+import org.spongycastle.math.ec.ECMultiplier;
+import org.spongycastle.math.ec.ECPoint;
+import org.spongycastle.math.ec.FixedPointCombMultiplier;
+
+/**
+ * EC-DSA as described in X9.62
+ */
+public class ECDSASigner
+ implements ECConstants, DSA
+{
+ private final DSAKCalculator kCalculator;
+
+ private ECKeyParameters key;
+ private SecureRandom random;
+
+ /**
+ * Default configuration, random K values.
+ */
+ public ECDSASigner()
+ {
+ this.kCalculator = new RandomDSAKCalculator();
+ }
+
+ /**
+ * Configuration with an alternate, possibly deterministic calculator of K.
+ *
+ * @param kCalculator a K value calculator.
+ */
+ public ECDSASigner(DSAKCalculator kCalculator)
+ {
+ this.kCalculator = kCalculator;
+ }
+
+ public void init(
+ boolean forSigning,
+ CipherParameters param)
+ {
+ SecureRandom providedRandom = null;
+
+ if (forSigning)
+ {
+ if (param instanceof ParametersWithRandom)
+ {
+ ParametersWithRandom rParam = (ParametersWithRandom)param;
+
+ this.key = (ECPrivateKeyParameters)rParam.getParameters();
+ providedRandom = rParam.getRandom();
+ }
+ else
+ {
+ this.key = (ECPrivateKeyParameters)param;
+ }
+ }
+ else
+ {
+ this.key = (ECPublicKeyParameters)param;
+ }
+
+ this.random = initSecureRandom(forSigning && !kCalculator.isDeterministic(), providedRandom);
+ }
+
+ // 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)
+ {
+ ECDomainParameters ec = key.getParameters();
+ BigInteger n = ec.getN();
+ BigInteger e = calculateE(n, message);
+ BigInteger d = ((ECPrivateKeyParameters)key).getD();
+
+ if (kCalculator.isDeterministic())
+ {
+ kCalculator.init(n, d, message);
+ }
+ else
+ {
+ kCalculator.init(n, random);
+ }
+
+ BigInteger r, s;
+
+ ECMultiplier basePointMultiplier = createBasePointMultiplier();
+
+ // 5.3.2
+ do // generate s
+ {
+ BigInteger k;
+ do // generate r
+ {
+ k = kCalculator.nextK();
+
+ ECPoint p = basePointMultiplier.multiply(ec.getG(), k).normalize();
+
+ // 5.3.3
+ r = p.getAffineXCoord().toBigInteger().mod(n);
+ }
+ while (r.equals(ZERO));
+
+ s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n);
+ }
+ while (s.equals(ZERO));
+
+ return new BigInteger[]{ r, s };
+ }
+
+ // 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)
+ {
+ ECDomainParameters ec = key.getParameters();
+ BigInteger n = ec.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 = ec.getG();
+ ECPoint Q = ((ECPublicKeyParameters)key).getQ();
+
+ ECPoint point = ECAlgorithms.sumOfTwoMultiplies(G, u1, Q, u2).normalize();
+
+ // components must be bogus.
+ if (point.isInfinity())
+ {
+ return false;
+ }
+
+ BigInteger v = point.getAffineXCoord().toBigInteger().mod(n);
+
+ return v.equals(r);
+ }
+
+ protected BigInteger calculateE(BigInteger n, byte[] message)
+ {
+ int log2n = n.bitLength();
+ int messageBitLength = message.length * 8;
+
+ BigInteger e = new BigInteger(1, message);
+ if (log2n < messageBitLength)
+ {
+ e = e.shiftRight(messageBitLength - log2n);
+ }
+ return e;
+ }
+
+ protected ECMultiplier createBasePointMultiplier()
+ {
+ return new FixedPointCombMultiplier();
+ }
+
+ protected SecureRandom initSecureRandom(boolean needed, SecureRandom provided)
+ {
+ return !needed ? null : (provided != null) ? provided : new SecureRandom();
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/signers/ECGOST3410Signer.java b/core/src/main/java/org/spongycastle/crypto/signers/ECGOST3410Signer.java
new file mode 100644
index 00000000..92cc56ab
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/signers/ECGOST3410Signer.java
@@ -0,0 +1,160 @@
+package org.spongycastle.crypto.signers;
+
+import org.spongycastle.crypto.CipherParameters;
+import org.spongycastle.crypto.DSA;
+import org.spongycastle.crypto.params.ECDomainParameters;
+import org.spongycastle.crypto.params.ECKeyParameters;
+import org.spongycastle.crypto.params.ECPrivateKeyParameters;
+import org.spongycastle.crypto.params.ECPublicKeyParameters;
+import org.spongycastle.crypto.params.ParametersWithRandom;
+import org.spongycastle.math.ec.ECAlgorithms;
+import org.spongycastle.math.ec.ECConstants;
+import org.spongycastle.math.ec.ECMultiplier;
+import org.spongycastle.math.ec.ECPoint;
+import org.spongycastle.math.ec.FixedPointCombMultiplier;
+
+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);
+
+ ECDomainParameters ec = key.getParameters();
+ BigInteger n = ec.getN();
+ BigInteger d = ((ECPrivateKeyParameters)key).getD();
+
+ BigInteger r, s;
+
+ ECMultiplier basePointMultiplier = createBasePointMultiplier();
+
+ do // generate s
+ {
+ BigInteger k;
+ do // generate r
+ {
+ do
+ {
+ k = new BigInteger(n.bitLength(), random);
+ }
+ while (k.equals(ECConstants.ZERO));
+
+ ECPoint p = basePointMultiplier.multiply(ec.getG(), k).normalize();
+
+ r = p.getAffineXCoord().toBigInteger().mod(n);
+ }
+ while (r.equals(ECConstants.ZERO));
+
+ s = (k.multiply(e)).add(d.multiply(r)).mod(n);
+ }
+ while (s.equals(ECConstants.ZERO));
+
+ return new BigInteger[]{ r, s };
+ }
+
+ /**
+ * 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).normalize();
+
+ // components must be bogus.
+ if (point.isInfinity())
+ {
+ return false;
+ }
+
+ BigInteger R = point.getAffineXCoord().toBigInteger().mod(n);
+
+ return R.equals(r);
+ }
+
+ protected ECMultiplier createBasePointMultiplier()
+ {
+ return new FixedPointCombMultiplier();
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/signers/ECNRSigner.java b/core/src/main/java/org/spongycastle/crypto/signers/ECNRSigner.java
new file mode 100644
index 00000000..2fa90fb2
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/signers/ECNRSigner.java
@@ -0,0 +1,188 @@
+package org.spongycastle.crypto.signers;
+
+import org.spongycastle.crypto.AsymmetricCipherKeyPair;
+import org.spongycastle.crypto.CipherParameters;
+import org.spongycastle.crypto.DSA;
+import org.spongycastle.crypto.DataLengthException;
+import org.spongycastle.crypto.generators.ECKeyPairGenerator;
+import org.spongycastle.crypto.params.ECKeyGenerationParameters;
+import org.spongycastle.crypto.params.ECKeyParameters;
+import org.spongycastle.crypto.params.ECPrivateKeyParameters;
+import org.spongycastle.crypto.params.ECPublicKeyParameters;
+import org.spongycastle.crypto.params.ParametersWithRandom;
+import org.spongycastle.math.ec.ECAlgorithms;
+import org.spongycastle.math.ec.ECConstants;
+import org.spongycastle.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().getAffineXCoord().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).normalize();
+
+ // components must be bogus.
+ if (P.isInfinity())
+ {
+ return false;
+ }
+
+ BigInteger x = P.getAffineXCoord().toBigInteger();
+ BigInteger t = r.subtract(x).mod(n);
+
+ return t.equals(e);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/signers/GOST3410Signer.java b/core/src/main/java/org/spongycastle/crypto/signers/GOST3410Signer.java
new file mode 100644
index 00000000..81d5d7a6
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/signers/GOST3410Signer.java
@@ -0,0 +1,131 @@
+package org.spongycastle.crypto.signers;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.spongycastle.crypto.CipherParameters;
+import org.spongycastle.crypto.DSA;
+import org.spongycastle.crypto.params.GOST3410KeyParameters;
+import org.spongycastle.crypto.params.GOST3410Parameters;
+import org.spongycastle.crypto.params.GOST3410PrivateKeyParameters;
+import org.spongycastle.crypto.params.GOST3410PublicKeyParameters;
+import org.spongycastle.crypto.params.ParametersWithRandom;
+
+/**
+ * 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/spongycastle/crypto/signers/GenericSigner.java b/core/src/main/java/org/spongycastle/crypto/signers/GenericSigner.java
new file mode 100644
index 00000000..0df5a875
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/signers/GenericSigner.java
@@ -0,0 +1,136 @@
+package org.spongycastle.crypto.signers;
+
+import org.spongycastle.crypto.AsymmetricBlockCipher;
+import org.spongycastle.crypto.CipherParameters;
+import org.spongycastle.crypto.CryptoException;
+import org.spongycastle.crypto.DataLengthException;
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.Signer;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.params.ParametersWithRandom;
+import org.spongycastle.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/spongycastle/crypto/signers/HMacDSAKCalculator.java b/core/src/main/java/org/spongycastle/crypto/signers/HMacDSAKCalculator.java
new file mode 100644
index 00000000..e919673f
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/signers/HMacDSAKCalculator.java
@@ -0,0 +1,152 @@
+package org.spongycastle.crypto.signers;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.macs.HMac;
+import org.spongycastle.crypto.params.KeyParameter;
+import org.spongycastle.util.Arrays;
+import org.spongycastle.util.BigIntegers;
+
+/**
+ * A deterministic K calculator based on the algorithm in section 3.2 of RFC 6979.
+ */
+public class HMacDSAKCalculator
+ implements DSAKCalculator
+{
+ private static final BigInteger ZERO = BigInteger.valueOf(0);
+
+ private final HMac hMac;
+ private final byte[] K;
+ private final byte[] V;
+
+ private BigInteger n;
+
+ /**
+ * Base constructor.
+ *
+ * @param digest digest to build the HMAC on.
+ */
+ public HMacDSAKCalculator(Digest digest)
+ {
+ this.hMac = new HMac(digest);
+ this.V = new byte[hMac.getMacSize()];
+ this.K = new byte[hMac.getMacSize()];
+ }
+
+ public boolean isDeterministic()
+ {
+ return true;
+ }
+
+ public void init(BigInteger n, SecureRandom random)
+ {
+ throw new IllegalStateException("Operation not supported");
+ }
+
+ public void init(BigInteger n, BigInteger d, byte[] message)
+ {
+ this.n = n;
+
+ Arrays.fill(V, (byte)0x01);
+ Arrays.fill(K, (byte)0);
+
+ byte[] x = new byte[(n.bitLength() + 7) / 8];
+ byte[] dVal = BigIntegers.asUnsignedByteArray(d);
+
+ System.arraycopy(dVal, 0, x, x.length - dVal.length, dVal.length);
+
+ byte[] m = new byte[(n.bitLength() + 7) / 8];
+
+ BigInteger mInt = bitsToInt(message);
+
+ if (mInt.compareTo(n) > 0)
+ {
+ mInt = mInt.subtract(n);
+ }
+
+ byte[] mVal = BigIntegers.asUnsignedByteArray(mInt);
+
+ System.arraycopy(mVal, 0, m, m.length - mVal.length, mVal.length);
+
+ hMac.init(new KeyParameter(K));
+
+ hMac.update(V, 0, V.length);
+ hMac.update((byte)0x00);
+ hMac.update(x, 0, x.length);
+ hMac.update(m, 0, m.length);
+
+ hMac.doFinal(K, 0);
+
+ hMac.init(new KeyParameter(K));
+
+ hMac.update(V, 0, V.length);
+
+ hMac.doFinal(V, 0);
+
+ hMac.update(V, 0, V.length);
+ hMac.update((byte)0x01);
+ hMac.update(x, 0, x.length);
+ hMac.update(m, 0, m.length);
+
+ hMac.doFinal(K, 0);
+
+ hMac.init(new KeyParameter(K));
+
+ hMac.update(V, 0, V.length);
+
+ hMac.doFinal(V, 0);
+ }
+
+ public BigInteger nextK()
+ {
+ byte[] t = new byte[((n.bitLength() + 7) / 8)];
+
+ for (;;)
+ {
+ int tOff = 0;
+
+ while (tOff < t.length)
+ {
+ hMac.update(V, 0, V.length);
+
+ hMac.doFinal(V, 0);
+
+ int len = Math.min(t.length - tOff, V.length);
+ System.arraycopy(V, 0, t, tOff, len);
+ tOff += len;
+ }
+
+ BigInteger k = bitsToInt(t);
+
+ if (k.compareTo(ZERO) > 0 && k.compareTo(n) < 0)
+ {
+ return k;
+ }
+
+ hMac.update(V, 0, V.length);
+ hMac.update((byte)0x00);
+
+ hMac.doFinal(K, 0);
+
+ hMac.init(new KeyParameter(K));
+
+ hMac.update(V, 0, V.length);
+
+ hMac.doFinal(V, 0);
+ }
+ }
+
+ private BigInteger bitsToInt(byte[] t)
+ {
+ BigInteger v = new BigInteger(1, t);
+
+ if (t.length * 8 > n.bitLength())
+ {
+ v = v.shiftRight(t.length * 8 - n.bitLength());
+ }
+
+ return v;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/signers/ISO9796d2PSSSigner.java b/core/src/main/java/org/spongycastle/crypto/signers/ISO9796d2PSSSigner.java
new file mode 100644
index 00000000..5ebb1e28
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/signers/ISO9796d2PSSSigner.java
@@ -0,0 +1,668 @@
+package org.spongycastle.crypto.signers;
+
+import java.security.SecureRandom;
+import java.util.Hashtable;
+
+import org.spongycastle.crypto.AsymmetricBlockCipher;
+import org.spongycastle.crypto.CipherParameters;
+import org.spongycastle.crypto.CryptoException;
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.InvalidCipherTextException;
+import org.spongycastle.crypto.SignerWithRecovery;
+import org.spongycastle.crypto.params.ParametersWithRandom;
+import org.spongycastle.crypto.params.ParametersWithSalt;
+import org.spongycastle.crypto.params.RSAKeyParameters;
+import org.spongycastle.util.Arrays;
+import org.spongycastle.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.spongycastle.crypto.SignerWithRecovery#hasFullMessage()
+ */
+ public boolean hasFullMessage()
+ {
+ return fullMessage;
+ }
+
+ /**
+ * Return a reference to the recoveredMessage message.
+ *
+ * @return the full/partial recoveredMessage message.
+ * @see org.spongycastle.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/spongycastle/crypto/signers/ISO9796d2Signer.java b/core/src/main/java/org/spongycastle/crypto/signers/ISO9796d2Signer.java
new file mode 100644
index 00000000..7047d1ee
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/signers/ISO9796d2Signer.java
@@ -0,0 +1,618 @@
+package org.spongycastle.crypto.signers;
+
+import java.util.Hashtable;
+
+import org.spongycastle.crypto.AsymmetricBlockCipher;
+import org.spongycastle.crypto.CipherParameters;
+import org.spongycastle.crypto.CryptoException;
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.InvalidCipherTextException;
+import org.spongycastle.crypto.SignerWithRecovery;
+import org.spongycastle.crypto.params.RSAKeyParameters;
+import org.spongycastle.util.Arrays;
+import org.spongycastle.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.spongycastle.crypto.SignerWithRecovery#hasFullMessage()
+ */
+ public boolean hasFullMessage()
+ {
+ return fullMessage;
+ }
+
+ /**
+ * Return a reference to the recoveredMessage message.
+ *
+ * @return the full/partial recoveredMessage message.
+ * @see org.spongycastle.crypto.SignerWithRecovery#getRecoveredMessage()
+ */
+ public byte[] getRecoveredMessage()
+ {
+ return recoveredMessage;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/signers/PSSSigner.java b/core/src/main/java/org/spongycastle/crypto/signers/PSSSigner.java
new file mode 100644
index 00000000..3fb8bf50
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/signers/PSSSigner.java
@@ -0,0 +1,348 @@
+package org.spongycastle.crypto.signers;
+
+import java.security.SecureRandom;
+
+import org.spongycastle.crypto.AsymmetricBlockCipher;
+import org.spongycastle.crypto.CipherParameters;
+import org.spongycastle.crypto.CryptoException;
+import org.spongycastle.crypto.DataLengthException;
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.Signer;
+import org.spongycastle.crypto.params.ParametersWithRandom;
+import org.spongycastle.crypto.params.RSABlindingParameters;
+import org.spongycastle.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/spongycastle/crypto/signers/RSADigestSigner.java b/core/src/main/java/org/spongycastle/crypto/signers/RSADigestSigner.java
new file mode 100644
index 00000000..2fe248e4
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/signers/RSADigestSigner.java
@@ -0,0 +1,240 @@
+package org.spongycastle.crypto.signers;
+
+import java.io.IOException;
+import java.util.Hashtable;
+
+import org.spongycastle.asn1.ASN1Encoding;
+import org.spongycastle.asn1.ASN1ObjectIdentifier;
+import org.spongycastle.asn1.DERNull;
+import org.spongycastle.asn1.nist.NISTObjectIdentifiers;
+import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.spongycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
+import org.spongycastle.asn1.x509.AlgorithmIdentifier;
+import org.spongycastle.asn1.x509.DigestInfo;
+import org.spongycastle.asn1.x509.X509ObjectIdentifiers;
+import org.spongycastle.crypto.AsymmetricBlockCipher;
+import org.spongycastle.crypto.CipherParameters;
+import org.spongycastle.crypto.CryptoException;
+import org.spongycastle.crypto.DataLengthException;
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.Signer;
+import org.spongycastle.crypto.encodings.PKCS1Encoding;
+import org.spongycastle.crypto.engines.RSABlindedEngine;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.params.ParametersWithRandom;
+import org.spongycastle.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("SHA-512/224", NISTObjectIdentifiers.id_sha512_224);
+ oidMap.put("SHA-512/256", NISTObjectIdentifiers.id_sha512_256);
+
+ oidMap.put("MD2", PKCSObjectIdentifiers.md2);
+ oidMap.put("MD4", PKCSObjectIdentifiers.md4);
+ oidMap.put("MD5", PKCSObjectIdentifiers.md5);
+ }
+
+ public RSADigestSigner(
+ Digest digest)
+ {
+ this(digest, (ASN1ObjectIdentifier)oidMap.get(digest.getAlgorithmName()));
+ }
+
+ public RSADigestSigner(
+ Digest digest,
+ ASN1ObjectIdentifier digestOid)
+ {
+ this.digest = digest;
+ this.algId = new AlgorithmIdentifier(digestOid, 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);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/signers/RandomDSAKCalculator.java b/core/src/main/java/org/spongycastle/crypto/signers/RandomDSAKCalculator.java
new file mode 100644
index 00000000..fb8e3ae7
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/signers/RandomDSAKCalculator.java
@@ -0,0 +1,43 @@
+package org.spongycastle.crypto.signers;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+class RandomDSAKCalculator
+ implements DSAKCalculator
+{
+ private static final BigInteger ZERO = BigInteger.valueOf(0);
+
+ private BigInteger q;
+ private SecureRandom random;
+
+ public boolean isDeterministic()
+ {
+ return false;
+ }
+
+ public void init(BigInteger n, SecureRandom random)
+ {
+ this.q = n;
+ this.random = random;
+ }
+
+ public void init(BigInteger n, BigInteger d, byte[] message)
+ {
+ throw new IllegalStateException("Operation not supported");
+ }
+
+ public BigInteger nextK()
+ {
+ int qBitLength = q.bitLength();
+
+ BigInteger k;
+ do
+ {
+ k = new BigInteger(qBitLength, random);
+ }
+ while (k.equals(ZERO) || k.compareTo(q) >= 0);
+
+ return k;
+ }
+}