I am having issues signing an rsa signature. I have a signature that has been encrypted with a private key. I have an issue when trying to validate it with the public key however. I get the following exception:
java.security.SignatureException: Signature length not correct: got 336 but was expecting 128
at sun.security.rsa.RSASignature.engineVerify(RSASignature.java:189)
at java.security.Signature$Delegate.engineVerify(Signature.java:1219)
at java.security.Signature.verify(Signature.java:652)
at XmlReader.main(XmlReader.java:65)
I have retrieved my signature and public key the following way:
BigInteger modulus = new BigInteger(Base64.getDecoder().decode(publicKeyString));
BigInteger exponent = new BigInteger(Base64.getDecoder().decode("AQAB"));
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, exponent);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(keySpec);
byte[] sigToVerify = Base64.getDecoder().decode(signatureString);
Signature sig = Signature.getInstance("MD5WithRSA");
sig.initVerify(pubKey);
boolean verifies = sig.verify(sigToVerify);
The application fails at the last line. Any thoughts as to where this exception is caused?
UPDATE:
Added data for signature to be verified:
String data = "...." //hidden since sensitive data
byte[] dataBytes = Base64.getEncoder().encode(data.getBytes());
dataBytes = Base64.getDecoder().decode(dataBytes);
Before calling sig.verify(sigToVerify) you should call
sig.update(data);
passing the data you're verifying signature for.
And make sure that calling verify in your argument you have signature bytes only.
Well, the signature size is obviously not correct. This means that at least the signature is not formatted correctly; an RSA signature by itself is always the size of the modulus in bytes (identical to the key size, in your case a short 1024 bit key).
algrid is right that usually you'd have to update the signature first. So it would be logical that the input of the signature is prefixed with the data that was signed. In that case the last 128 bytes are probably the signature, and the bytes before that are the data.
It is also possible that the signature is not a "raw" signature but a formatted signature, e.g. using the PKCS#7 / CMS syntax or PGP syntax. In that case you would need an library to decode it.
Note that you cannot verify a signature without the data that was signed, or at least the hash over the data. You can only check the correctness of the signature in that case (by validating the padding performed before modular exponentiation for RSA, in case we need to go into details).
Related
I have this JAVA code & I need to write the same thing in php:
public static String signMsg(String msg, String privateKey)
throws Exception {
byte[] bytes = Base64.getDecoder().decode(privateKey);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
Signature ps = Signature.getInstance("SHA256withRSA");
ps.initSign(kf.generatePrivate(spec));
ps.update(msg.getBytes("UTF-8"));
byte[] sigBytes = ps.sign();
return Base64.getEncoder().encodeToString(sigBytes);
}
Any idea how to do that?
Thanks in advance :)
Regarding your first approach: A signature is created with the private key. The public key is used to verify the signature. Regarding your second approach: A HMAC is not the same as a signature.
The Java code loads a private key in PKCS8 format, PEM encoded without header and footer. In the PHP code the key can be read in the same format and encoding. Alternatively, the key can be loaded in PKCS#1 format. Regarding the encoding, a PEM or DER encoded key is also accepted.
Additionally, it must be specified which algorithm is used for signing. The Java code applies RSA with PKCS#1 v1.5 padding and SHA-256 as digest. Furthermore, the generated signature is Base64 encoded. In order for the PHP code to provide the same RSA signature, the same parameters must be used.
Note that signing does not necessarily generate the same signature using the same message and the same key. It depends on the algorithm. However, in the case of RSA with PKCS#1 v1.5 padding, always the same signature is generated (deterministic). For PSS, on the other hand, a different signature is generated each time (probabilistic).
The following PHP code uses the PHPSECLIB and generates the same signature as the Java code:
use phpseclib3\Crypt\RSA;
$privateKey= 'MIIEvg...';
$signatureB64 = base64_encode( // Base64 encode signature
RSA::load($privateKey)-> // Choose RSA, load private PKCS8 key
withHash('sha256')-> // Choose SHA-256 as digest
withPadding(RSA::SIGNATURE_PKCS1)-> // Choose PKCS#1 v1.5 padding
sign('The quick brown fox jumps over the lazy dog') // Sign messsage
);
print($signatureB64);
I want to sign a message using RSA private key. This message will be verified by someone else maybe using another programming language other than Java.
I will use the java.security.Signature class to sign the messsage.
The code will be :
Signature privateSignature = Signature.getInstance("SHA256withRSA");
privateSignature.initSign(key); //RSA private key
privateSignature.update(text.getBytes("UTF8")); //text is the clear text string
byte[] signature = privateSignature.sign();
return Base64.getEncoder().encodeToString(signature);
When I check the result and used the publickey to decrypt the signature, I found that padding bytes are added before the SHA256 digest.
My Question is : is the resulted signature generated in a standard way so that it can be verified with the public key in widely used programming languages or is it java specific?
I need to sign data using Java with the SHA-256 and EMSA-PKCS1-V1_5 encoding and RSA private key.
Also I need to use padding type 1 in EMSA-PKCS1-V1_5 which means that the data before signing should be appended by zeros (not random data, as in padding type 2).
The output of the signature on the same data should be the same.
What is equivalent in Java to this mechanism?
I was trying to sign using SHA256withRSA but the output on the same data is always different.
EDIT:
I am using the following code (data, sigAlg, and cert is always the same, so I assume that the signature should be always the same):
CMSSignedDataStreamGenerator generator = new CMSSignedDataStreamGenerator();
String sigAlg = "SHA256withRSA";
ContentSigner contentSigner = new JcaContentSignerBuilder(sigAlg).setProvider("BC").build(getPrivateKey());
generator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()).build(contentSigner, cert));
generator.addCertificates(new JcaCertStore(certs));
OutputStream out = generator.open(responseOutputStream);
out.write(data);
out.close();
At the moment in C# I'm signing a challenge like this:
RSACryptoServiceProvider rsa;
RSAPKCS1SignatureFormatter RSAFormatter = new RSAPKCS1SignatureFormatter(rsa);
RSAFormatter.SetHashAlgorithm("SHA1");
byte[] SignedHash = RSAFormatter.CreateSignature(paramDataToSign);
Then I give the SignedHash to Windows, it accepts it and everything is OK. But I need to move this part to Android and there's the problem, that I just can't get the same signed hash value.
In Android I tried to make the signed hash but they differ from the one generated in C#.
Signature signer = Signature.getInstance("SHA1withRSA", "BC");
signer.initSign(privateKey);
signer.update(paramDataToSign);
signer.sign();
In C# - using the following piece of code - I get the same result as in Android, but it is not an option cause then Windows does not accept the signed hash.
ISigner signer = SignerUtilities.GetSigner("SHA1withRSA");
signer.Init(true, privateKey);
signer.BlockUpdate(paramDataToSign, 0, paramDataToSign.Length);
signer.GenerateSignature();
Here's written that C# PKCS1SignatureFormatter and Java Signature should give the same result, but they do not. http://www.jensign.com/JavaScience/dotnet/VerifySig/
What could be the problem?
Here are the base 64 (WebSafe) values that I get:
Challenge = zHyz12Tk4m151nssYIBWqBCAxhQ
RSAPKCS1SignatureFormatter SignedHash = kmu39keplCAV4Qnu22wdprLz4nGSsrVtHbxQ5YMUG7p-0YwReCG4ROIlFvYs4CGfjCiAGFPw4PLrLx7mrlAA6iuhJMkgm_PMTW9alQYTH612hLEUP4EmK0M2kw8CveLcjI3HA08z8bByllIzRyAlM8bcR438vw2uhx_CbgvOOHn8vwBPnvWbFqpi2doYoq2xEuFBRe7eBPrxbMRqEd3ExdQ9c9rYT4ivOJ4pbioyi6D5i5_1crvGwM6nQanMZCmooRYJO65NP3B4wWnvQZpJLRD0U08wWcvyGBFWp188ZovDjnkTQZku6lzmwGXfqQwtBz9uNvLcTbp7cVyt5EyQxw
Signature and ISigner SignedHash = Vt-b5QfGPnSPpZuIB8-H4N1K5hQXpImS4e8k56_HruDSqy3DLsz96QKUrccshjr1z9nTK3Mwvd5yPdyTJOqSUcDQqxV46LPhWQNsubqKxAz97ePpeslIH1gHdnzkh46ixsWqgDrhR7egQtDkU8PPsph1qahCxaVkRYspQBV0jPZ-LK4EjoGGnuWTCihVKjruXJZ2VY8yZ9QRAsHVptr0Nv-mldO2MFK-oEVbtVbHqUPf5So8im3oRSm68OqY4g56bCdFNSbhcFBjrZ1QPjnxiIk43-_5tevafqoOB2D_E_mQHCJwmRg3MrNij6IdAdloCejnhCWzgMHdcG1Ug_Qmig
EDIT:
So the simplest solution is using Bouncy Castle API:
AsymmetricBlockCipher rsaEngine = new PKCS1Encoding(new RSABlindedEngine());
rsaEngine.init(true, privateKey);
DigestInfo dInfo = new DigestInfo(new AlgorithmIdentifier(X509ObjectIdentifiers.id_SHA1, DERNull.INSTANCE), paramDataToSign);
byte[] digestInfo = dInfo.getEncoded(ASN1Encoding.DER);
rsaEngine.processBlock(digestInfo, 0, digestInfo.length);
The problem is that RSAFormatter.CreateSignature(paramDataToSign); passes the hash value, while signer.update(paramDataToSign); passes the data before it is hashed. So it is likely that you have to remove a MessageDigest calculation for your Java code for this to work.
Alternatively, if you only have the hash value, you may have a look into the Bouncy Castle lightweight API to find a method that accepts a value that is pre-hashed. This can probably be performed using new RSADigestSigner(new StaticDigest(paramDataToSign, "SHA-1")).generateSignature().
Problem is that StaticDigest does not exist, so you'll have to comment here if you really require it. Alternative, mirror the implementation of RSADigestSigner but substitute a pre-calculated hash.
I have a key pair already, public and private. How do I actually use the java.security.Signature to do verification of a string I signed with one of the keys?
Edit:
I have both the keys as Strings. The verify method, it is actually
verify(byte[] signature)
The javadoc says:
verify(byte[] signature) Indicates whether the given signature can be
verified using the public key or a certificate of the signer.
How would I make that signature recognize which public/private key to use for that verifying, before I call the verify method? In other words, how do I turn my string keys into key objects that would get accepted by signature?
Use KeyFactory to translate key specifications to objects.
Call Signature.getInstance(algName) to get a signature instance.
Use Signature's initVerify method to associate a key for signature verification.
Use update to feed the Signature bytes.
Finally, call verify.
Profit
From the KeyFactory javadoc:
The following is an example of how to use a key factory in order to instantiate a DSA public key from its encoding. Assume Alice has received a digital signature from Bob. Bob also sent her his public key (in encoded format) to verify his signature. Alice then performs the following actions:
X509EncodedKeySpec bobPubKeySpec = new X509EncodedKeySpec(bobEncodedPubKey);
KeyFactory keyFactory = KeyFactory.getInstance("DSA");
PublicKey bobPubKey = keyFactory.generatePublic(bobPubKeySpec);
Signature sig = Signature.getInstance("DSA");
sig.initVerify(bobPubKey);
sig.update(data);
sig.verify(signature);