Android digital signature verification

I am developing an Android application that requires digitally signing an html document. The document is in the database in the form of JSON. I am signing a document locally using BASH Script, which I found on another SO question:

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

The private key was generated using:

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

The public key was generated using:

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

I want to check the signature created in Signature.bin along with the data in someHTMLDoc.html in the application.

I am posting html and signature as an ex JSON object:

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

The android application stores PublicKey in shared privileges as follows:

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

Note the "\ n" (new line) there (it was automatically added when copying a line from publicKey.pem in the Android Gradle Config.

Well, after all the preparations, now the question. I try to verify the key without 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 a few bytes? perhaps the JSON object affects the signature?

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

Thank you in advance for your help, it was highly appreciated.

+5
source share
1 answer

A digital signature is the process of calculating a digest (function H) of data (C) and encrypting it using an asymmetric encryption algorithm (function E) to create the text cypher (S):

 S = E(H(C)) 

Signature verification accepts the signature, decrypts the given signature (function D), which leads to H (C) only if the public key used in decryption is paired with the private key used in encryption and calculates the data digest to verify that two digests match :

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

From this it is clear that the bytes specified by the hash function (C) must be exactly the same for the signature to be checked.

In your case, this is not so, because when you calculate the digest using openssl dgst , the output (H (C) on the right) literally looks something like this:

 SHA1(someHTMLDoc.html)= 22596363b3de40b06f981fb85d82312e8c0ed511 

And this is the input for RSA encryption.

And when you check the signature, the digest output (H (C) on the left) is raw bytes, for example, in hexadecimal format:

 22596363b3de40b06f981fb85d82312e8c0ed511 

So you will end up encrypting the bytes to create (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 comparison with bytes (H (C) on the left):

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

You also need to use -sign with openssl dgst to have the correct output format (see The 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 used Spongy Castle to PEM-decode the public key to make things more readable and easy to use.

+3
source

Source: https://habr.com/ru/post/1237334/


All Articles