Capture fingerprint from smartphone and save to a file
Asked Answered
D

1

9

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.

Dualism answered 11/3, 2016 at 7:39 Comment(12)
What do you mean by "transfer the result of the fingerprint scan capture"? To get some kind of image representing the fingerprint? That's not allowed. What you as an application programmer get to know is whether the fingerprint matched one of the enrolled ones or not.Latimore
"Thus, raw images and processed fingerprint features must not be passed in untrusted memory. All such biometric data needs to be secured within sensor hardware or trusted memory." (source)Latimore
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.Dualism
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.Dualism
I sure hope not. Why would anyone want an image of their fingerprint to be sent off to who-knows-where? Just use the fingerprint API the way it was intended (unlock the CryptoObject associated with the user, and use it to sign some data that you send to the server).Latimore
The idea was to make the smartphone as a key, so that you don't need YOUR phone to unlock the server, but any phone, so that for exemple, you run out of baterry, you can use someones else phone to unclock the server.Dualism
Well, I'm not sure how you'd do that using fingerprints on a phone you've never used before. Perhaps you could use voice recognition instead.Latimore
That's why I need to either send the fingerprint to the server or used the server database to authenticate the fingerprint on the phone, either way one of them has to be linked via NFCDualism
I don't see how using NFC meets the requirement that "All such biometric data needs to be secured within sensor hardware or trusted memory". If the fingerprint leaves your phone through NFC then it's no longer secured. You'll probably have to rethink the idea for your application.Latimore
Like I said before, "if there is a possibility to do it even outside of "proper" ways that would be enough" steps to try to secure this data will taken later, all I need to get done now is get a hold of the fingerprint.Dualism
Those are requirements on the device manufacturer. So my point was that if they've done what they're supposed to do, there's no way for you to get the fingerprint data.Latimore
I see, I'm not sure what is used to do the matching, but it look,for a noob like me, that is generates a file based on a secure key?!?! If so, could we send this key to the server so it generates the same file from the database and them compare those files instead?so that if the file generated from the scan result is compared with the one form the database, this way teh actual fingerprint wouldn't be put in a unsecure location. I think this discusion is going too far here, if possible to do so I'll open a new question and close this one.Dualism
T
15

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.

Tabber answered 15/3, 2016 at 21:23 Comment(3)
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-fingerprintSolanum
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.Tabber
There are legitimate use cases for raw fingerprint image access: one of which is citizen mobility. Lack of proper APIs actually hinders development of tools like government apps that could be used by a citizen to remotely request an ID card, that needs that citizen's fingerprint as one of biometric data to be stored in the document itself.Eventful

© 2022 - 2024 — McMap. All rights reserved.