Data signed on iOS can't be verified in Java

2020-08-24 02:55发布

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?

4条回答
走好不送
2楼-- · 2020-08-24 03:30

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

查看更多
倾城 Initia
3楼-- · 2020-08-24 03:44

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.

查看更多
淡お忘
4楼-- · 2020-08-24 03:47

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);
查看更多
时光不老,我们不散
5楼-- · 2020-08-24 03:49

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.

查看更多
登录 后发表回答