7

I am developing an Android application that requires Digitally signing an html document. The document resides in the DB, in a JSON form. I'm signing the document locally using a BASH Script I found on some other SO question :

openssl dgst -sha1 someHTMLDoc.html > hash openssl rsautl -sign -inkey privateKey.pem -keyform PEM -in hash > signature.bin

Private key was generated using :

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:3 -out privateKey.pem 

Public key was generated using :

openssl pkey -in privateKey.pem -out publicKey.pem -pubout

I want to verify the signature created in Signature.bin together with the data in someHTMLDoc.html, back in the application.

I am sending both the html and signature as JSON Object ex:

{ "data" : "<html><body></body></html>", "signature":"6598 13a9 b12b 21a9 ..... " }

The android application holds the PublicKey in shared prefs as follows :

-----BEGIN PUBLIC KEY----- MIIBIDANBgkqhkiG9w0AAAEFAAOCAQ0AvniCAKCAQEAvni/NSEX3Rhx91HkJl85 \nx1noyYET ......

Notice the "\n" (newline) in there (was automatically added when copying string from publicKey.pem to Android Gradle Config.

Ok, after all preparations, now the question. I am trying to validate the key with no success.

I am using the following code :

private boolean verifySignature(String data, String signature) {
    InputStream is = null;
    try {
        is = new ByteArrayInputStream(Config.getDogbarPublic().getBytes("UTF-8")); //Read DogBar Public key

        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        List<String> lines = new ArrayList<String>();
        String line;
        while ((line = br.readLine()) != null)
            lines.add(line);

        // removes the first and last lines of the file (comments)
        if (lines.size() > 1 && lines.get(0).startsWith("-----") && lines.get(lines.size() - 1).startsWith("-----")) {
            lines.remove(0);
            lines.remove(lines.size() - 1);
        }

        // concats the remaining lines to a single String
        StringBuilder sb = new StringBuilder();
        for (String aLine : lines)
            sb.append(aLine);
        String key = sb.toString();

        byte[] keyBytes = Base64.decode(key.getBytes("utf-8"), Base64.DEFAULT);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey publicKey = keyFactory.generatePublic(spec);

        Signature signCheck = Signature.getInstance("SHA1withRSA"); //Instantiate signature checker object.
        signCheck.initVerify(publicKey);
        signCheck.update(data.getBytes());
        return signCheck.verify(signature.getBytes()); //verify signature with public key
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}

Can anyone help ? what am i doing wrong ?

Am i missing some byte conversion ? maybe the JSON object is affecting the signature ?

Should a signature contain the \n (linebreak) that the original file contains or should it be without in the JSON file ?

Thanks in advance for all the help, its highly appreciated.

4ae1e1
  • 7,228
  • 8
  • 44
  • 77
Noxymon
  • 201
  • 4
  • 15

2 Answers2

9

Digital signature is a process of computing digest (function H) of data (C) and encrypting it with asymmetric encryption algorithm (function E) to produce cypher text (S):

S = E(H(C))

Signature verification takes the signature decrypts the given signature (function D) - which results in H(C) only if the public key used in decryption is paired with private key used in encryption, and computes the digest of data to check if the two digests match:

H(C) == D(E(H(C)))

It's clear from this that the bytes given to the hash function (C) must be exactly the same in order for the signature to validate.

In your case they are not, because when you're computing the digest using openssl dgst the output (H(C) on the right) is literally something like:

SHA1(someHTMLDoc.html)= 22596363b3de40b06f981fb85d82312e8c0ed511

And this is the input to the RSA encryption.

And when you're verifying the signature, the output of the digest (H(C) on the left) are the raw bytes, for instance in hex:

22596363b3de40b06f981fb85d82312e8c0ed511

So you end up encrypting bytes to produce (H(C) on the right):

0000000: 5348 4131 2873 6f6d 6548 746d 6c44 6f63  SHA1(someHtmlDoc
0000010: 2e68 746d 6c29 3d20 3232 3539 3633 3633  .html)= 22596363
0000020: 6233 6465 3430 6230 3666 3938 3166 6238  b3de40b06f981fb8
0000030: 3564 3832 3331 3265 3863 3065 6435 3131  5d82312e8c0ed511
0000040: 0a                                       .

and comparing against bytes (H(C) on the left):

0000000: 2259 6363 b3de 40b0 6f98 1fb8 5d82 312e  "Ycc..@.o...].1.
0000010: 8c0e d511                                ....

Also you need to use -sign with openssl dgst in order to have proper output format (see Difference between openSSL rsautl and dgst).

So on the OpenSSL side do:

openssl dgst -sha1 -sign privateKey.pem someHTMLDoc.html > signature.bin

On the Java side do:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.security.Signature;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;

import org.spongycastle.util.io.pem.PemObject;
import org.spongycastle.util.io.pem.PemReader;

public class VerifySignature {
    public static void main(final String[] args) throws Exception {
        try (PemReader reader = publicKeyReader(); InputStream data = data(); InputStream signatureData = signature()) {
            final PemObject publicKeyPem = reader.readPemObject();
            final byte[] publicKeyBytes = publicKeyPem.getContent();
            final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            final X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
            final RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(publicKeySpec);

            final Signature signature = Signature.getInstance("SHA1withRSA");
            signature.initVerify(publicKey);

            final byte[] buffy = new byte[16 * 1024];
            int read = -1;
            while ((read = data.read(buffy)) != -1) {
                signature.update(buffy, 0, read);
            }

            final byte[] signatureBytes = new byte[publicKey.getModulus().bitLength() / 8];
            signatureData.read(signatureBytes);

            System.out.println(signature.verify(signatureBytes));
        }
    }

    private static InputStream data() throws FileNotFoundException {
        return new FileInputStream("someHTMLDoc.html");
    }

    private static PemReader publicKeyReader() throws FileNotFoundException {
        return new PemReader(new InputStreamReader(new FileInputStream("publicKey.pem")));
    }

    private static InputStream signature() throws FileNotFoundException {
        return new FileInputStream("signature.bin");
    }
}

I've used Spongy Castle for PEM decoding of the public key to make things a bit more readable and easier to use.

Zoran Regvart
  • 4,630
  • 22
  • 35
  • i am not sure i fully understand.. how ould you go about encrypting ? should i take only the `22596363b3de40b06f981fb85d82312e8c0ed511` part of the dgst function and sign with that ? could you please explain what i should do rather than what is the mistake i have made? i am having a hard time understanding how to go about what was written. – Noxymon Dec 06 '15 at 09:25
  • 1
    See my edited answer, you should use `openssl dgst` with `-sign` option to produce binary output needed by the Signature implementation in JCE. – Zoran Regvart Dec 06 '15 at 15:29
  • Give this man a cookie ! You sir are a genius ! Thank you so much. – Noxymon Dec 06 '15 at 16:54
  • Will award you with the bounty in 19 hours. Thanks a lot again. – Noxymon Dec 06 '15 at 16:55
0

If you have a digitally signed XML file (downloaded from the web) and a certificate (.cer file) and you want to verify the digital signature in an android app then here is the code:

You need two things xmlFilePath and certificateFilePath

boolean verifySignature() {
        boolean valid = false;
        try {

            File file = new File("xmlFilePath");
            DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
            f.setNamespaceAware(true);
            Document doc = f.newDocumentBuilder().parse(file);

            NodeList nodes = doc.getElementsByTagNameNS(Constants.SignatureSpecNS, "Signature");
            if (nodes.getLength() == 0) {
                throw new Exception("Signature NOT found!");
            }

            Element sigElement = (Element) nodes.item(0);
            XMLSignature signature = new XMLSignature(sigElement, "");


            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            InputStream ims = new InputStream("certificateFilePath");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(ims);

            if (cert == null) {
                PublicKey pk = signature.getKeyInfo().getPublicKey();
                if (pk == null) {
                    throw new Exception("Did not find Certificate or Public Key");
                }
                valid = signature.checkSignatureValue(pk);
            } else {
                valid = signature.checkSignatureValue(cert);
            }
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(this, "Failed signature " + e.getMessage(), Toast.LENGTH_SHORT).show();
        }

        return valid;
    }

If you want to do it in java but not in android studio. Here is the code:

public static boolean isXmlDigitalSignatureValid(String signedXmlFilePath,
                                                     String pubicKeyFilePath) throws Exception {

        boolean validFlag;
        File file = new File(signedXmlFilePath);
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document doc = db.parse(file);
        doc.getDocumentElement().normalize();
        NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
        if (nl.getLength() == 0) {
            throw new Exception("No XML Digital Signature Found, document is discarded");
        }
        FileInputStream fileInputStream = new FileInputStream(pubicKeyFilePath);
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate cert = (X509Certificate) cf.generateCertificate(fileInputStream);
        PublicKey publicKey = cert.getPublicKey();
        DOMValidateContext valContext = new DOMValidateContext(publicKey, nl.item(0));
        XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
        XMLSignature signature = fac.unmarshalXMLSignature(valContext);
        validFlag = signature.validate(valContext);
        return validFlag;

    }

The reason is that you will need to add dependency if you use the same code in android studio, sometimes confusing also.

If you are interested in reading digital signature documents, you can read www.xml.com/post It is an interesting document for understanding the need for a digital signature.

Vijay
  • 1,163
  • 8
  • 22