When verifying a signature using Signature.verify I receive an "Invalid encoding for signature" exception.
When verifying same signature using Azure service, the signature is verified.
I have a hash-data (SHA-256), a public key, and a signature that I'm trying to verify.
The signature was received using com.microsoft.azure.keyvault.KeyVaultClient.sign method, with signing algorithm "ES256".
This works (using ES256 algorithm) :
com.microsoft.azure.keyvault.KeyVaultClient keyVaultClient;
String keyPairIdentifier;
boolean verify(byte[] hashData, byte[] signature, JsonWebKeySignatureAlgorithm signingAlgorithm) {
com.microsoft.azure.keyvault.models.KeyVerifyResult result = keyVaultClient.verify(keyPairIdentifier, signingAlgorithm, hashData, signature);
return result.value().booleanValue();
}
This fails (certificate holds same public key that is stored in Azure keyvault):
Signature ecdsaSign = Signature.getInstance("SHA256withECDSA");
ecdsaSign.initVerify(certificate.getPublicKey());
ecdsaSign.update(hashData);
ecdsaSign.verify(signature)
Expected result - true (signature is verified)
Actual result:
java.security.SignatureException: Could not verify signature
at sun.security.ec.ECDSASignature.engineVerify(ECDSASignature.java:325)
at java.security.Signature$Delegate.engineVerify(Signature.java:1222)
at java.security.Signature.verify(Signature.java:655)
at TestKV.KeyVault.VerifyDPSignature.verifySignatureUsingCertificate(VerifyDPSignature.java:143)
at TestKV.KeyVault.VerifyDPSignature.main(VerifyDPSignature.java:104)
Caused by: java.security.SignatureException: Invalid encoding for signature
at sun.security.ec.ECDSASignature.decodeSignature(ECDSASignature.java:400)
at sun.security.ec.ECDSASignature.engineVerify(ECDSASignature.java:322)
... 4 more
Caused by: java.io.IOException: Sequence tag error
at sun.security.util.DerInputStream.getSequence(DerInputStream.java:330)
at sun.security.ec.ECDSASignature.decodeSignature (ECDSASignature.java:376)
dave_thompson_085 - thanks!
There were a few mistakes in the code you attached, the tags of the signature parts should be 0x02, not 0x30, and you didn't increase o after copying the first part.
This is the code after the changes:
byte[] r = new BigInteger(1,Arrays.copyOfRange(signature,0,32)).toByteArray();
byte[] s = new BigInteger(1,Arrays.copyOfRange(signature,32,64)).toByteArray();
byte[] der = new byte[6+r.length+s.length];
der[0] = 0x30; // Tag of signature object
der[1] = (byte)(der.length-2); // Length of signature object
int o = 2;
der[o++] = 0x02; // Tag of ASN1 Integer
der[o++] = (byte)r.length; // Length of first signature part
System.arraycopy (r,0, der,o, r.length);
o += r.length;
der[o++] = 0x02; // Tag of ASN1 Integer
der[o++] = (byte)s.length; // Length of second signature part
System.arraycopy (s,0, der,o, s.length);
After the format change I didn't get the "Sequence tag error" exception. But the verification still failed.
Thanks!
Azure does JWS which simply concatenates fixed-size I2OSP of r and s but Java JCE, like most but not all standards, uses ASN.1 DER encoding e.g. rfc3279 (caveat: there are now OIDs for ECDSA with other hashes).
To convert JWS/plain to DER, see my (cross) https://security.stackexchange.com/questions/174095/convert-ecdsa-signature-from-plain-to-der-format for a C approach, but Java makes it easier because BigInteger does half the work for you:
// byte[64] plain contains the JWS-style r,s (de-base64-ed if necessary)
byte[] r = new BigInteger(1,Arrays.copyOfRange(plain,0,32)).toByteArray();
byte[] s = new BigInteger(1,Arrays.copyOfRange(plain,32,64)).toByteArray();
byte[] der = new byte[6+r.length+s.length]; der[0] = 0x30; der[1] = der.length-2; int o = 2;
der[o++] = 2; der[o++] = (byte)r.length; System.arraycopy (r,0, der,o, r.length); o+=r.length;
der[o++] = 2; der[o++] = (byte)s.length; System.arraycopy (s,0, der,o, s.length); //o+=s.length;
2020-05 corrected and added: also Java 9 up handles this directly using algoirthm names like SHA256withECDSAinP1363format, and on all versions of Java if you add BouncyCastle it does so using names like SHA256withPLAIN-ECDSA or SHA256withCVC-ECDSA. See how to specify signature length for java.security.Signature sign method and Java ECDSAwithSHA256 signature with inconsistent length .
I just had to first decode the signature's raw bytes to Base64 in my case.
byte[] signatureBytes = Base64.getDecoder().decode(signature.getBytes());
byte[] r = new BigInteger(1,Arrays.copyOfRange(signatureBytes,0,32)).toByteArray();
byte[] s = new BigInteger(1,Arrays.copyOfRange(signatureBytes,32,64)).toByteArray();
Related
I have already posted a similar question concerning how to load RSA keys in java. See the best response of this question to fully understand the first part of my code (the method getPulicKey, I mean).
private static PublicKey getPublicKey(String publicKey)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
try (PEMParser pp = new PEMParser(new StringReader(publicKey))) {
SubjectPublicKeyInfo subjPubKeyInfo = (SubjectPublicKeyInfo) pp.readObject();
RSAKeyParameters rsa = (RSAKeyParameters) PublicKeyFactory.createKey(subjPubKeyInfo);
RSAPublicKeySpec rsaSpec = new RSAPublicKeySpec(rsa.getModulus(), rsa.getExponent());
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey myKey = kf.generatePublic(rsaSpec);
System.out.println(myKey);
return myKey;
}
}
The method verify (below) raises the following exception
Signature length not correct: got 768 but was expecting 512, in Java verify
In the following code, I decode the signature because I suppose it is Base64, but I'm not sure, sorry. I don't know wether I could show you the signature and the object. The signature is a sequence of 1024 digits and numbers. It does not end with "=". The object I have to verify is a json object in String format.
The following is the method I have written to verify a String object, given a sign and a publicKey. It calls the above method getPublicKey(...).
public static boolean verify(String object, String sign, String publicKey) throws NoSuchAlgorithmException,
InvalidKeySpecException, IOException, InvalidKeyException, SignatureException {
//object to be verified
//sign is the signature stored in the postgres DB
//publicKey is the public key stored in the postgres DB
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(getPublicKey(publicKey));
byte[] objectBytes = Base64.getEncoder().encode(object.getBytes("utf-8"));
signature.update(objectBytes);
byte[] signBytes = Base64.getDecoder().decode(sign.getBytes("utf-8"));
System.out.println(signBytes.length); //this line prints 768, with decode. 1024, otherwhise
return signature.verify(signBytes);
}
EDIT:
My workmates are using the following two nodejs methods (verifySign and createSign).
In the following nodejs code (where there are workmates' methods), I encript with "createSign" the message "fake message". The sign is the following:
5f188225c68dee2ce8de588dfaccb667710da94abb5388deabfe3ad83f7a94a72ee4a3c8c51be26c5b58cdec8c82cf8135c478ad609b7985496e201b23de6c5d03e93dcd9df7b5e2315efbfd2ff6496b0aea3b425bb99c912a16aeb5efb6cefc1e175c32aaf16af3a2baca5b54f974af0f14c853228bc06410e7ad1b2b0ecec19f5aed151389bd9ccebd5e998159d5205d81a7c7e37b502df3eb5229a5fd3492680576ebfa1e76b7c47fb757a9bfb18aa9ea0b71512ab9e1afc8e551ebf6d74a042bd447233953efbf374a3a6a210ead2019b8cc8548bb304979b4bfdc90dce644cb109bbddb75dda9df1322fd8e08ef1144e870324f34d4c826d9a4b64be0442aedc6f3d5f571d7336af212825c4e0216aa5eabab6218d685a3e73d81693149b45af5f1857c4a0e50b396d1a2ea5a3effafcc4e124fd23d0427abfe5509357936ef5e7c7ca4476d6a5ae7a26e9563923a03d0780f0d897039d4d3aa2ce49dc84b31907a50045456acb57edd11a896632969245d0f97fd88dace7eb256099bbc4eedf52b5d53b481b2aeb829101d0089903ea9c3621bcbd763962b84ad57407623b576cc6a9c3328d85e0f7dd78565cd39a6648a68dd6f4334dd3a68e48491ae655601a5c9be7673ae0d3f955431fb21f33c0178ecb9067072a6b1e360ee77a45f8e855e6c545276aefc7ae70b5c7e0f1ec0b66460575e3386f8a4bbf7fd3704
Then, I verify it with public key pk (see below).
const PASSPHRASE_KEY = "...";
const crypto = require('crypto');
const prk = "...";
const pk = "-----BEGIN RSA PUBLIC KEY-----\r\n" +
"MIICCgKCAgEA1ht0OqZpP7d/05373OE7pB7yCVGNGzkUEuCneyfOzps6iA03NbvI\r\n" +
"1ZL0Jpp/N3AW73lGdhaoa3X3JE4GsI/bsToVLQwTKmIOC4yjTvBctmFEoyhhTfxW\r\n" +
"s1UHZKl4XZ/7THbRlKHhRaTKyfDAbikkMAxNT/qutLAPjnN1qOwjb1oRq52NP6FJ\r\n" +
"KWTTikz4UeOHroX+Xthn2fJSJDlQ4YMdBbgrZVx5JcHKNuPTKRf5gI8QQKMSA9Q9\r\n" +
"QJRE5OGp7b6dG14ZmOUnUxb00Mp20LgcaGPcuWU+oFsbQaF6W4G4bdkSZRJJXhSg\r\n" +
"d4Q7mahpar94/gnztJmth0GzqTWUYyZIWNqIFoMwuOgeaiDV43zb3uLsRVpRKYYy\r\n" +
"esmzcOy/jTScVLRCD8QRyu9B2wgCkNAVztQOXPCOOa4O1LlVQWaecIs4WPhOqDhi\r\n" +
"KTBhyVkpC1TrrBkp+QMqMqWll1OyVb6k/7uV0qE/i6rHJtjo5v9bcIgYzswyx9CD\r\n" +
"9PKl2Q0L0Jg7TMG+yLDIrLfGeuSeEc4XYJzN7bJcCeiizzu5iU9dQUkrncOrq9jn\r\n" +
"Ub2pM/+A+JqIsoPK3IY/pJKqH4JYpGKhO1iPQF6iXIZT1r3ZgJUSQtzSeyYqhkla\r\n" +
"2uR2BsbPbDqebCuXm3lAsY5w+dujijcn96PKwYha1LsK5sACHuJ79AMCAwEAAQ==\r\n" +
"-----END RSA PUBLIC KEY-----\r\n" +
"";
function createSign(pvt_key, data_unsigned) {
//Create a SHA256 sign generator
const signer = crypto.createSign('SHA256');
//Update context with data to sign
signer.update(data_unsigned);
//Sign the document based to user's private key
return signer.sign({
key: pvt_key,
passphrase: PASSPHRASE_KEY
},
'hex'
);
}
function verifySign(pub_key, signed_data, signature) {
const verifier = crypto.createVerify('sha256');
//Update context with data to verify
verifier.update(signed_data);
//Verify sign with user's public key
const verified = verifier.verify(
pub_key,
signature,
'hex'
);
//Send result
return verified;
}
const phrase = "fake message";
var signMade = createSign(prk, phrase);
console.log("my signature: " + signMade);
//The signature is 5f188225c68dee2ce8de588dfaccb667710da94abb5388deabfe3ad83f7a94a72ee4a3c8c51be26c5b58cdec8c82cf8135c478ad609b7985496e201b23de6c5d03e93dcd9df7b5e2315efbfd2ff6496b0aea3b425bb99c912a16aeb5efb6cefc1e175c32aaf16af3a2baca5b54f974af0f14c853228bc06410e7ad1b2b0ecec19f5aed151389bd9ccebd5e998159d5205d81a7c7e37b502df3eb5229a5fd3492680576ebfa1e76b7c47fb757a9bfb18aa9ea0b71512ab9e1afc8e551ebf6d74a042bd447233953efbf374a3a6a210ead2019b8cc8548bb304979b4bfdc90dce644cb109bbddb75dda9df1322fd8e08ef1144e870324f34d4c826d9a4b64be0442aedc6f3d5f571d7336af212825c4e0216aa5eabab6218d685a3e73d81693149b45af5f1857c4a0e50b396d1a2ea5a3effafcc4e124fd23d0427abfe5509357936ef5e7c7ca4476d6a5ae7a26e9563923a03d0780f0d897039d4d3aa2ce49dc84b31907a50045456acb57edd11a896632969245d0f97fd88dace7eb256099bbc4eedf52b5d53b481b2aeb829101d0089903ea9c3621bcbd763962b84ad57407623b576cc6a9c3328d85e0f7dd78565cd39a6648a68dd6f4334dd3a68e48491ae655601a5c9be7673ae0d3f955431fb21f33c0178ecb9067072a6b1e360ee77a45f8e855e6c545276aefc7ae70b5c7e0f1ec0b66460575e3386f8a4bbf7fd3704
console.log("was it me to sign that?");
var res = verifySign(pk, phrase, signMade);
console.log(res);
It returns true. However, If I pass to the java method verify the following (same) parameters:
object = "fake message"
sign = 5f188225c68dee2ce8de588dfaccb667710da94abb5388deabfe3ad83f7a94a72ee4a3c8c51be26c5b58cdec8c82cf8135c478ad609b7985496e201b23de6c5d03e93dcd9df7b5e2315efbfd2ff6496b0aea3b425bb99c912a16aeb5efb6cefc1e175c32aaf16af3a2baca5b54f974af0f14c853228bc06410e7ad1b2b0ecec19f5aed151389bd9ccebd5e998159d5205d81a7c7e37b502df3eb5229a5fd3492680576ebfa1e76b7c47fb757a9bfb18aa9ea0b71512ab9e1afc8e551ebf6d74a042bd447233953efbf374a3a6a210ead2019b8cc8548bb304979b4bfdc90dce644cb109bbddb75dda9df1322fd8e08ef1144e870324f34d4c826d9a4b64be0442aedc6f3d5f571d7336af212825c4e0216aa5eabab6218d685a3e73d81693149b45af5f1857c4a0e50b396d1a2ea5a3effafcc4e124fd23d0427abfe5509357936ef5e7c7ca4476d6a5ae7a26e9563923a03d0780f0d897039d4d3aa2ce49dc84b31907a50045456acb57edd11a896632969245d0f97fd88dace7eb256099bbc4eedf52b5d53b481b2aeb829101d0089903ea9c3621bcbd763962b84ad57407623b576cc6a9c3328d85e0f7dd78565cd39a6648a68dd6f4334dd3a68e48491ae655601a5c9be7673ae0d3f955431fb21f33c0178ecb9067072a6b1e360ee77a45f8e855e6c545276aefc7ae70b5c7e0f1ec0b66460575e3386f8a4bbf7fd3704
publicKey copied from pk (see node code above)
java raises the exception:
Signature length not correct: got 768 but was expecting 512
Signature lenght not correct.
You're treating the signature as if it's base64-encoded, but it's not - it's just hex.
1024 characters represents 768 base64-encoded bytes, or 512 hex-encoded bytes.
Just decode using hex instead of base64 and it should be fine.
Hint that this is along the right lines, as well as observing that every character of your signature is a valid hex digit (which would be extremely unlikely if it's actually base64):
return signer.sign({
key: pvt_key,
passphrase: PASSPHRASE_KEY
},
'hex' // Note this use of 'hex'...
);
I sign text using OpenSSL (in C++) however my Java program doesn't always validate signed messages (only ~1 out of 5 gets verified). Interestingly https://kjur.github.io/jsrsasign/sample/sample-ecdsa.html doesn't verify any of them:
Curve name: secp256k1
Signature algorithm: SHA256withECDSA
privateKey
431313701ec60d303fa7d027d5f1579eaa57f0e870b23e3a25876e61bed2caa3
publicKey
035bcefc4a6ca257e394e82c20027db2af368474afb8917273713644f11a7cecb3
Failed:
text to sign=
pcax2727gRo8M6vf9Vjhr1JDrQ3rdPYu6xx81000pcax273z8kaV5Ugsiqz3tvWGo8Gg6sch6V4912341535867163229
signature=
3044022061dff8e39f9324b0794ec2c58abda971898f694ca980baf3c2a4045a9048b441022054a2fb8ef3d383fd7eeb31425dba440e2fd2053778d4ab3725046385c7845cff0000
Successful:
text to sign=
pcax2727gRo8M6vf9Vjhr1JDrQ3rdPYu6xx81000pcax273z8kaV5Ugsiqz3tvWGo8Gg6sch6V4912341535867122614
signature=
3046022100f200d0fb9e86a16bd46ee2dd11f1840a436d0a5c6823001a516e975a44906fcf022100d062a60611fc0f21d81fa3140741c8b6e650fff33d2c48aef69a3a40d7c7b3ca
Java
private static final String SHA256WITH_ECDSA = "SHA256withECDSA";
public static boolean isValidSignature(PublicKey pub, byte[] dataToVerify, byte[] signature) {
try {
Signature sign = Signature.getInstance(SHA256WITH_ECDSA, BouncyCastleProvider.PROVIDER_NAME);
sign.initVerify(pub);
sign.update(dataToVerify);
return sign.verify(signature);
} catch (Exception e) {
log.error("Error: " + e.getMessage());
}
return false;
}
C++
std::vector<unsigned char> utils::crypto::sign(std::string& private_key_58, std::string& message) {
auto priv_bytes = utils::base58::decode_base(private_key_58);
auto digest = utils::crypto::sha256(message);
auto key = utils::crypto::ec_new_keypair(priv_bytes);
auto signature = ECDSA_do_sign(digest.data(), digest.size(), key);
auto der_len = ECDSA_size(key);
auto der = (uint8_t*) calloc(der_len, sizeof(uint8_t));
auto der_copy = der;
i2d_ECDSA_SIG(signature, &der_copy);
std::vector<unsigned char> s (der, der+der_len);
return s;
}
std::vector<unsigned char> utils::crypto::sha256(std::string& str) {
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256_CTX sha256;
SHA256_Init(&sha256);
SHA256_Update(&sha256, str.c_str(), str.size());
SHA256_Final(hash, &sha256);
std::vector<unsigned char> data(hash, hash + SHA256_DIGEST_LENGTH);
return data;
}
EC_KEY *utils::crypto::ec_new_keypair(std::vector<unsigned char>& priv_bytes) {
EC_KEY *key = nullptr;
BIGNUM *priv = nullptr;
BN_CTX *ctx = nullptr;
const EC_GROUP *group = nullptr;
EC_POINT *pub = nullptr;
key = EC_KEY_new_by_curve_name(NID_secp256k1);
if (!key) {
std::cerr << "Can't generate curve secp256k1\n";
std::abort();
}
priv = BN_new();
BN_bin2bn(priv_bytes.data(), 32, priv);
EC_KEY_set_private_key(key, priv);
ctx = BN_CTX_new();
BN_CTX_start(ctx);
group = EC_KEY_get0_group(key);
pub = EC_POINT_new(group);
EC_POINT_mul(group, pub, priv, NULL, NULL, ctx);
EC_KEY_set_public_key(key, pub);
EC_POINT_free(pub);
BN_CTX_end(ctx);
BN_CTX_free(ctx);
BN_clear_free(priv);
return key;
}
Neardupes ECDSA signature length and how to specify signature length for java.security.Signature sign method (and more links there)
ASN.1 DER encoding is variable size for all but certain very limited data, and in particular for ECDSA (or DSA) signatures. ECDSA_size returns the maximum length possible for the given key, but each actual signature may be either that length or shorter, depending on the binary representations of the values r and s in the signature, which for your purposes can be treated essentially as random numbers.
In cases where an actual signature is shorter than ECDSA_size you still encode the entire buffer and pass it to your Java; notice the two bytes of zero (0000 in hex) at the end of your 'failed' example? A DER decoder can ignore trailing garbage, and when I test such a case on older BouncyCastle and SunEC providers it actually works okay, but it fails for me starting at BouncyCastle 1.54 -- with a rather clear exception,
java.security.SignatureException: error decoding signature bytes.
-- and SunEC starting at 8u121 with cause or exception similar to java.security.SignatureException: Invalid encoding for signature.
Many implementations have recently made DER decoding stricter, after some successful attacks on 'lax' encodings, including the secp256k1 signatures in Bitcoin -- see https://bitcoin.stackexchange.com/questions/51706/what-can-be-changed-in-signed-bitcoin-transaction and https://en.bitcoin.it/wiki/Transaction_malleability . This is mentioned in the Oracle Java 8u121 release notes item "More checks added to DER encoding parsing code" although I don't see anything similar for Bouncy.
Since secp256k1 is a Certicom/X9 'prime' (Fp) curve group, its cofactor is 1 and its order is very close to the underlying field size which in turn is very close to 256 bits which is a multiple of 8, so signatures in this group will DER-encode to the maximum length (and work) almost exactly 1/4 (25%) of the time; the rest of the time they will fail.
The official and best solution is to use the updated value in the pointer, here der_copy, output by (any) i2d* routine, to determine the length of the encoding, and use that length. If you can't handle variable length for some reason, you can transmit the whole buffer but then truncate it before passing to BouncyCastle (or SunEC) by using 2+signature[1] as the valid length -- but not if you change to a curve larger than about 480 bits; above that it is different and more complicated.
I need to verify a signature in java. I get an url with multiple params, one of them is the signature (hexadecimal format). The message signed is the SHA-256 hash of the concatenation of all the other params. I also have the certificate with the public key to be used for the check.
All the values I'm using for the test are given to me by an example which is supposed to be correct, I just create the concatenation string.
This is the code i run:
// signed message --> hash of concat
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update( concat.getBytes() );
byte[] message = digest.digest();
System.out.println("message length "+message.length); // --> 32
// signature belonging to the message --> checkValue
System.out.println("check value length " +checkValue.length()); // --> 512
byte[] sigBytes = checkValue.getBytes();
System.out.println("check value bytes "+sigBytes.length); // --> 512
// public certificate of the CA
File file3 = new File(certificatePath);
byte[] encCertRSA = new byte[(int) file3.length()];
FileInputStream fis3 = new FileInputStream(file3);
fis3.read(encCertRSA);
fis3.close();
InputStream is = new ByteArrayInputStream( encCertRSA );
CertificateFactory f = CertificateFactory.getInstance("X.509");
X509Certificate certRSA = (X509Certificate)f.generateCertificate(is);
certRSA.checkValidity();
PublicKey pubKeyRSA = certRSA.getPublicKey();
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(pubKeyRSA);
// supply the Signature object with the data for which a signature was generated --> hash of concat
sig.update(message);
boolean isValid = sig.verify( sigBytes );
System.out.println("The signature of the email verifies: " + isValid);
This is the error i get:
java.security.SignatureException: Signature length not correct: got 512 but was expecting 256
at sun.security.rsa.RSASignature.engineVerify(Unknown Source)
at java.security.Signature$Delegate.engineVerify(Unknown Source)
at java.security.Signature.verify(Unknown Source)
Am i doing anything wrong? I was expecting the signature to have a length of 256, not 512. I run a test doing a substring of the signature value to match the length of 256 and I don't get the error above, but the the sig.verify returns false.
if you look the code you wrote there is System.out.println("check value bytes "+sigBytes.length); // --> "512" and then boolean isValid = sig.verify( sigBytes ); it looks like your sigBytes variable already has 512 as length before you check
I'm trying to obtain the byte[] that represents the signature that is in a String but keep receiving "java.security.SignatureException: invalid encoding for signature"
What i'm trying to do is to send a signed string and veryfying it at server, here is my code:
Here is where i sign my string to send it via Web Service:
Signature signer = Signature.getInstance("DSA");
signer.initSign(signerKey);
signer.update(someString)
byte[] signature = signer.sign();
newToken = someString + Base64.getEncoder().encodeToString(signature);
Here is the server-side code to obtain the string and verify the whole thing with it:
byte [] sig = Base64.getDecoder().decode(stringSignature);
Signature verifier = Signature.getInstance("DSA");
verifier.initVerify(verifierPubKey);
verifier.update(token);
verified = verifier.verify(signature);
So, what's the best way to obtain a signature from a string that actually represents the signature i'm trying to verify?
Thanks.
I think this:
verified = verifier.verify(signature);
should be changed to this:
verified = verifier.verify(sig);
I'm guessing signature is the Base64 encoding or something, rather than the raw bytes you need.
I have tested a solution to verify an ECDSA signature (How can I get a PublicKey object from EC public key bytes?) that works perfect with the given data.
This is the data:
byte[] pubKey = DatatypeConverter.parseHexBinary("049a55ad1e210cd113457ccd3465b930c9e7ade5e760ef64b63142dad43a308ed08e2d85632e8ff0322d3c7fda14409eafdc4c5b8ee0882fe885c92e3789c36a7a");
byte[] message = DatatypeConverter.parseHexBinary("54686973206973206a75737420736f6d6520706f696e746c6573732064756d6d7920737472696e672e205468616e6b7320616e7977617920666f722074616b696e67207468652074696d6520746f206465636f6465206974203b2d29");
byte[] signature = DatatypeConverter.parseHexBinary("304402205fef461a4714a18a5ca6dce6d5ab8604f09f3899313a28ab430eb9860f8be9d602203c8d36446be85383af3f2e8630f40c4172543322b5e8973e03fff2309755e654");
And this is the code (which prints true):
private static boolean isValidSignature(byte[] pubKey, byte[] message,byte[] signature) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException, InvalidKeySpecException {
Signature ecdsaVerify = Signature.getInstance("SHA256withECDSA", new BouncyCastleProvider());
ecdsaVerify.initVerify(getPublicKeyFromBytes(pubKey));
ecdsaVerify.update(message);
return ecdsaVerify.verify(signature);
}
private static PublicKey getPublicKeyFromBytes(byte[] pubKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("prime256v1");
KeyFactory kf = KeyFactory.getInstance("ECDSA", new BouncyCastleProvider());
ECNamedCurveSpec params = new ECNamedCurveSpec("prime256v1", spec.getCurve(), spec.getG(), spec.getN());
ECPoint point = ECPointUtil.decodePoint(params.getCurve(), pubKey);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
ECPublicKey pk = (ECPublicKey) kf.generatePublic(pubKeySpec);
return pk;
}
public static void main (String[] args) {
System.out.println(isValidSignature(pubKey, message, signature));
}
My problem comes when I change the signature and data to an example input from an already implemented system:
final static byte[] pubKey = DatatypeConverter.parseHexBinary("0447303876C6FED5550DF3EE1136989FCD87293D54A5D8E2F2F6D7FBE9A81089B889A5917443AF33E696178CEF4C9D6A4288B2745B29AF6C8BCAD1348F78EB9F9B");
final static byte[] message = DatatypeConverter.parseHexBinary("02158001f53611a06e2d1a270000013ed9305dc2780524015110500000002d0100140092569202017aa00c5dd30000000000000000000000000000000007d1000001020001b20788b80059f48d95cdefc8c6000200200030d41e0000012016840310a50733a9870fffd0430100");
final static byte[] signature = DatatypeConverter.parseHexBinary("531F8918FF250132959B01F7F56FDFD9E6CA3EC2144E12A6DA37C281489A3D96");
New data outputs this error:
java.security.SignatureException: error decoding signature bytes.
at org.bouncycastle.jcajce.provider.asymmetric.util.DSABase.engineVerify(Unknown Source)
at java.security.Signature$Delegate.engineVerify(Signature.java:1178)
at java.security.Signature.verify(Signature.java:612)
at its.sec.exec.TestProgram.isValidSignature(TestProgram.java:168)
at its.sec.exec.TestProgram.execution(TestProgram.java:101)
at its.sec.exec.TestProgram.main(TestProgram.java:55)
I assume the problem is about the signature that comes with the secured message because:
The key pair is the same length and format that the example. And are correct since it comes from the certificate that signs the message.
The message itself (payload) shouldn't affect the security process.
Last thing worth mention is that my documentation says that the signature must be preceded by a field called "R" which "contains the x coordinate of the elliptic curve point resulting from multiplying the generator element by the ephemeral private key" and its length must be the same as the signature (32 byte).
Can someone point me out what I'm missing here?
EDIT: Solution
As Peter Dettman pointed in his answer, the signature was not correctly formatted (also content was incorrect too) in order to be computed by the verify() method. Here is a good explanation that mainly says that:
When encoded in DER, this (signature) becomes the following sequence of bytes:
0x30 b1 0x02 b2 (vr) 0x02 b3 (vs)
where:
b1 is a single byte value, equal to the length, in bytes, of the remaining list of bytes (from the first 0x02 to the end of the encoding);
b2 is a single byte value, equal to the length, in bytes, of (vr);
b3 is a single byte value, equal to the length, in bytes, of (vs);
(vr) is the signed big-endian encoding of the value "r", of minimal length;
(vs) is the signed big-endian encoding of the value "s", of minimal length.
Applying that change, signature grows to 70 bytes and the execution outputs no error.
The expected ECDSA signature format that the BC (and other provider) implementations work with is a DER-encoded ASN.1 sequence containing two integer values r and s. This signature format has been specified in ANSI X9.62. This is the format in the first set of data you give (note that signature is a total of at least 70 bytes).
In the second set of data, signature is only 32 bytes, and is not an ASN.1 sequence at all. I would guess that this value is only the s value, and it is missing the r value and the ASN.1 INTEGER encoding for them both, instead encoding the values as a unsigned big integer value with the same size as the key.
this is a sample code to write r and s in ASN1 DER encoded format
// construct the ASN1Sequence with r and s
ByteArrayOutputStream outs = new ByteArrayOutputStream();
byte radd = (byte)(((signed[0] & 0x80) > 0) ? 1 : 0);
byte sadd = (byte)(((signed[32] & 0x80) > 0) ? 1 : 0);
byte length = (byte)(0x44 + radd + sadd);
outs.write(0x30);
outs.write(length); // length 68 bytes +
outs.write(0x02); // ASN1Integer
outs.write(0x20 + radd); // length 32 bytes
if(radd > 0)
outs.write(0x00); // positive val
outs.write(signed, 0, 32);
outs.write(0x02); // ASN1Integer
outs.write(0x20 + sadd); // length 32 bytes
if(sadd > 0)
outs.write(0x00); // positive val
outs.write(signed, 32, 32);
signed = outs.toByteArray();