Fixed length 64 Bytes EC P-256 Signature with JCE
Asked Answered
T

2

6

I need a fixed length 64 Byte ECDSA signature with the NIST P-256 Curve.

The implementation hast to use JCE.

The following code sample can generate a signature and verify it.

Provider provSign = new SunEC();
Provider provVerify = new SunEC();


    // generate EC key
    KeyPairGenerator kg = KeyPairGenerator.getInstance("EC", provSign);
    ECGenParameterSpec ecParam = new ECGenParameterSpec("secp256r1");
    kg.initialize(ecParam);      
    KeyPair keyPair = kg.generateKeyPair(); 
    PrivateKey privateKey = keyPair.getPrivate();      
    PublicKey publicKey = keyPair.getPublic();

    try
    {
      // export public key                  
      KeyFactory kf = KeyFactory.getInstance("EC", provSign);
      ECPublicKeySpec publicKeySpec = kf.getKeySpec(keyPair.getPublic(), ECPublicKeySpec.class);

      // import public key into other provider
      kf = KeyFactory.getInstance("EC", provVerify);
      publicKey = (PublicKey)kf.generatePublic(publicKeySpec);      
    }
    catch (InvalidKeySpecException ex)
    {                       
      ex.printStackTrace();
    }


      // do test        
      Signature sig = Signature.getInstance("SHA256withECDSA", provSign);
      Signature ver = Signature.getInstance("SHA256withECDSA", provVerify);

      byte[] data = new byte[64];

      // sign
      sig.initSign(privateKey);
      sig.update(data);
      byte [] sign = sig.sign();

      // Working Signature verification
      ver.initVerify(publicKey);
      ver.update(data);
      if (ver.verify(sign) == false)
      {
        throw new Exception("Signature Verification failed");
      }

The problem is that the sign is somehow encoded (I think in DER Format) and is between 70 and 72 Bytes long but I need a 64 Byte (unencoded/raw) signature.

What I have tried: Convert to fixed length 64 Byte Signature

      DerInputStream derInputStream = new DerInputStream(sign);
      DerValue[] values = derInputStream.getSequence(2);
      byte[] random = values[0].getPositiveBigInteger().toByteArray();
      byte[] signature = values[1].getPositiveBigInteger().toByteArray();


      // r and s each occupy half the array
      // Remove padding bytes
      byte[] tokenSignature = new byte[64];
      System.arraycopy(random, random.length > 32 ? 1 : 0, tokenSignature, random.length < 32 ? 1 : 0,
              random.length > 32 ? 32 : random.length);
      System.arraycopy(signature, signature.length > 32 ? 1 : 0, tokenSignature, signature.length < 32 ? 33 : 32,
              signature.length > 32 ? 32 : signature.length);

      System.out.println("Full Signature length: "+tokenSignature.length+" r length: "+random.length+" s length"+signature.length);

How to check the 64 Bytes tokenSignature now??? I don't know how to convert the 64 Byte tokenSignature back to the right format

      ver.initVerify(publicKey);
      ver.update(data);
      if (ver.verify(???) == false)
      {
        throw new Exception("Signature Verification failed");
      }

I have achieved the 64 Byte signature verification with BouncyCastle ECDSASigner. But I can't use ECDSASigner because it is not extending SignatureSpi and therefore not working with JCE complient crypto server.

Trilbi answered 3/12, 2015 at 10:37 Comment(6)
Put two integers in a sequence using DEROutputStream?Rayerayfield
Thanks Maarten now I got it. I have already tried it with DEROutputStream and failed at the first time.Trilbi
@MaartenBodewes you can add your comment as Answer :-)Trilbi
I'll try today, but if it isn't forthcoming you might want to add your code as answer.Rayerayfield
@MaartenBodewes okay I have added the answerTrilbi
DerInputStream and DerValue were removed from bouncycastle. Do you have any idea how can I convert the signature to fixed lenght using ASN1InputStream?Creon
T
3

I got it working now thanks to @MaartenBodewes

//How to Check Signature
byte[] r = Arrays.copyOfRange(tokenSignature, 0,tokenSignature.length/2);
byte[] s = Arrays.copyOfRange(tokenSignature, tokenSignature.length/2,tokenSignature.length);

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DEROutputStream derOutputStream = new DEROutputStream(byteArrayOutputStream);
ASN1EncodableVector v=new ASN1EncodableVector();
v.add(new ASN1Integer(new BigInteger(1,r)));
v.add(new ASN1Integer(new BigInteger(1,s)));
derOutputStream.writeObject(new DERSequence(v));
byte[] derSignature = byteArrayOutputStream.toByteArray();

ver.update(data);
if (ver.verify(derSignature) == false)
{
  throw new Exception("Signature Verification failed");
}
Trilbi answered 4/12, 2015 at 10:1 Comment(5)
Yep that looks like my code. I would not use literals for signature size (in copyOfRange) of course but the solution should be correct otherwise.Rayerayfield
Quick question: does the constructor of ASN1Integer always result in a positive integer?Rayerayfield
I also wanted to use new BigInteger(1,r) first but the signature was always correct. (tested in a for Loop 100 times)Trilbi
Better safe than sorry. Chances of one in a million crop up 9 times out of 10.Rayerayfield
I also printed the first byte of r and it works for both negative and positive numbers. But I also think it is better and changed the answer therefore.Trilbi
N
3

It works using SHA256withPLAIN-ECDSA instead of SHA256withECDSA

Please have a look at the following

Java ECDSAwithSHA256 signature with inconsistent length

Neau answered 24/6, 2020 at 8:54 Comment(1)
Note that SHA256withPLAIN-ECDSA is not available with the AndroidKeyStore provider on Android. And one must use this Provider to make sure the generated keys are unexportable and protected behind the secure storage of the phone (using biometrics, etc). Instead, generate DER / ASN1 encoded signature with SHA256withECDSA and then use the BouncyCastle library to format it to P1363 / Plain: https://mcmap.net/q/1464126/-der-decode-ecdsa-signature-in-javaInfantilism

© 2022 - 2025 — McMap. All rights reserved.