Qualche mese fa, Godfrey Nolan ha scritto un eccellente articolo che discute how uno sviluppatore Android app in grado di memorizzare le password degli utenti e sensibile / informazioni personali. Il chiavi Android fornisce un livello di sistema memoria credenziali di protezione. Con l’archivio di chiavi, un’applicazione in grado di creare una nuova / coppia di chiavi pubblica privata, e utilizzare questo per cifrare i segreti delle applicazioni prima di salvarlo nelle cartelle di ammasso privato. In questo articolo, ci accingiamo a mostrare come usare il Keystore Android per creare ed eliminare le chiavi, e come utilizzare queste chiavi per cifrare e decifrare utente testo fornito.
Preparazione
Prima di iniziare la codifica, è utile per capire un po ‘la Keystore Android, e le sue capacità. Il Keystore non viene usato direttamente per la memorizzazione segreti di applicazione come la password, tuttavia, fornisce un contenitore sicuro, che può essere utilizzato dalle applicazioni per memorizzare le chiavi private, in un modo che è abbastanza difficile per gli utenti malintenzionati (non autorizzati) e applicazioni per recuperare.
Come suggerisce il nome, un’applicazione in grado di memorizzare più chiavi nel keystore, ma un’applicazione può solo visualizzare, e ricerca, le sue chiavi. Idealmente, con il keystore, un’app genererebbe / o ricevere un paio privata / pubblica chiave, che potrebbero essere memorizzati nel keystore. La chiave pubblica può essere usata per cifrare i segreti delle applicazioni, prima di essere memorizzati nelle cartelle specifiche app, con la chiave privata utilizzata per decifrare le stesse informazioni quando necessario.
Anche se il fornitore di Android Keystore è stato introdotto nel API livello 18 (Android 4.3), il Keystore sé è disponibile dal API 1, limitato da usare da sistemi VPN e Wi-Fi.
L’archivio chiavi stessa è criptata con l’utente proprio perno lockscreen / password, quindi, quando lo schermo del dispositivo è bloccato il Keystore non è disponibile. Tenetelo a mente se si dispone di un servizio in background che potrebbe essere necessario per accedere ai segreti delle applicazioni.
layout
Il layout principale della nostra applicazione di esempio è un ListView, con oggetti realizzati da un elenco di tutte le chiavi (in realtà la chiave alias / nomi) creati dalla app. Questo viene salvato come layout / activity_main.xml
& lt; xmlns: ListView. Androide = "https://schemas.android.com/apk/res/android" xmlns: strumenti = "http: // schemas.android.com/tools "android: id =" @ + id / listView "android: layout_width =" match_parent "Android: layout_height =" match_parent "Android: fillViewport =" "strumenti: context =" true com.sample.foo .simplekeystoreapp.MainActivity "& gt; & Lt; / ListView & gt;
Ogni elemento della lista contiene un TextView che rappresenta l'alias chiave, un pulsante per eliminare la chiave, e tasti ciascuna per cifrare e decifrare il testo. Questo layout / list_item.xml nel nostro progetto.
& lt;? xml version =" 1.0 "encoding =" utf-8 "& gt; & Lt; RelativeLayout xmlns: card_view = "https://schemas.android.com/apk/res-auto" xmlns: androide = "https://schemas.android.com/apk/res/android" android: id = " @ + id / cardBackground "android: layout_width =" match_parent "android: layout_height =" wrap_content "card_view: cardCornerRadius =" 4DP "android: layout_margin =" 5dp "& gt; & Lt; TextView android: id = "@ + id / keyAlias" android: layout_width = "match_parent" android: layout_height = "wrap_content" android: gravità = androide "center_vertical": textSize = "30dp" / & gt; & Lt; Button android: id = "@ + id / deleteButton" android: layout_width = "wrap_content" android: layout_height = androide "wrap_content": layout_below = "@ id / keyAlias" android: layout_alignParentLeft = "true" android: layout_centerHorizontal = " true "android: text =" @ string / delete "style =" @ stile / Base.Widget.AppCompat.Button.Borderless "/ & gt; & Lt; Button android: id = "@ + id / encryptButton" android: layout_width = "wrap_content" android: layout_height = "wrap_content" android: layout_below = "@ + id / keyAlias" Android: layout_alignRight = "@ + id / keyAlias" Android: text = "@ string / cifrare" style = "@ stile / Base.Widget.AppCompat.Button.Borderless" / & gt; & Lt; Button android: id = "@ + id / decryptButton" android: layout_width = "wrap_content" android: layout_height = "wrap_content" android: layout_below = "@ + id / keyAlias" Android: layout_toLeftOf = "@ + id / encryptButton" Android: text = "@ string / decifrare" style = "@ stile / Base.Widget.AppCompat.Button.Borderless" / & gt; & Lt; / RelativeLayout & gt;
Lista Intestazione
L'intestazione List viene aggiunto alla ListView utilizzando il metodo
Visualizza listHeader = View.inflate (questo, R.layout.activity_main_header, null ); listView.addHeaderView (listHeader);
Nell'immagine sopra, la ListView è vuoto, in modo che solo l'intestazione della lista è visibile. La Lista di intestazione è piuttosto semplice, con un ModificaIlTesto in alto, che si aspetta una stringa da utilizzare come alias durante la creazione di un tasto. Un pulsante per generare nuove chiavi si trova immediatamente sotto di questo. Il pulsante è seguita da tre EditTexts, uno in attesa di un stringa di input da cifrare, un altro mostra il risultato della crittografia, e la terza indica la stringa decrittografata (per una decrittazione di successo).. Questo file viene salvato nel layout / activity_main_header.xml
& lt;? Xml version = "1.0" encoding = "utf-8" & gt; & Lt; xmlns RelativeLayout: androide = "https://schemas.android.com/apk/res/android" android: orientamento = androide "verticale": layout_width = "match_parent" android: layout_height = "match_parent" android: gravità = " center_horizontal "android: paddingLeft =" @ dimen / activity_horizontal_margin "android: paddingRight =" @ dimen / activity_horizontal_margin "android: paddingTop =" @ dimen / activity_vertical_margin "android: paddingBottom =" @ dimen / activity_vertical_margin "& gt; & Lt; EditText android: id = "@ + id / aliasText" android: layout_width = "match_parent" android: layout_height = "wrap_content" android: layout_centerHorizontal = "true" Android: Hint = "@ stringa / key_alias" / & gt; & Lt; Button android: id = "@ + id / generateKeyPair" android: layout_width = "wrap_content" android: layout_height = androide "wrap_content": layout_below = "@ id / aliasText" android: layout_centerHorizontal = "true" android: layout_alignParentRight = " true "android: text =" @ stringa / generare "Android: onclick =" createNewKeys "/ & gt; & Lt; EditText android: id = "@ + id / startText" android: layout_width = "match_parent" android: layout_height = androide "wrap_content": layout_below = "@ id / generateKeyPair" android: layout_centerHorizontal = "true" android: hint = " @ string / initial_text "/ & gt; & Lt; EditText android: id = "@ + id / encryptedText" android: layout_width = "match_parent" android: layout_height = androide "wrap_content": layout_below = "@ id / startText" android: layout_centerHorizontal = "true" android: editable = " false "android: textIsSelectable =" true "android: hint =" @ string / final_text "/ & gt; & Lt; EditText android: id = "@ + id / decryptedText" android: layout_width = "match_parent" android: layout_height = androide "wrap_content": layout_below = "@ id / encryptedText" android: layout_centerHorizontal = "true" android: editable = " false "android: textIsSelectable =" true "android: hint =" @ string / decrypt_result "/ & gt; & Lt; / RelativeLayout & gt;
MainActivity
Come per qualsiasi attività, cominciamo con il metodo onCreate (). La prima cosa da fare è ottenere un riferimento al AndroidKeyStore, e poi inizializzare, usando:
Keystore.getInstance ("AndroidKeyStore"); keystore.load (null)
Abbiamo poi chiamiamo i nostri refreshKeys () metodo (discusso di seguito) per elencare tutti i tasti nostra applicazione è memorizzati nel keystore. Questo assicura che le chiavi nel keystore verranno visualizzati immediatamente quando ListView è inizializzato
Lista tutte le chiavi nel keystore
Per recuperare una enumerazione di tutte le chiavi a disposizione un'applicazione nel keystore, è sufficiente chiamare gli alias () metodo. Nelle nostre refreshKeys () il metodo seguito, abbiamo recuperare gli alias keystore, e mettere le stringhe restituite in un ArrayList (usato da adattatore del nostro ListView)
refreshKeys private void () {keyAliases = new ArrayList. & Lt; & gt; (); try {Enumeration & lt; stringa & gt; alias = keyStore.aliases (); mentre (aliases.hasMoreElements ()) {keyAliases.add (aliases.nextElement ()); }} Catch (Exception e) {} if (listAdapter! = Null) listAdapter.notifyDataSetChanged (); }
Aggiungi una nuova chiave per l'archivio chiavi
Per generare un pubblico / privato coppia di chiavi, abbiamo bisogno di un oggetto KeyPairGenerator. Otteniamo un'istanza di KeyPairGenerator impostato per utilizzare il RSA algoritmo con la "AndroidKeyStore". Chiamare generateKeyPair () crea la nuova coppia di chiavi (chiave Public Private e corrispondente), e lo aggiunge al Keystore.
Ogni tasto creato da un'applicazione deve avere un alias unico, che può essere qualsiasi stringa. Usiamo un oggetto KeyPairGeneratorSpec per costruire le specifiche del nostro chiave necessaria. È possibile impostare il periodo di validità della chiave (setStartDate () e setEndDate ()), impostare l'alias e il soggetto (per le chiavi di auto firmato) tra gli altri. Il soggetto deve essere un oggetto href="https://developer.android.com/reference/javax/security/auth/x500/X500Principal.html" > X500Principal
createNewKeys public void (Panorama vista) {String alias = aliasText.getText (). ToString (); try {// Crea nuova chiave, se necessario, se {inizio Calendario = Calendar.getInstance () (keyStore.containsAlias (alias)!); Fine Calendar = Calendar.getInstance (); end.add (Calendar.YEAR, 1); KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder (questo) .setAlias (alias) .setSubject (new X500Principal ("CN = nome del campione, O = Autorità Android")) .setSerialNumber (BigInteger.ONE) .setStartDate (start.getTime ()) .setEndDate (end.getTime ()) .build (); KeyPairGenerator generatore = KeyPairGenerator.getInstance ("RSA", "AndroidKeyStore"); generator.initialize (spec); Coppia di chiavi coppia di chiavi = generator.generateKeyPair (); }} Catch (Exception e) {Toast.makeText (questo, "Eccezione" + e.getMessage () + "si è verificato", Toast.LENGTH_LONG) .Show (); Log.e (TAG, Log.getStackTraceString (e)); } refreshKeys (); }
Elimina chiave dal Keystore
eliminazione di una chiave dal keystore è abbastanza semplice. Armati con l'alias chiave, chiamare keystore.deleteEntry (keyAlias). Non c'è modo di ripristinare una chiave eliminata, quindi si consiglia di essere certi prima di utilizzare questo.
DELETEKEY public void (String alias finale) {AlertDialog alertDialog = new AlertDialog.Builder (questo) .setTitle ( "Elimina Chiave") .setMessage ("Vuoi eliminare la chiave " "+ alias +" "dall'archivio di chiavi?") .setPositiveButton ("Yes", nuova DialogInterface.OnClickListener () {public void onClick (DialogInterface dialogo, int che) {try {keyStore.deleteEntry (alias); refreshKeys ();} catch (KeyStoreException e) {Toast.makeText (MainActivity.this, "Eccezione" + e.getMessage () + "si è verificato", Toast. LENGTH_LONG) .Show (); Log.e (TAG, Log.getStackTraceString (e));} dialog.dismiss (); }}) .setNegativeButton ("No", nuovo DialogInterface.OnClickListener () {public void onClick (dialogo DialogInterface, int che) {dialog.dismiss ();}}) .create (); alertDialog.show (); }
Encrypt blocco di testo
Crittografia di un blocco di testo viene eseguita con la chiave pubblica della coppia di chiavi. Recuperiamo la chiave pubblica, richiedere un Cipher utilizzando il nostro crittografia preferito / trasformazione decrittazione ("RSA / BCE / PKCS1Padding"), quindi inizializzare la cifra di eseguire la crittografia (Cipher.ENCRYPT_MODE) utilizzando la chiave pubblica recuperato. Cipher opera su (e restituisce) un byte []. Noi avvolgiamo il Cipher in una CipherOutputStream, insieme a un ByteArrayOutputStream per gestire le complessità di cifratura. Il risultato del processo di crittografia viene poi convertito in una stringa Base64 per la visualizzazione.
EncryptString public void (String alias) {try {KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry (alias, null ); RSAPublicKey publicKey = (RSAPublicKey) privateKeyEntry.getCertificate () getPublicKey ().; // Crittografare il testo String initialText = startText.getText () toString ().; if (initialText.isEmpty ()) {Toast.makeText (questo "text Inserisci nel widget 'testo iniziale'", Toast.LENGTH_LONG) .Show (); ritorno; } Ingresso Cipher = Cipher.getInstance ("RSA / BCE / PKCS1Padding", "AndroidOpenSSL"); input.init (Cipher.ENCRYPT_MODE, publicKey); ByteArrayOutputStream outputStream = new ByteArrayOutputStream (); CipherOutputStream cipherOutputStream = new CipherOutputStream (outputStream, input); cipherOutputStream.write (initialText.getBytes ("UTF-8")); cipherOutputStream.close (); byte [] vals = outputStream.toByteArray (); encryptedText.setText (Base64.encodeToString (vals, Base64.DEFAULT)); } Catch (Exception e) {Toast.makeText (questo, "Eccezione" + e.getMessage () + "si è verificato", Toast.LENGTH_LONG) .Show (); Log.e (TAG, Log.getStackTraceString (e)); }}
decrittografia
La decrittografia è fondamentalmente il processo di crittografia in senso inverso. Decrittografia avviene tramite la chiave privata della coppia di chiavi. Abbiamo poi inizializzato un Cipher con lo stesso algoritmo di trasformazione usato per la crittografia, ma messi al Cipher.DECRYPT_MODE. La stringa Base64 viene decodificato in un byte [], che viene quindi posto in un ByteArrayInputStream. Utilizziamo quindi un CipherInputStream per decifrare i dati in un byte []. Questo è quindi visualizzato come una Stringa
decryptString public void (String alias) {try {KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry (alias, null).; RSAPrivateKey PrivateKey = (RSAPrivateKey) privateKeyEntry.getPrivateKey (); Uscita Cipher = Cipher.getInstance ("RSA / BCE / PKCS1Padding", "AndroidOpenSSL"); output.init (Cipher.DECRYPT_MODE, PrivateKey); String testo cifrato = encryptedText.getText () toString ().; CipherInputStream cipherInputStream = new CipherInputStream (new ByteArrayInputStream (Base64.decode (testo cifrato, Base64.DEFAULT)), uscita); ArrayList & lt; Byte & gt; Valori = new ArrayList & lt; & gt; (); int nextByte; while ((nextByte = cipherInputStream.read ()) = -1) {values.add ((byte) nextByte); } Byte [] byte = new byte [values.size ()]; for (int i = 0; i & lt; bytes.length; i ++) {bytes [i] = values.get (i) .byteValue (); } String finalText = new String (byte, 0, bytes.length, "UTF-8"); decryptedText.setText (finalText); } Catch (Exception e) {Toast.makeText (questo, "Eccezione" + e.getMessage () + "si è verificato", Toast.LENGTH_LONG) .Show (); Log.e (TAG, Log.getStackTraceString (e)); }}
sviluppatori Android Newsletter
arrotondare
L'archivio chiavi Android rende la creazione e la gestione delle chiavi app una brezza, e fornisce una cassetta di sicurezza e volta relativamente sicuro per le applicazioni di memorizzare le chiavi di crittografia. Naturalmente la chiave pubblica può essere inviato anche al server, e la chiave pubblica del server inviato alle applicazioni, al fine di garantire la sicurezza delle comunicazioni tra l'applicazione e il server. Come al solito, il codice sorgente completo è disponibile sul github per l'uso a vostro piacimento. Per aggiunte, correzioni e / o discussioni, lascia un commento qui sotto, siamo ansiosi di sentire da voi.
Android Autorità