mchr
mchr

Reputation: 6251

Verify DSA Signature generated by Java in C#

I know this is a topic with lots of existing questions but I am failing to find any existing answers which cover my exact case.

I need to sign a string (a URL) in some Java code and deliver the string along with the signature to a C# program.

I run the following Java code once to generate a DSA keypair:

KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA", "SUN");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
keyGen.initialize(1024, random);

KeyPair pair = keyGen.generateKeyPair();
PrivateKey priv = pair.getPrivate();
PublicKey pub = pair.getPublic();

/* save the private key in a file */
byte[] privkey = priv.getEncoded();
FileOutputStream privkeyfos = new FileOutputStream("key.priv");
privkeyfos.write(privkey);
privkeyfos.close();

/* save the public key in a file */
byte[] pubkey = pub.getEncoded();
FileOutputStream pubkeyfos = new FileOutputStream("key.public");
pubkeyfos.write(pubkey);
pubkeyfos.close();

I am then using the following code to generate the signature.

public static String Sign(String keyPath, byte[] data)
{
  FileInputStream keyfis = new FileInputStream(new File(keyPath, "key.priv"));
  byte[] encKey = new byte[keyfis.available()];
  keyfis.read(encKey);
  keyfis.close();

  PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(encKey);
  KeyFactory keyFactory = KeyFactory.getInstance("DSA");
  PrivateKey privKey = keyFactory.generatePrivate(privKeySpec);

  Signature dsa = Signature.getInstance("SHA1withDSA", "SUN");

  dsa.initSign(privKey);

  ByteArrayInputStream in = new ByteArrayInputStream(data);
  BufferedInputStream bufin = new BufferedInputStream(in);
  byte[] buffer = new byte[1024];
  int len;
  while ((len = bufin.read(buffer)) >= 0)
  {
    dsa.update(buffer, 0, len);
  }
  bufin.close();

  byte[] realSig = dsa.sign();

  return new String(Base64.encodeBase64(realSig), "UTF-8");
}

In my C# code I have access to the string, the Base64 encoded signature and the "key.public" file from the first step.

Can anyone provide a block of code which combines these elements along with a suitable library to determine whether the string has been tampered with?

Upvotes: 1

Views: 5306

Answers (1)

mchr
mchr

Reputation: 6251

I have now solved this with some key input coming from this article: http://www.codeproject.com/KB/security/CryptoInteropSign.aspx

The main verification is done using the following C# function.

private static Boolean isValid(String xiString, String xiSig)
{
  AsnKeyParser keyParser = new AsnKeyParser("path/to/key.public");
  DSAParameters publicKey = keyParser.ParseDSAPublicKey();

  DSACryptoServiceProvider DSA = new DSACryptoServiceProvider();
  DSA.ImportParameters(publicKey);
  DSASignatureDeformatter DSADeformatter = new DSASignatureDeformatter(DSA);
  UTF8Encoding UTF8 = new UTF8Encoding();
  byte[] plainBytes = UTF8.GetBytes(xiString);      
  var sha1 = new SHA1Managed();
  var hash = sha1.ComputeHash(plainBytes);
  byte[] asn1SigBytes = Convert.FromBase64String(xiSig);
  byte[] sigBytes = ConvertToP1363Signature(asn1SigBytes);
  Boolean retVal = DSADeformatter.VerifySignature(hash, sigBytes);
  return retVal;
}

This relies on two helper methods.

1) AsnKeyParser is a class attached to the linked article. The article offers a C# download from which I used two files: AsnKeyParser.cs and BerDecodeError.cs. I deleted the RSA functions from AsnKeyParser to remove the dependency on the BigInteger file.

This class handles parsing the "key.public" file created by my Java code.

2) A function for converting the 46-48 byte DER encoded signature generated by Java into a DSA signature which C# will accept.

This function is based on code in the comments of the linked article.

private static byte[] ConvertToP1363Signature(byte[] ASN1Sig)
{
  AsnParser asn = new AsnParser(ASN1Sig);
  asn.NextSequence();
  byte[] r = asn.NextInteger();
  byte[] s = asn.NextInteger();

  // Returned to caller
  byte[] p1363Signature = new byte[40];

  if (r.Length > 21 || (r.Length == 21 && r[0] != 0))
  {
    // WTF???
    // Reject - signature verification failed
  }
  else if (r.Length == 21)
  {
    // r[0] = 0
    // r[1]'s high bit *should* be set
    Array.Copy(r, 1, p1363Signature, 0, 20);
  }
  else if (r.Length == 20)
  {
    // r[0]'s high bit *should not* be set
    Array.Copy(r, 0, p1363Signature, 0, 20);
  }
  else
  {
    // fewer than 20 bytes
    int len = r.Length;
    int off = 20 - len;
    Array.Copy(r, 0, p1363Signature, off, len);
  }

  if (s.Length > 21 || (s.Length == 21 && s[0] != 0))
  {
    // WTF???
    // Reject - signature verification failed
  }
  else if (s.Length == 21)
  {
    // s[0] = 0
    // s[1]'s high bit *should* be set
    Array.Copy(s, 1, p1363Signature, 20, 20);
  }
  else if (s.Length == 20)
  {
    // s[0]'s high bit *should not* be set
    Array.Copy(s, 0, p1363Signature, 20, 20);
  }
  else
  {
    // fewer than 20 bytes
    int len = s.Length;
    int off = 40 - len;
    Array.Copy(s, 0, p1363Signature, off, len);
  }

  return p1363Signature;
}

Upvotes: 2

Related Questions