I was trying to validate an XML signature.
The validation according to this tutorial works fine.
But I also tried to a second approach. To verify it with the verify method of the Signature class
I extracted the signature and the certificate from the xml file, and I did the following:
public static boolean checkSignedFile(byte[] data, byte[] sigToVerify,
byte[] cert, String algorithm) throws CertificateException,
NoSuchAlgorithmException, InvalidKeyException, SignatureException {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate c = (Certificate) cf
.generateCertificate(new ByteArrayInputStream(cert));
PublicKey pk = c.getPublicKey();
Signature sig;
boolean verifies = false;
sig = Signature.getInstance(algorithm);
sig.initVerify(pk);
sig.update(data);
verifies = sig.verify(sigToVerify);
return verifies;
}
the result was false. The signature did not verify. What could be the reason for that?
You can't verify XMLDsig like this. It wouldn't work. The signature is not calculated over the raw XML. It has to go through canonicalization, digest etc.
What do you use for data[]? To get it right, you almost have to rewrite the XMLDsig library.
If data[] is the content of the signed XML file, what is sigToVerify?
XMLSig creates a Signature-Element (SignedInfo) that contains the digest of each Element to be signed and meta-information like used canonicalization/transformation algorithms. Then the digest of this SignedInfo-Elemnt is calculated and signed.
Hence, if sigToVerify is the signature created by a XMLSignature implementation it must not be equal to the signature of the complete XML file.
Here is a more complete explanation. And if your interested, take a look at the specification.
Related
Innocently, I thought "SHA1withRSA algorithm" was simply operating the plainText with "SHA1", and use RSA/pkcs1padding to encrypt the result of "SHA1"。However, I found I was wrong until I wrote some java code to test what I thought.
I use RSA publickey to decrypt the signature which I use the corresponding privatekey to sign with "SHA1withRSA algorithm" . But I found the result is not equal to "SHA1(plainText)", below is my java code:
String plaintext= "123456";
Signature signature=Signature.getInstance("SHA1withRSA",new BouncyCastleProvider());
signature.initSign(pemPrivatekey);
signature.update(plaintext.getBytes());
byte[] sign = signature.sign();
//RSA decode
byte[] bytes = RsaCipher.decryptByRsa(sign, pemPublickey);
String rsaDecodeHex=Hex.toHexString(bytes);
System.out.println(rsaDecodeHex.toLowerCase());
String sha1Hex = Hash.getSha1(plaintext.getBytes());
System.out.println(sha1Hex);
//rsaDecodeHex!=sha1Hex
Easy to find that rsaDecodeHex!=sha1Hex, where
rsaDecodeHex=3021300906052b0e03021a050004147c4a8d09ca3762af61e59520943dc26494f8941b
and
sha1Hex=7c4a8d09ca3762af61e59520943dc26494f8941b 。
So, What's the detail in "SHA1withRSA" ?
The digital signature algorithm defined in PCKS#1 v15 makes a RSA encryption on digest algorithm identifier and the digest of the message encoded in ASN.1
signature =
RSA_Encryption(
ASN.1(DigestAlgorithmIdentifier + SHA1(message) ))
See (RFC2313)
10.1 Signature process
The signature process consists of four steps: message digesting, data
encoding, RSA encryption, and octet-string-to-bit-string conversion.
The input to the signature process shall be an octet string M, the
message; and a signer's private key. The output from the signature
process shall be a bit string S, the signature.
So your rsaDecodeHex contains the algorithm identifier and the SHA1 digest of plainText
I have a requirement wherein I have to generate a URL where one of the parameter is signature and signature has to be generated using below requirement in a Java Application:
The other 4 URL parameter values should be hashed (in the order specified below) using MD5 and sign using the private certificate. (The signature will be DER-encoded PKCS #1 block as defined in RSA Laboratory's Public Key Cryptography Standards Note #1.) The resulting digest should be converted to ASCII character set using base64 and then encoded to comply with HTTP URL character set limitations.
Order Parameter
1 [queryparameter1]
2.. [queryparameter …] *
3 Expiration
The final url should look something like
https://<ServerName>:<Port>/imageRet/pod?ID=123456789&build=XHB&date=201102151326&expiration=20110218155523&signature=H767dhghjKJ#23mxi
I have never worked on Cryptography before and hence don't know how to start.
Can somebody help how can this be achived.
This will be the signature code
Signature sig = Signature.getInstance("MD5withRSA");
sig.initSign(privateKey);
sig.update(canonicalize(params));
byte signature[] = sig.sign();
String signatureB64UrlEncoded = Base64.getUrlEncoder().encodeToString(signature);
Where canonicalize(params) means converting the String parameters of the url to byte[] the way your service specified. You have not given details. This step is not trivial at all because equivalent urls may generate different signatures.
For example
q=hello%20world --> Qazz_tVB-guYai5oW0Eef6BbVP ...
q=hello world --> JJWDEPMQDmffcsjR0dP3vnrkFT ...
An example implementation, but surely not valid...
//Convert params[] to byte[] converting each String to byte with default charset and concatenating results
public byte[] canonicalize(String params[] ) throws IOException{
final ByteArrayOutputStream out = new ByteArrayOutputStream();
for (String param: params){
out.write(param.getBytes());
}
return out.toByteArray();
}
Take a look at Amazon AWS to see an example of how canonicalize a URL
If you finally decide to use a more secure algorithm, simply replace MD5withRSA with for example SHA256withRSA
I get Error "The document has been altered or corrupted since the signature was applied" when signature was applied to pdf using itext.
Digitally signed pdf is generated but the green check mark is not coming. What has to be done to get that green check mark.
Right now it says signature is INVALID.
I used following link for reference
http://itextpdf.com/examples/iia.php?id=222
I use following code to apply signature using iText.
String path = "resources/examplestore";
String keystore_password = "password";
String key_password = "password";
String alias = "signFiles";
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(new FileInputStream(path), keystore_password.toCharArray());
PrivateKey pk = (PrivateKey) ks.getKey(alias, key_password.toCharArray());
Certificate[] chain = ks.getCertificateChain(alias);
String path = "resources/examplestore";
String keystore_password = "password";
String key_password = "password";
String alias = "signFiles";
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(new FileInputStream(path), keystore_password.toCharArray());
PrivateKey pk = (PrivateKey) ks.getKey(alias, key_password.toCharArray());
Certificate[] chain = ks.getCertificateChain(alias);
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0', null, true);
PdfSignatureAppearance appearance = stamper
.getSignatureAppearance();
appearance.setReason("I'm approving this.");
appearance.setLocation("Foobar");
appearance.setVisibleSignature(new Rectangle(160, 732, 232, 780), 1, "second");
ExternalSignature es = new PrivateKeySignature(pk, "SHA-256", "BC");
ExternalDigest digest = new BouncyCastleDigest();
MakeSignature.signDetached(appearance, digest, es, chain, null, null, null, 0, CryptoStandard.CMS);
I also Verify the code after this.
I am not completely sure in this answer as I hardly ever had to deal with DSA signature, generally I had to deal with RSA or ECDSA signatures.
The HASH value is ok.
The first assumption upon seeing a "The document has been altered or corrupted since the signature was applied" message is that the actual document hash differs from the document hash included in the signature. (Altering the document would result in such a difference.) In case of your document, though, the HASH value in the signature's signed attribute messageDigest is correct.
Thus, the problem is in the signature itself.
A quite obvious peculiarity is the used signature algorithm:
1071 06 7: OBJECT IDENTIFIER dsa (1 2 840 10040 4 1)
: (ANSI X9.57 algorithm)
<05 00>
1080 05 0: NULL
: }
This is peculiar because the signature algorithm needs to include a HASH algorithm to allow for verification, e.g.
The algorithm identifier for DSA with SHA-1 signature values is:
id-dsa-with-sha1 OBJECT IDENTIFIER ::= { iso(1) member-body(2)
us(840) x9-57 (10040) x9cm(4) 3 }
(RFC 3370 section 3.1 DSA)
or
When SHA-224 is used, the OID is:
id-dsa-with-sha224 OBJECT IDENTIFIER ::= { joint-iso-ccitt(2)
country(16) us(840) organization(1) gov(101) csor(3)
algorithms(4) id-dsa-with-sha2(3) 1 }.
When SHA-256 is used, the OID is:
id-dsa-with-sha256 OBJECT IDENTIFIER ::= { joint-iso-ccitt(2)
country(16) us(840) organization(1) gov(101) csor(3)
algorithms(4) id-dsa-with-sha2(3) 2 }.
(RFC 5758 section 3.1 DSA Signature Algorithm)
In old SMIME specs using id-dsa instead of id-dsa-with-XXX was interpreted as id-dsa-with-sha1.
Thus, if during verification the algorithm identifier from your signature is accepted at all, it is interpreted to have been used in context with SHA1; your code, though (and I assume your signing code for the signatures first and second coincides in this respect), contains:
ExternalSignature es = new PrivateKeySignature(pk, "SHA-256", "BC");
I.e. your code hashes using SHA-256. Thus, your signature won't be positively verified.
The issue
The background issue is that com.itextpdf.text.pdf.security.PdfPKCS7.setExternalDigest(byte[], byte[], String) for DSA signatures only stores the id-dsa OID in digestEncryptionAlgorithmOid and PdfPKCS7.getEncodedPKCS7(byte[], Calendar, TSAClient, byte[], Collection<byte[]>, CryptoStandard) uses digestEncryptionAlgorithmOid as is as signature algorithm OID.
Thus, for all DSA signatures id-dsa is used as signature algorithm even though it actually should not be used at all, and if it is used implies SHA-1.
PS: In case of RSA signatures not including the HASH algorithm in the signature algorithm value is no problem because RSA signatures actually are encoded structures mentioning the HASH algorithm used. DSA, on the other hand, is not about decoding but merely about checking, so there is no implicit HASH algorithm mentioned anywhere.
Here's my code as below:
public static void main(String[] args) throws IOException, GeneralSecurityException, DocumentException {
String path = "<pfx file>";
char[] pass = "<password>".toCharArray();
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
KeyStore ks = KeyStore.getInstance("pkcs12", provider.getName());
ks.load(new FileInputStream(path), pass);
String alias = (String) ks.aliases().nextElement(); /* alias='CCA India 2011\u0000'*/
PrivateKey pk = (PrivateKey) ks.getKey(alias, pass);/* returns null */
Certificate[] chain = ks.getCertificateChain(alias);/* returns null */
X509Certificate last = (X509Certificate) chain[chain.length - 1];
System.out.println(last.getNotBefore());
System.out.println(last.getNotAfter());
}
The alias that is returned back has a \u0000 at the end of it. I am not sure what to make of it. Is that the reason why pk and chain is null? I tried to trim the alias to no avail.
I am able to import this certificate into the microsoft keystore. Meaning I am able to see it in the Internet Explorer .. Certificates. I am able to use it to sign documents on Adobe Reader. So there is no issue the pfx file. Just not able to work with it in java.
I have JCE installed as well.
I am quite sure the alias is your problem. We had similar problems with upper and lower case letters. Some Providers are case sensitive (I think BC for example) some are not (I think the sun providers) and I am not sure if they support special characters.
Have you tried the Sun "SunJSSE" Provider? It should be used by default if you do not specify BC and he supports PKCS#12. I think the provider is also a bit lenient in regards to the alias than bc. If nothing helps I would try to change the alias of the entry, maybe some microsoft tool can do this if they support this alias.
I have a problem with the "SHA1withRSA" signature using Bouncy Castle on Android: Bouncy Castle signature value does not match with .NET signature value.
I tried to use many algorithms like "SHA1withRSA", "SHA1withRSAAndMGF1" or "SHA1withRSA/ISO9796-2" with no satisfactory results.
If I use the same message, same algorithm and the same private key, the result must be identical in .NET and Bouncy Castle... shouldn't it?
Whats wrong in my code???
Getting the private key:
...
ks = KeyStore.getInstance("PKCS12");
...
key = ks.getKey(alias, senha.toCharArray());
if (key instanceof PrivateKey) {
privateKey = (PrivateKey) key;
}
Signature method:
public String signer(String txt, String alg) throws Exception {
Signature signer = Signature.getInstance(alg, new BouncyCastleProvider());
signer.initSign(privateKey);
signer.update(txt.getBytes("UTF-8"));
return Base64.encodeToString(signer.sign(), Base64.NO_WRAP);
}
My app must use XMLDSIG protocol to send XML to a government's web service.
Value being signed can have some random padding data (is your signature is always the same on one side?). You should generate signature on one side and try to verify on the other.
This is especially the case for the following algorithms:
"SHA1WITHRSAANDMGF1"
"SHA224WITHRSAANDMGF1"
"SHA256WITHRSAANDMGF1"
"SHA384WITHRSAANDMGF1"
"SHA512WITHRSAANDMGF1"
Install the Bouncy Castle provider and see if the signature can be verified with the public key.