I'm trying to create a SOAP message with signature (Russian GOST34.10-2001 algorithm) in Java for some goverment services. The thing is that the service tells me that Signature is invalid and I can't see where I made a mistake. The key container where I read private key and certificate from is valid.
Where things go wrong? I understand that my question is quite specific but I still hope that someone could show me the way.
update1:a little update: body of message has some cyrillic(utf)-8 nodes and values
update2:
So I read through this question and tried to get the PrivateKey in the same way and got this Exception:
java.lang.IllegalArgumentException: private key algorithm does not match algorithm of public key in end entity certificate
Here's some code:
Here I marshal classes into SOAP body:
try {
MessageFactory mf = MessageFactory.newInstance();
SOAPMessage sm = mf.createMessage();
SOAPPart sp = sm.getSOAPPart();
SOAPEnvelope envelope = sp.getEnvelope();
QName bodyId = new QName("http://ws.unisoft/", "SendFullULRequest");
SOAPBody sb = (SOAPBody) envelope.getBody();
SOAPHeader sh = (SOAPHeader) envelope.getHeader();
javax.xml.bind.JAXBContext jaxbCtx = javax.xml.bind.JAXBContext.newInstance(FullULRq.class);
javax.xml.bind.Marshaller marshaller = jaxbCtx.createMarshaller();
marshaller.marshal(new JAXBElement<FullULRq>(bodyId, FullULRq.class, fulr),
sm.getSOAPBody());
sm.saveChanges();
Then I compute Digest Value:
ReferenceType rt2 = new ReferenceType();
rt2.setURI("#body");
////Пункт 7
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Source xmlSource = new DOMSource(sm.getSOAPBody());
Result outputTarget = new StreamResult(outputStream);
try {
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty(OutputKeys.INDENT, "no");
transformer.transform(xmlSource, outputTarget);
Init.init();
Canonicalizer canon = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
final ByteArrayInputStream is = new ByteArrayInputStream(canon.canonicalize(outputStream.toByteArray()));
rt2.setDigestValue(computeDigestWithStream(is));
} catch (Exception e) {
e.printStackTrace();
}
With this method:
public static byte[] computeDigestWithStream(ByteArrayInputStream stream) throws Exception {
final MessageDigest digest =
MessageDigest.getInstance("GOST3411");
// processing data
final DigestInputStream digestStream =
new DigestInputStream(stream, digest);
while (digestStream.available() != 0) {
digestStream.read();
}
And finally I generate a signature from the block (in which element is present):
ByteArrayOutputStream outputStream2 = new ByteArrayOutputStream();
Source xmlSource2 = new DOMSource(sm.getSOAPBody());
Result outputTarget2 = new StreamResult(outputStream2);
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty(OutputKeys.INDENT, "no");
transformer.transform(xmlSource2, outputTarget2);
Init.init();
Canonicalizer canon = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
InputStream is2 = new ByteArrayInputStream(outputStream2.toByteArray());
byte[] bb = canon.canonicalize(outputStream2.toByteArray());
byte[] signval = sign("CryptoProSignature", key, bb);
sign() method:
public static byte[] sign(String algorithmName, PrivateKey privateKey, byte[] data) throws Exception {
final Signature sig = Signature.getInstance(algorithmName);
sig.initSign(privateKey);
sig.update(data);
return sig.sign();
}
And if it is relevant this is how I read the privateKey from a container:
KeyStore ks = KeyStore.getInstance("FloppyStore");
String passwd = "*";
String kalias = null;
ks.load(null, null);
Enumeration enum1 = ks.aliases();
for (; enum1.hasMoreElements();) {
String tAlias = (String) enum1.nextElement();
if (ks.isKeyEntry(tAlias)) {
kalias = tAlias;
}
System.out.println(tAlias);
}
PrivateKey key = (PrivateKey) ks.getKey(kalias, passwd.toCharArray());
Thanks!
Related
need some tips/advice about (optimization) and how to sign XML with a certificate in the fastest way possible. On some databases (Oracle) it needs 30 msec to sign a file.. But on another database, it needs 1.2 sec. This function/method is called in a loop. It signs some XML data and returns a signed XML in a String. (Java method is exposed as PL/SQL function).
public static String signXML(String vhodniXml) throws Exception {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
dbFactory.setNamespaceAware(true);
Document doc = dbFactory.newDocumentBuilder().parse(new ByteArrayInputStream(vhodniXml.getBytes()));
NodeList nl = doc.getElementsByTagName("fu:BusinessPremiseRequest");
Node node = nl.item(0);
((Element) node).setIdAttribute("Id", true);
Enumeration e = p12.aliases();
String alias = (String) e.nextElement();
Key privateKey = p12.getKey(alias, geslo.toCharArray());
KeyStore.PrivateKeyEntry keyEntry
= (KeyStore.PrivateKeyEntry) p12.getEntry(alias, new KeyStore.PasswordProtection(geslo.toCharArray()));
X509Certificate cert = (X509Certificate) keyEntry.getCertificate();
PublicKey publicKey = cert.getPublicKey();
final XMLSignatureFactory sigFactory = XMLSignatureFactory.getInstance("DOM");
// Create a Reference to the enveloped document
Reference ref = sigFactory.newReference("#data",
sigFactory.newDigestMethod(DigestMethod.SHA256, null),
Collections.singletonList(sigFactory.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)),
null,
null);
SignedInfo si = sigFactory.newSignedInfo(sigFactory.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null), sigFactory.newSignatureMethod("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", null), Collections.singletonList(ref));
// Create a KeyValue containing the RSA PublicKey
KeyInfoFactory keyInfoFactory = sigFactory.getKeyInfoFactory();
X509IssuerSerial x509IssuerSerial = keyInfoFactory.newX509IssuerSerial(cert.getSubjectX500Principal().getName(), cert.getSerialNumber());
List x509Content = new ArrayList();
x509Content.add(cert.getSubjectX500Principal().getName());
x509Content.add(x509IssuerSerial);
KeyValue keyValue = keyInfoFactory.newKeyValue(publicKey);
X509Data xd = keyInfoFactory.newX509Data(x509Content);
// Create a KeyInfo and add the KeyValue to it
KeyInfo keyInfo = keyInfoFactory.newKeyInfo(Collections.singletonList(xd));
// Create a DOMSignContext and specify the RSA PrivateKey and
// location of the resulting XMLSignature's parent element
DOMSignContext dsc = new DOMSignContext(
privateKey,
node
);
// Create the XMLSignature (but don't sign it yet)
XMLSignature signature = sigFactory.newXMLSignature(si, keyInfo);
// Marshal, generate (and sign) the enveloped signature
signature.sign(dsc);
ByteArrayOutputStream os = new ByteArrayOutputStream();
Transformer trans = TransformerFactory.newInstance()
.newTransformer();
trans.transform(new DOMSource(doc), new StreamResult(os));
return new String(os.toByteArray());
}
The first thing to do is to remove some IO operations by storing your key related objects in fields because reading the KeyStore to get keys takes time.
I need to encrypt a set of user credentials and send it to a SOAP web service. The following code snippet (I think it's C#) is provided in the documentation, and my Java code is based on it.
private string Encrypt256(string text, AesCryptoServiceProvider aes)
{
// Convert string to byte array
byte[] src = Encoding.Unicode.GetBytes(text);
// encryption
using (ICryptoTransform encrypt = aes.CreateEncryptor())
{
byte[] dest = encrypt.TransformFinalBlock(src, 0, src.Length);
// Convert byte array to Base64 strings
return Convert.ToBase64String(dest);
}
}
...
Credentials credential = new Credentials();
credential.UserName = "username";
credential.Password = "password";
credential.ClientUtcTime = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ", System.Globalization.CultureInfo.InvariantCulture);
//--Serialize credential
XmlSerializer serializer = new XmlSerializer(credential.GetType());
string xmlCredential = string.Empty;
using (var stringwriter = new System.IO.StringWriter())
{
serializer.Serialize(stringwriter, credential);
xmlCredential = stringwriter.ToString();
}
//--Encrypt credential with AES256 symmetric
String encryptedCredential = Encrypt256(xmlCredential, aesServiceProvider);
...
The following is my Java code.
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(256);
SecretKey sk = kg.generateKey();
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.ENCRYPT_MODE, sk);
Credentials cred = new UsernamePasswordCredentials("username", "password");//no need for time field?
String eCred = Base64.encodeBase64String(aesCipher.doFinal(objectToByteArray(cred)));
...
private byte[] objectToByteArray(Object obj) {
byte[] bytes = null;
try (
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
) {
oos.writeObject(obj);
oos.flush();
bytes = bos.toByteArray();
}
catch (IOException e) {
e.printStackTrace();
}
return bytes;
}
Then I came across SealedObject and CipherOutputStream. I tried writing code snippets for those.
Using SealedObject
// slight change here; cred must implement Serializable
UsernamePasswordCredentials cred = new UsernamePasswordCredentials("username", "password");
// same as above except for the following two lines
SealedObject so = new SealedObject(cred, aesCipher);
String eCred = Base64.encodeBase64String(objectToByteArray(so));
Using CipherOutputStream
Credentials cred = new UsernamePasswordCredentials("username", "password");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
CipherOutputStream cos = new CipherOutputStream(bos, aesCipher);
cos.write(objectToByteArray(cred));
cos.close();
String eCred = Base64.encodeBase64String(bos.toByteArray());
For all three code snippets, is the code correct? Considering that this code will be called frequently, which approach is the most efficient?
I have been trying to serialize the object PKCS10CertificationRequest for a while now. I figured the right way to do it is to create an ASN1Primitive class, send it over the network, then deserialize it. However, there seems to be only serialization into ASN1, but there seems to be no deserialization from ASN1, and I don't want to manually parse and reconstruct the Request. What should I do? My code so far is
Security.addProvider(new BouncyCastleProvider());
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "SC");
kpg.initialize(1024);
KeyPair kp = kpg.genKeyPair();
System.out.println("Private: " + kp.getPrivate());
System.out.println("Public: " + kp.getPublic());
X500NameBuilder x500NameBld = new X500NameBuilder(BCStyle.INSTANCE);
x500NameBld.addRDN(BCStyle.C, "AU");
x500NameBld.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");
x500NameBld.addRDN(BCStyle.L, "Melbourne");
x500NameBld.addRDN(BCStyle.ST, "Victoria");
x500NameBld.addRDN(BCStyle.EmailAddress, "feedback-crypto#bouncycastle.org");
X500Name subject = x500NameBld.build();
PKCS10CertificationRequestBuilder requestBuilder = new JcaPKCS10CertificationRequestBuilder(subject, kp.getPublic());
PKCS10CertificationRequest req1 = requestBuilder.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider("SC").build(
kp.getPrivate()));
JcaPKCS10CertificationRequest req2 = new JcaPKCS10CertificationRequest(req1.getEncoded()).setProvider("SC");
//serialization
ByteArrayOutputStream abOut = new ByteArrayOutputStream();
ASN1OutputStream berOut = new ASN1OutputStream(abOut);
berOut.writeObject(req2.toASN1Structure());
byte[] serializedData = abOut.toByteArray();
ASN1Primitive asn1Primitive = ASN1Primitive.fromByteArray(serializedData);
System.out.println("");
System.out.println("" + asn1Primitive.toString());
And the output is
[[0, [[[2.5.4.6, AU]], [[2.5.4.10, The Legion of the Bouncy Castle]], [[2.5.4.7, Melbourne]], [[2.5.4.8, Victoria]], [[1.2.840.113549.1.9.1, feedback-crypto#bouncycastle.org]]], [[1.2.840.113549.1.1.1, NULL], #03818D0030818902818100A...
I don't want to parse this manually. What should I do instead?
Forget about ASN1, it is a mess, and there seems to be no automatic deserialization. However, you can use the JcaPEMWriter and PEMParser classes in BouncyCastle to create a String object to serialize or deserialize the data, and send it over the network.
StringWriter sw = new StringWriter();
JcaPEMWriter pemWriter = new JcaPEMWriter(sw);
pemWriter.writeObject(req2);
pemWriter.close();
PEMParser pemParser = null;
try
{
pemParser = new PEMParser(new StringReader(sw.toString()));
Object parsedObj = pemParser.readObject();
System.out.println("PemParser returned: " + parsedObj);
if (parsedObj instanceof PKCS10CertificationRequest)
{
JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest = new JcaPKCS10CertificationRequest((PKCS10CertificationRequest)parsedObj);
System.out.println("" + jcaPKCS10CertificationRequest.getPublicKey());
}
}
catch (IOException ex)
{
ex.printStackTrace();
}
finally
{
if (pemParser != null)
{
pemParser.close();
}
}
EDIT: Although if someone really needs to get elements out of ASN1Encodable object (like an RDN of X500Name, apparently you need the IETFUtils class as per https://stackoverflow.com/a/5527171/2413303 .
Is there a way to print the CSR generated with PKCS10CertificationRequest class? I am struggling to see the generated request.
PKCS10CertificationRequest certRequest = new PKCS10CertificationRequest(fromByteArray);
System.out.println("CSR string = "+certRequest.toString());
System.out.println("CSR Subject Name = "+certRequest.getSubject().toString());
System.out.println("CSR Subject PubkeyInfo = "+certRequest.getSubjectPublicKeyInfo().toString());
Hope this can help:
PemObject pemObject = new PemObject("CERTIFICATE REQUEST", certRequest.getEncoded());
StringWriter str = new StringWriter();
PEMWriter pemWriter = new PEMWriter(str);
pemWriter.writeObject(pemObject);
pemWriter.close();
str.close();
System.out.println(str);
I tried to use private key to sign the message digest to generate the signature, and then use public key to verify the signature to get message digest. I want to compare with this two message digests, but i got a error about "java.security.SignatureException: Signature length not correct: got 250 but was expecting 128".
When i tried to print out Arrays.toString(data2), its all 0.
I also tried to follow the link: Verify the Signature
public void firstDigitalSignature() throws IOException, NoSuchAlgorithmException, Throwable
{
//*Generate Message Digest1*
byte[] buffer=null;
buffer = new byte[(int) inputFile1.length()];
FileInputStream fis = new FileInputStream(inputFile1);
fis.read(buffer);
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(buffer);
digestBytes = messageDigest.digest();
//convert the byte to hex format method 2
StringBuffer hexString = new StringBuffer();
for(int i=0;i<digestBytes.length;i++){
hexString.append(Integer.toHexString(0xFF & digestBytes[i]));
}
System.out.println("Message Digest-1: "+hexString.toString());
//*Using private key to encrypt the image-Digital signauture1 *
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(digestBytes);
encryptmd= signature.sign();
StringBuffer hexString2 = new StringBuffer();
for(int i=0;i<encryptmd.length;i++){
hexString2.append(Integer.toHexString(0xFF & encryptmd[i]));
x=hexString2.toString();
File file = new File("c://Directroy111");
if (!file.exists()) {
if (file.mkdir()) {
System.out.println("Doctor is created!");
} else {
System.out.println("Failed to create Doctor!");
}
}
BufferedWriter out = new BufferedWriter(
new FileWriter("C:\\Directroy111\\Digital Signature Doctor.txt"));
out.write(x);
out.close();
this.copyImageFiles(sourceFile, destinationDir);
}
System.out.println("Message Digest Encrypted-1: "+hexString2.toString()+"\n");
}
public void firstVerify() throws IOException, NoSuchAlgorithmException, Throwable
{
//Generate Message Digest1 - Decrypt
String verifyfile= "c:\\Directroy111\\2.jpg";
File decryptfile= new File(verifyfile);
byte[] buffer2=null;
buffer2 = new byte[(int) decryptfile.length()]; //array type is integer, thats why we use int here
FileInputStream fis2 = new FileInputStream(decryptfile);
fis2.read(buffer2);
MessageDigest messageDigest2 = MessageDigest.getInstance("SHA-256");
messageDigest2.update(buffer2);
byte[] digestBytes2 = messageDigest2.digest();
StringBuffer hexString22 = new StringBuffer();
for(int i=0;i<digestBytes2.length;i++){
hexString22.append(Integer.toHexString(0xFF & digestBytes2[i]));
}
System.out.println("Message Digest(Hash)-1(Decryption): "+hexString22.toString()); //System.out.println(hexString);
//*******Decypt*************//
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
//FileReader read= new FileReader("C:\\TEMP1\\Doctor\\Digital Signature Doctor.txt");
FileInputStream br2 = new FileInputStream("C:\\Directroy111\\Digital Signature Doctor.txt");
//BufferedInputStream bis=new BufferedInputStream(br2);
//BufferedReader br = new BufferedReader(new FileReader(br2));
byte[] data2=new byte[br2.available()];
System.out.println(Arrays.toString(data2));
br2.read(data2);
br2.close();
FileInputStream datafis=new FileInputStream("C:\\Directroy111\\Digital Signature Doctor.txt");
BufferedInputStream bufin=new BufferedInputStream(datafis);
byte[] buffer=new byte[1024];
int len;
while(bufin.available()!=0){
len=bufin.read(buffer);
signature.update(buffer,0,len);
};
bufin.close();
System.out.println("111111");
boolean decryptmd2= signature.verify(data2);
System.out.println("signature verifies: " + decryptmd2);
if(decryptmd2==false){
str = String.valueOf(decryptmd2);
System.out.println("Message Digest-1(Decryption): "+str);
}else{
System.out.println("1111");
}
//**Verify*
if(str.equals(hexString22.toString())){
System.out.println("Digital Signature-1 was not modified"+"\n");
}else{
System.out.println("ERROR!!! Digital Signature-1 was modified"+"\n");
}
}
I see a couple issues here. First, the actual signing code should be included with your question as well. Without being able to see the signing code, it's more difficult to debug the problem. Check if your signing algorithm is the same as your verification algorithm (SHA256withRSA).
Next, if you're using the SHA256withRSA algorithm, you don't need to calculate a message digest first. The algorithm will calculate it for you and sign the digest. You just pass in the entire file to be signed.
In your verification step, you are updating the Signature object with the same file that you are saying is the signature. Both files are Digital Signature Doctor.txt. You should update the Signature object with the file to be signed, which I believe from your example is 2.jpg. Then when you call the verify() method, pass in the signature bytes.
Finally, you are misinterpreting the return value of verify(). It returns a boolean. It does not return the message digest or the original message. When your code does String.valueOf(decryptmd2), the value will either be "false" or "true".
I'm not sure why you are trying to compare the message digests. Let's say you are sent a message with a signature signed by a private key corresponding to a public key that you trust. IF the verify() method returns true, you can be sure that:
the message was signed by that person
the message you received is identical to the message that was signed
That should be sufficient for what you need.
Echoing gtrig's comments, here is a complete example showing signature creation and verification:
import java.io.*;
import java.security.*;
public class RSASignatureExample {
private static int BUFSIZE = 8192;
private PrivateKey privateKey;
private PublicKey publicKey;
public RSASignatureExample() throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
privateKey = kp.getPrivate();
publicKey = kp.getPublic();
}
private static void processData(File f, Signature s) throws Exception {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(f), BUFSIZE);
byte[] buf = new byte[BUFSIZE];
int numRead;
while ((numRead = bis.read(buf)) > 0) {
s.update(buf, 0, numRead);
}
bis.close();
}
public byte[] sign(File fileToSign) throws Exception {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
processData(fileToSign, signature);
return signature.sign();
}
public boolean verify(File fileToVerify, byte[] signatureBytes) throws Exception {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
processData(fileToVerify, signature);
return signature.verify(signatureBytes);
}
public static void main(String[] args) throws Exception {
RSASignatureExample example = new RSASignatureExample();
File tempFile = File.createTempFile("rsa", null);
FileOutputStream fos = new FileOutputStream(tempFile);
fos.write("Hello World".getBytes("UTF-8"));
fos.close();
// Sign the file
byte [] signatureBytes = example.sign(tempFile);
// Verify the signature
boolean isVerified = example.verify(tempFile, signatureBytes);
System.out.printf("Signature verified ?: %b", isVerified);
}
}