2015-12-03 17 views
5

Sto sviluppando un'applicazione Android che richiede la firma digitale di un documento html. Il documento risiede nel DB, in un modulo JSON. Sto firmando il documento localmente utilizzando uno script bash che ho trovato su qualche domanda altro SO:Verifica firma digitale su Android

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

chiave privata è stata generata utilizzando:

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

chiave pubblica è stata generata utilizzando:

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

Voglio verificare la firma creata in Signature.bin insieme con i dati in alcuniHTMLDoc.html, indietro in th e applicazione.

io mando sia il codice HTML e la firma come JSON oggetto es:

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

L'applicazione Android detiene il PublicKey in preferenze condivise come segue:

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

Avviso del "\ n" (newline) (è stato aggiunto automaticamente durante la copia di stringa da publicKey.pem a Android Gradle Config.

Ok, dopo tutti i preparativi, ora la domanda. Sto provando a convalidare la chiave senza successo.

Sto usando il seguente codice:

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; 
    } 
} 

chiunque può aiutare? Che cosa sto facendo di sbagliato ?

Mi manca qualche conversione di byte? forse l'oggetto JSON sta influenzando la firma?

Se una firma contiene lo \ n (interruzione di riga) che il file originale contiene o dovrebbe essere senza nel file JSON?

Grazie in anticipo per tutto l'aiuto, è molto apprezzato.

+0

Continuo a provare diversi metodi senza esito positivo, ho provato a rimuovere le interruzioni di riga dall'oggetto JSON, ho provato getBytes ("ASCII") e getBytes ("UTF-8"). – Noxymon

+1

Hai ricevuto qualche eccezione? Quale? – Henry

+0

non ho ricevuto alcuna eccezione, restituisce solo False per 'signCheck.verify()' – Noxymon

risposta

3

firma digitale è un processo di calcolo digest (funzione H) di dati (C) e la crittografia con algoritmo di crittografia asimmetrica (la funzione E) per produrre il testo cifrato (S):

S = E(H(C)) 

verifica della firma prende la firma decrittografa la firma data (funzione D) - che risulta in H (C) solo se la chiave pubblica utilizzata in decifrazione viene abbinata alla chiave privata utilizzata nella crittografia e calcola il digest dei dati per verificare se i due digesti corrispondono:

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

È chiaro da questo t i byte dati alla funzione di hash (C) devono essere esattamente gli stessi per la convalida della firma.

Nel tuo caso non lo sono, perché quando si sta calcolando il digest utilizzando openssl dgst l'uscita (H (C) a destra) è letteralmente qualcosa come:

SHA1(someHTMLDoc.html)= 22596363b3de40b06f981fb85d82312e8c0ed511 

e questo è l'ingresso la crittografia RSA.

E quando si è verifica della firma, l'uscita del digest (H (C) a sinistra) sono le prime byte, per esempio in esadecimale:

22596363b3de40b06f981fb85d82312e8c0ed511 

così si finisce crittografia byte per produrre (H (C) a destra):

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          . 

e confrontandoli byte (H (C) sulla sinistra):

0000000: 2259 6363 b3de 40b0 6f98 1fb8 5d82 312e "[email protected]].1. 
0000010: 8c0e d511        .... 

Inoltre è necessario utilizzare -sign con openssl dgst in modo da avere il formato di output corretto (vedi Difference between openSSL rsautl and dgst).

Così sul lato OpenSSL fare:

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

Sul lato Java fare:

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"); 
    } 
} 

Ho usato Spongy Castle per PEM la decodifica della chiave pubblica per rendere le cose un po 'più leggibile e più facile da usare.

+0

non sono sicuro di aver capito appieno .. come si può procedere con la crittografia? dovrei prendere solo la parte '22596363b3de40b06f981fb85d82312e8c0ed511' della funzione dgst e firmare con quella? potresti spiegare cosa dovrei fare piuttosto che quale è l'errore che ho fatto? sto avendo difficoltà a capire come andare su ciò che è stato scritto. – Noxymon

+1

Vedere la mia risposta modificata, dovresti usare 'openssl dgst' con l'opzione' -sign' per produrre l'output binario necessario all'implementazione della firma in JCE. –

+0

Dai a quest'uomo un biscotto! Lei, signore, è un genio ! Grazie mille. – Noxymon