Capture fingerprint from smartphone and save to a file

23,003

Android fingerprint authentication is designed specifically to make it impossible to do what you want.

The design ensures that fingerprint image data is never available within the Android OS at all. Even if you root the device or compromise the kernel, this data is simply not available. Fingerprint images are required to be passed securely from the fingerprint scanner to the secure hardware that does the fingerprint matching. This is actually an Android compliance requirement. The reason is that fingerprints are important personal information which should not be allowed to leak.

Regarding your alternate suggestion, I can't image why any manufacturer would make that possible.

If you want to use Android as a universal key, you should do something like:

  1. Generate a public/private key pair on the device with AndroidKeyStore (I recommend EC), specifying that the key may only be used with fingerprint authentication.
  2. Extract the public key and register it with the server.
  3. To "unlock" with your universal key, conduct a challenge-response protocol with the server, like:
    1. Generate a random value on the server, say, 32 bytes.
    2. Send the random value to the phone.
    3. Create a Signature object with your AndroidKeyStore key, init it and wrap it in a FingerprintManager.CryptoObject.
    4. Do fingerprint authentication and when it completes sign the random value.
    5. Send the signed random value to the server.
    6. Have the server verify the random value and signature.

If you want to do fingerprint verification on the server you'll need to get some other sort of fingerprint scanner. You can't do this with an Android (or iOS) phone.

Share:
23,003
Giovanni Marcolla
Author by

Giovanni Marcolla

Updated on February 07, 2020

Comments

  • Giovanni Marcolla
    Giovanni Marcolla about 4 years

    I'm doing a project where I need to transfer the result of the fingerprint scan capture to a local server using NFC connection, and then receive an answer from the server.

    So far I manage to understand how to do the transfer and scan the fringerprint using google's API, but the problem is that I can't get the scan result to send it for the server to complete the authentication.

    I'm using google's fingerprint library from API23.

    MainActivity:

    package com.gmtechnology.smartalarm;
    
    import android.Manifest;
    import android.app.KeyguardManager;
    import android.content.Intent;
    import android.content.pm.PackageManager;
    import android.hardware.fingerprint.FingerprintManager;
    import android.net.Uri;
    import android.nfc.NdefMessage;
    import android.nfc.NdefRecord;
    import android.nfc.NfcAdapter;
    import android.os.Build;
    import android.os.Bundle;
    import android.os.Environment;
    import android.provider.Settings;
    import android.security.keystore.KeyGenParameterSpec;
    import android.security.keystore.KeyPermanentlyInvalidatedException;
    import android.security.keystore.KeyProperties;
    import android.support.design.widget.FloatingActionButton;
    import android.support.design.widget.NavigationView;
    import android.support.v4.app.ActivityCompat;
    import android.support.v4.view.GravityCompat;
    import android.support.v4.widget.DrawerLayout;
    import android.support.v7.app.ActionBarDrawerToggle;
    import android.support.v7.app.AppCompatActivity;
    import android.support.v7.widget.Toolbar;
    import android.view.Menu;
    import android.view.MenuItem;
    import android.view.View;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import java.io.File;
    import java.io.IOException;
    import java.security.InvalidAlgorithmParameterException;
    import java.security.InvalidKeyException;
    import java.security.KeyStore;
    import java.security.KeyStoreException;
    import java.security.NoSuchAlgorithmException;
    import java.security.NoSuchProviderException;
    import java.security.UnrecoverableKeyException;
    import java.security.cert.CertificateException;
    
    import javax.crypto.Cipher;
    import javax.crypto.KeyGenerator;
    import javax.crypto.NoSuchPaddingException;
    import javax.crypto.SecretKey;
    
    
    public class MainActivity extends AppCompatActivity
        implements NavigationView.OnNavigationItemSelectedListener {
    
    private static final String KEY_NAME = "main_key";
    protected FingerprintManager fingerprintManager;
    protected KeyguardManager keyguardManager;
    private KeyStore keyStore;
    protected KeyGenerator keyGenerator;
    private Cipher cipher;
    protected FingerprintManager.CryptoObject cryptoObject;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        TextView state = (TextView) findViewById(R.id.state);
    
        FloatingActionButton lock = (FloatingActionButton) findViewById(R.id.lock);
        FloatingActionButton unlock = (FloatingActionButton) findViewById(R.id.unlock);
        FloatingActionButton start = (FloatingActionButton) findViewById(R.id.start);
        FloatingActionButton stop = (FloatingActionButton) findViewById(R.id.stop);
    
        PackageManager pm = this.getPackageManager();
        // Check whether NFC is available on device
        if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC)) {
            // NFC is not available on the device.
            Toast.makeText(this, "The device does not has NFC hardware",
                    Toast.LENGTH_SHORT).show();
        }
        // Check whether device is running Android 4.1 or higher
        else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            // Android Beam feature is not supported.
            Toast.makeText(this, "Wrong android version",
                    Toast.LENGTH_SHORT).show();
        }
    
        keyguardManager =
                (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
        fingerprintManager =
                (FingerprintManager) getSystemService(FINGERPRINT_SERVICE);
    
        if (!keyguardManager.isKeyguardSecure()) {
    
            Toast.makeText(this,
                    "Lock screen security not enabled in Settings",
                    Toast.LENGTH_LONG).show();
            return;
        }
    
        if (ActivityCompat.checkSelfPermission(this,
                Manifest.permission.USE_FINGERPRINT) !=
                PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(this,
                    "Fingerprint authentication permission not enabled",
                    Toast.LENGTH_LONG).show();
    
            return;
        }
    
        if (!fingerprintManager.hasEnrolledFingerprints()) {
    
            // This happens when no fingerprints are registered.
            Toast.makeText(this,
                    "Register at least one fingerprint in Settings",
                    Toast.LENGTH_LONG).show();
            return;
        }
    
        generateKey();
    
        if (cipherInit()) {
            cryptoObject = new FingerprintManager.CryptoObject(cipher);
            //cryptoObject == the scanned fingerprint
            FingerprintHandler helper = new FingerprintHandler(this);
            helper.startAuth(fingerprintManager, cryptoObject, lock, unlock, start, stop, state);
        }
    
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
    
        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawer.setDrawerListener(toggle);
        toggle.syncState();
    
        NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
        navigationView.setNavigationItemSelectedListener(this);
    
        state(1, lock, unlock, start, stop, state);
    }
    
    public void sendFile(View view) {
        NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
    
        String command = "command";
    
        if(!nfcAdapter.isEnabled()){
    
            Toast.makeText(this, "Please enable NFC.",
                    Toast.LENGTH_SHORT).show();
            startActivity(new Intent(Settings.ACTION_NFC_SETTINGS));
        }
    
        else if(!nfcAdapter.isNdefPushEnabled()) {
    
            Toast.makeText(this, "Please enable Android Beam.",
                    Toast.LENGTH_SHORT).show();
            startActivity(new Intent(Settings.ACTION_NFCSHARING_SETTINGS));
        }
    
        else {
    
            if(view.getId() == R.id.lock) {
    
                command = "lock";
    
            }
    
            else if(view.getId() == R.id.unlock) {
    
                command = "unlock";
    
            }
    
            else if(view.getId() == R.id.start) {
    
                command = "start";
    
            }
    
            else if(view.getId() == R.id.stop) {
    
                command = "stop";
    
            }
    
            NdefRecord ndefRecord = NdefRecord.createMime("text/plain", command.getBytes());
            NdefMessage ndefMessage = new NdefMessage(ndefRecord);
    
            String fileName = "wallpaper.png";
    
            // Retrieve the path to the user's public pictures directory
            File fileDirectory = Environment
                    .getExternalStoragePublicDirectory(
                            Environment.DIRECTORY_PICTURES);
    
            // Create a new file using the specified directory and name
            File fileToTransfer = new File(fileDirectory, fileName);
            fileToTransfer.setReadable(true, false);
    
            nfcAdapter.setBeamPushUris(
                    new Uri[]{Uri.fromFile(fileToTransfer)}, this);
            nfcAdapter.setNdefPushMessage(ndefMessage, this);
        }
    }
    
    protected void generateKey() {
        try {
            keyStore = KeyStore.getInstance("AndroidKeyStore");
        } catch (Exception e) {
            e.printStackTrace();
        }
    
        try {
            keyGenerator = KeyGenerator.getInstance(
                    KeyProperties.KEY_ALGORITHM_AES,
                    "AndroidKeyStore");
        } catch (NoSuchAlgorithmException |
                NoSuchProviderException e) {
            throw new RuntimeException(
                    "Failed to get KeyGenerator instance", e);
        }
    
        try {
            keyStore.load(null);
            keyGenerator.init(new
                    KeyGenParameterSpec.Builder(KEY_NAME,
                    KeyProperties.PURPOSE_ENCRYPT |
                            KeyProperties.PURPOSE_DECRYPT)
                    .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                    .setUserAuthenticationRequired(true)
                    .setEncryptionPaddings(
                            KeyProperties.ENCRYPTION_PADDING_PKCS7)
                    .build());
            keyGenerator.generateKey();
        } catch (NoSuchAlgorithmException |
                InvalidAlgorithmParameterException
                | CertificateException | IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    public boolean cipherInit() {
        try {
            cipher = Cipher.getInstance(
                    KeyProperties.KEY_ALGORITHM_AES + "/"
                            + KeyProperties.BLOCK_MODE_CBC + "/"
                            + KeyProperties.ENCRYPTION_PADDING_PKCS7);
        } catch (NoSuchAlgorithmException |
                NoSuchPaddingException e) {
            throw new RuntimeException("Failed to get Cipher", e);
        }
    
        try {
            keyStore.load(null);
            SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME,
                    null);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            return true;
        } catch (KeyPermanentlyInvalidatedException e) {
            return false;
        } catch (KeyStoreException | CertificateException
                | UnrecoverableKeyException | IOException
                | NoSuchAlgorithmException | InvalidKeyException e) {
            throw new RuntimeException("Failed to init Cipher", e);
        }
    }
    
    
    @Override
    public void onBackPressed() {
        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        if (drawer.isDrawerOpen(GravityCompat.START)) {
            drawer.closeDrawer(GravityCompat.START);
        } else {
            super.onBackPressed();
        }
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
    
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
    
        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }
    
        return super.onOptionsItemSelected(item);
    }
    
    @SuppressWarnings("StatementWithEmptyBody")
    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
        // Handle navigation view item clicks here.
        int id = item.getItemId();
    
        if (id == R.id.nav_camera) {
            // Handle the camera action
        } else if (id == R.id.nav_gallery) {
    
        } else if (id == R.id.nav_share) {
    
        } else if (id == R.id.nav_send) {
    
        }
    
        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        drawer.closeDrawer(GravityCompat.START);
        return true;
    }
    
    
    public void state (int s, FloatingActionButton lock, FloatingActionButton unlock, FloatingActionButton start, FloatingActionButton stop, TextView state) {
    
        if (s == 1) {
            state.setText(R.string.scan);
            state.setTextColor(0xFF970E0E);
            lock.setEnabled(false);
            lock.hide();
            unlock.setEnabled(false);
            unlock.hide();
            start.setEnabled(false);
            start.hide();
            stop.setEnabled(false);
            stop.hide();
        }
    
        if (s == 2) {
            state.setText(R.string.done);
            state.setTextColor(0xFF149926);
            lock.setEnabled(true);
            lock.show();
            unlock.setEnabled(true);
            unlock.show();
            start.setEnabled(true);
            start.show();
            stop.setEnabled(true);
            stop.show();
        }
    
        if (s == 3) {
            state.setText(R.string.error);
            state.setTextColor(0xFF970E0E);
            lock.setEnabled(false);
            lock.hide();
            unlock.setEnabled(false);
            unlock.hide();
            start.setEnabled(false);
            start.hide();
            stop.setEnabled(false);
            stop.hide();
        }
    }
    }
    

    FingerprintHandler:

    package com.gmtechnology.smartalarm;
    
    import android.Manifest;
    import android.content.Context;
    import android.content.pm.PackageManager;
    import android.hardware.fingerprint.FingerprintManager;
    import android.os.CancellationSignal;
    import android.support.design.widget.FloatingActionButton;
    import android.support.v4.app.ActivityCompat;
    import android.widget.TextView;
    
    public class FingerprintHandler extends
        FingerprintManager.AuthenticationCallback {
    
    protected CancellationSignal cancellationSignal;
    private Context appContext;
    
    
    private FloatingActionButton mlock;
    private FloatingActionButton mstart;
    private FloatingActionButton munlock;
    private FloatingActionButton mstop;
    private TextView mstate;
    
    
    public FingerprintHandler(Context context) {
        appContext = context;
    }
    
    public void startAuth(FingerprintManager manager,
                          FingerprintManager.CryptoObject cryptoObject, FloatingActionButton lock, FloatingActionButton unlock, FloatingActionButton start, FloatingActionButton stop, TextView state) {
    
        mlock = lock;
        munlock = unlock;
        mstart = start;
        mstop = stop;
        mstate = state;
    
        cancellationSignal = new CancellationSignal();
    
        if (ActivityCompat.checkSelfPermission(appContext,
                Manifest.permission.USE_FINGERPRINT) !=
                PackageManager.PERMISSION_GRANTED) {
            return;
        }
        manager.authenticate(cryptoObject, cancellationSignal, 0, this, null);
    }
    
    @Override
    public void onAuthenticationHelp(int helpMsgId,
                                     CharSequence helpString) {
    
        new MainActivity().state(3, mlock, munlock, mstart, mstop, mstate);
    }
    
    @Override
    public void onAuthenticationFailed() {
    
        new MainActivity().state(3, mlock, munlock, mstart, mstop, mstate);
    }
    
    @Override
    public void onAuthenticationSucceeded(
            FingerprintManager.AuthenticationResult result) {
    
        new MainActivity().state(2, mlock, munlock, mstart, mstop, mstate);
    }
    }
    

    If anyone could point me out how I can capture and convert the scan result to a format where I can send it via NFC that would be awesome!

    PS: I'm not a programmer, please go easy on me T^T

    restating the possibilities based on Micheal answers: Would there be anyway to directly connecting the sensor to the server using NFC so that the data can be directly send to the server for authentication without saving it? The whole idea is that the authentication is done by an outside server not the smartphone, this way making the smartphone an "universal key", so that ANY smartphone can be used to authenticate to any server, using the correct application of course, or maybe somehow trick the smartphone into using the database connect via NFC to compare the fingerprint, this way the current information is on a safe memory, but the database used to compare the fingerprint is on a remote server. I know those sound tricky, maybe even "crazy", but if there is a possibility to do it even outside of "proper" ways that would be enought, it's only for reaserch and study propouses.

  • Ravi Yadav
    Ravi Yadav almost 4 years
    How banking apps like iMobile does that. They said that the fingerprint data is not stored on their server.its locally saved. How they map particular user with fingerprint. medianama.com/2017/09/223-icici-bank-mobile-banking-fingerpr‌​int
  • divegeek
    divegeek almost 4 years
    As I said above, fingerprints never leave the device. They never even leave the secure area of the device; they're not available in Android, so they're definitely not sent to the server.