12

I have some data that I'm signing on iOS with SecKeyRawSign using Elliptic Curve private key. However, verifying that data in Java using Signature.verify() returns false

The data is a random 64 bit integer, split into bytes like so

uint64_t nonce = (some 64 bit integer)
NSData *nonceData = [NSData dataWithBytes: &nonce length: sizeof(nonce)];

From that data I'm creating a SHA256 digest

int digestLength = CC_SHA256_DIGEST_LENGTH;
uint8_t *digest = malloc(digestLength);
CC_SHA256(nonceData.bytes, (CC_LONG)nonceData.length, digest);
NSData *digestData = [NSData dataWithBytes:digest length:digestLength];

and then signing it with private key

size_t signedBufferSize = kMaxCipherBufferSize;
uint8_t *signedBuffer = malloc(kMaxCipherBufferSize);

OSStatus status = SecKeyRawSign(privateKeyRef,
                                kSecPaddingPKCS1SHA256,
                                (const uint8_t *)digestData.bytes,
                                digestData.length,
                                &signedBuffer[0],
                                &signedBufferSize);

NSData *signedData = nil;
if (status == errSecSuccess) {
    signedData = [NSData dataWithBytes:signedBuffer length:signedBufferSize];
}

Everything appears to work fine.

Then, in Java server, I'm trying to verify that signed data

PublicKey publicKey = (a public key sent from iOS, X509 encoded)

Long nonce = (64 bit integer sent from iOS)
String signedNonce = (base64 encoded signed data)

ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
buffer.putLong(nonce);
byte[] nonceBytes = buffer.array();
byte[] signedNonceBytes = Base64.getDecoder().decode(signedNonce.getBytes());

Signature signer = Signature.getInstance( "SHA256withECDSA" );
signer.initVerify( publicKey );
signer.update( nonceBytes );
Boolean isVerified = signer.verify( signedNonceBytes );

At this point, signer.verify() returns false

I also tried to sign plain data, instead of SHA256 digest, but that doesn't work either.

What am I missing? Am I signing the data correctly? Am I using correct padding? Is there something else to be done with data to be able to verify it with SHA256withECDSA algorithm?

mag_zbc
  • 6,801
  • 14
  • 40
  • 62
  • Can you encrypt the same Nonce in Java with same algo and see if encrypted versions are coming different? So basically you encrypt at both ends and see if the encrypted data is same or not – Tarun Lalwani May 23 '18 at 12:36
  • I would recommend to check that the ObjC side is correct: (1) is the size of the generated signature the expected size? and (2) can you verify the signature in ObjC code? And a wild guess: may this be an endianness problem? I.e `[NSData dataWithBytes: &nonce...` accesses the actual raw memory, while the Java side might see the nonce in a more generic "network" endianness. Therefore try whether a nonce of 0 works. – mschmidt May 23 '18 at 21:23
  • 2
    One of the errors is byte ordering. iOS is little endian. The way you create `nonceData`, this order is retained. However on the Java side, `ByteBuffer` defaults to big endian. So you need to add: `buffer.order(ByteOrder.LITTLE_ENDIAN);` – Codo May 29 '18 at 20:05
  • @Codo this was it. Please write is as an answer so I can accept it. – mag_zbc Jun 01 '18 at 13:15
  • Ok, I've added it as an answer. – Codo Jun 01 '18 at 14:17

4 Answers4

2

The byte ordering does not match:

  • iOS is little endian. The way you create nonceData, this order is retained.
  • On the Java side, ByteBuffer defaults to big endian, independent of the underlying operating system / hardware.

So you need to change the byte order:

ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putLong(nonce);
Codo
  • 75,595
  • 17
  • 168
  • 206
0

I'm a java guy, so I can't say anything about the iOS side, but a quick check of the java side can be done using the commented assumptions:

// Generate a new random EC keypair for testing
KeyPair keys = KeyPairGenerator.getInstance("EC").generateKeyPair();
PrivateKey privateKey = keys.getPrivate();
PublicKey publicKey = keys.getPublic();

// Generate a Random nonce to test with
byte[] nonceBytes = new byte[8]; // (some 64 bit integer)
new Random(System.nanoTime()).nextBytes(nonceBytes);

// sign     
Signature sign = Signature.getInstance("SHA256withECDSA");
sign.initSign(privateKey);
sign.update(nonceBytes);
byte[] signature = sign.sign();

//verify
Signature verify = Signature.getInstance("SHA256withECDSA");
verify.initVerify(publicKey);
verify.update(nonceBytes);
Boolean isVerified = verify.verify(signature);

// print results
System.out.println("nonce used  ::" + Base64.getEncoder().encodeToString(nonceBytes));
System.out.println("Signed nonce ::" + Base64.getEncoder().encodeToString(signature));
System.out.println("nonce used ::" + isVerified);

As you'd expect returns, the code above will always return that the signature is verified. Check your assumptions are accurate and validate the keys being used are correct on both sides.

Jason Warner
  • 2,469
  • 1
  • 11
  • 15
0

I can advice you to use some Crypto Library which is available for both iOS and JAVA sides (f.e.:https://github.com/VirgilSecurity/virgil-crypto). This will ensure that the algorithm and block types (etc.) are the same in both cases and you won't need to worry about it anymore. I believe you will finds many crypto libraries on a GitHub.

T. Pasichnyk
  • 702
  • 3
  • 16
0

In getBytes() you could specify the encoding technique using java.nio.charset.StandardCharsets. and do the same with the decoder.

https://docs.oracle.com/javase/7/docs/api/java/nio/charset/StandardCharsets.html

Saranya Vs
  • 3
  • 1
  • 6