Fingerprint API

22 febbraio 2017

Una delle principali novità introdotte con Android Marshmallow sono le Fingerprint API, dedicate all’autenticazione di utenti tramite le impronte digitali. Ovviamente, questa funzionalità è disponibile solo per dispositivi che integrano un apposito scanner.

Per poter far uso di queste classi, è necessario:

  • impostare il livello minimo di API alla versione 23 nel file build.gradle del modulo applicativo;
  • definire la permission di tipo FINGERPRINT nel file AndroidManifest.xml:
    <uses-permission
            android:name="android.permission.USE_FINGERPRINT" />
    
  • installare una versione degli Android SDK Tools non inferiore alla 24.3.

Il meccanismo sarà inoltre utilizzabile solo su dispositivi ove sia stata registrata almeno un’impronta digitale, procedura che può essere attuata tramite il menu Sicurezza, tra le Impostazioni del dispositivo.

In questa lezione implementeremo un’app dal funzionamento molto semplice: un layout contenente un pulsante che mostra una notifica Toast. Il nostro scopo è impedirne l’accesso non autorizzato, vincolandone la fruizione al superamento della verifica delle impronte digitali.

Figura 1. L’Activity principale (click per ingrandire)

L'Activity principale

Il lavoro di autenticazione viene reso piuttosto agevole dalle API disponibili. Infatti, nel metodo onCreate, richiederemo l’istanza di due servizi di sistema: FingerprintManager, che si occuperà di svolgere l’autenticazione tramite impronte digitali tramite il metodo authenticate, e KeyguardManager, che verificherà la presenza nel dispositivo di un blocco all’accesso (mediante PIN, sequenza di qualche genere o altro):

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        keyguardManager =
                (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
        fingerprintManager =
                (FingerprintManager) getSystemService(FINGERPRINT_SERVICE);

        if (!keyguardManager.isKeyguardSecure()) {
            Toast.makeText(this, "Il dispositivo non dispone di alcun blocco",Toast.LENGTH_LONG).show();
            return;
        }

        Cipher cipher = CipherGenerator.getCipher();
        if (cipher != null) {
            cryptoObject = new FingerprintManager.CryptoObject(cipher);
        }
    }

Si noti che, sul finire del metodo, viene inizializzato un oggetto definito come CryptoObject. Questo sarà passato al FingerprintManager per rendere sicura la comunicazione con lo scanner di impronte, come vedremo più avanti.

L’inizio dell’autenticazione coinciderà con il metodo onResume che, come sappiamo, coincide con l’inizio dell’interazione utente con l’Activity:

 protected void onResume() {
        super.onResume();

        String msg;
        String title;
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT)
                == PackageManager.PERMISSION_GRANTED && fingerprintManager.hasEnrolledFingerprints() && cryptoObject!=null) {
            cancellationSignal = new CancellationSignal();
            fingerprintManager.authenticate(cryptoObject, cancellationSignal, 0, callback, null);
            title = "Autenticazione richiesta";
            msg = "Quest'app è soggetta a controllo mediante impronta digitale";
        }
        else {
            title = "Errore";
            msg = "Impossibile procedere con autenticazione via impronta digitale";
        }


        dialog = new AlertDialog.Builder(this)
                .setIcon(R.drawable.ic_fp_40px)
                .setTitle(title)
                .setCancelable(false)
                .setMessage(msg)
                .create();
        dialog.show();
    }

Nel metodo onResume, l’autenticazione avrà realmente inizio solo se le permission necessarie sono state assegnate, e se almeno un’impronta digitale è stata registrata. Il meccanismo che utilizzeremo per impedire di usare l’app fino allo sblocco consisterà, per semplicità, in una normale AlertDialog non cancellabile.

Il metodo authenticate del FingerprintManager, al momento dell’invocazione, riceve il CryptoObject cui abbiamo già accennato, un CancellationSignal che useremo nell’onPause per interdire il meccanismo di autenticazione, ed un riferimento ad un listener i cui metodi saranno invocati all’esito del riconoscimento delle impronte:

    private FingerprintManager.AuthenticationCallback callback= new FingerprintManager.AuthenticationCallback() {

        @Override
        public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
            // autenticazione riuscita, sblocchiamo app
            dialog.dismiss();
        }

        /*
         * omesso il codice dei metodi
         *  onAuthenticationError,
         *  onAuthenticationHelp e 
         *  onAuthenticationFailed
         */
    };

Il metodo onAuthenticationSucceeded viene invocato quando viene riconosciuta una impronta digitale valida: interpreteremo ciò come autorizzazione ad utilizzare l’applicazione. Nel nostro esempio, lo sblocco si estrinsecherà semplicemente nella chiusura della finestra di dialogo.

Crittografia e Keystore

Vediamo ora da dove proviene il Cipher che abbiamo richiamato per creare il CryptoObject. In Android, sono disponibili le funzionalità di crittografia del mondo Java:

  • JCA (Java Cryptography Architecture), corrispondente al package java.security, che include molte funzionalità fondamentali su crittografia e firma digitale;
  • JCE (Java Cryptography Extension), etichettata con il package javax.crypto, che mette a disposizione API di più alto livello, come appunto il Cipher che useremo.

Un oggetto Cipher verrà proprio inizializzato con il metodo definito nella nostra classe CipherGenerator:

public class CipherGenerator {

    public static Cipher getCipher() {
        KeyStore keyStore;
        KeyGenerator keyGenerator;
        Cipher cipher;
        String keyName = "chiave_accesso";
        String keystore="AndroidKeyStore";

        try {
            // 1. recupero di un'istanza del keystore
            keyStore = KeyStore.getInstance(keystore);

            keyStore.load(null);

            // 2. recuperiamo un oggetto KeyGenerator, il generatore di chiavi
            keyGenerator = KeyGenerator.getInstance(
                    KeyProperties.KEY_ALGORITHM_AES, keystore);

            // 3. parametri di inizializzazione del KeyGenerator
            KeyGenParameterSpec keygenspec=new
                    KeyGenParameterSpec.Builder(keyName,
                    KeyProperties.PURPOSE_ENCRYPT |
                            KeyProperties.PURPOSE_DECRYPT)
                    .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                    .setUserAuthenticationRequired(true)
                    .setEncryptionPaddings(
                            KeyProperties.ENCRYPTION_PADDING_PKCS7)
                    .build();

            // 4. inizializzazione del KeyGenerator
            keyGenerator.init(keygenspec);

            // 5. generazione di chiavi
            keyGenerator.generateKey();

        } catch (GeneralSecurityException | IOException e) {
            // comprende NoSuchAlgorithmException, NoSuchProviderException, KeyStoreException
            return null;
        }

        try {
            cipher=Cipher.getInstance ("AES/CBC/PKCS7Padding");
            SecretKey skey = (SecretKey) keyStore.getKey(keyName,
                    null);
            cipher.init(Cipher.ENCRYPT_MODE, skey);
            return cipher;
        } catch (GeneralSecurityException e) {
            return null;
        }
    }
}

Come si vede nelle ultime righe, il Cipher viene ottenuto con il metodo getInstance, prassi comune in JCE come applicazione del pattern Factory. Forniamo una stringa di determinazione dell’algoritmo detta, in genere, transformation (nel nostro esempio “AES/CBC/PKCS7Padding”) che specifica in maniera compatta, rispettivamente, l’algoritmo crittografico da usare (AES), la modalità di trattamento dei blocchi (CBC) ed il tipo di padding da applicare (PKCS7Padding). Si faccia attenzione che le combinazioni utilizzabili sono indicate nella documentazione ufficiale, e non tutte sono disponibili in ogni verisone di API Android: AES/CBC/PKCS7Padding, da noi usata, è stata introdotta solo con Marshmallow. Il Cipher, inoltre, dovrà essere dotato di una chiave crittografica, che gestiremo mediante AndroidKeyStore, un meccanismo di sistema che permette di custodirle al sicuro.

Tutte le lezioni

NFC

1 ... 45 46 47 ... 82

Se vuoi aggiornamenti su Fingerprint API inserisci la tua e-mail nel box qui sotto:
Tags:
 
X
Se vuoi aggiornamenti su Fingerprint API

inserisci la tua e-mail nel box qui sotto:

Ho letto e acconsento l'informativa sulla privacy

Acconsento al trattamento di cui al punto 3 dell'informativa sulla privacy