Android
Huella digital API en Android
Buscar..
Observaciones
ver también
Añadiendo el escáner de huellas dactilares en la aplicación de Android
Android es compatible con la API de huellas dactilares de Android 6.0 (Marshmallow) SDK 23
Para usar esta función en su aplicación, primero agregue el permiso USE_FINGERPRINT en su manifiesto.
<uses-permission
android:name="android.permission.USE_FINGERPRINT" />
Aquí el procedimiento a seguir.
Primero debe crear una clave simétrica en el Key Store de Android usando KeyGenerator, que solo se puede usar después de que el usuario se haya autenticado con la huella digital y pasar un KeyGenParameterSpec.
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
keyPairGenerator.initialize(
new KeyGenParameterSpec.Builder(KEY_NAME,
KeyProperties.PURPOSE_SIGN)
.setDigests(KeyProperties.DIGEST_SHA256)
.setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1"))
.setUserAuthenticationRequired(true)
.build());
keyPairGenerator.generateKeyPair();
Al establecer KeyGenParameterSpec.Builder.setUserAuthenticationRequired en true, puede permitir el uso de la clave solo después de que el usuario la autentifique, incluso cuando se autentica con la huella digital del usuario.
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
PublicKey publicKey =
keyStore.getCertificate(MainActivity.KEY_NAME).getPublicKey();
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
PrivateKey key = (PrivateKey) keyStore.getKey(KEY_NAME, null);
Luego comience a escuchar una huella dactilar en el sensor de huellas dactilares llamando a FingerprintManager.authenticate con un cifrado inicializado con la clave simétrica creada. O, alternativamente, puede recurrir a la contraseña verificada del lado del servidor como autenticador.
Crea e inicializa FingerprintManger
desde fingerprintManger.class
getContext().getSystemService(FingerprintManager.class)
Para autenticar el uso de la Api de FingerprintManger
y crear una subclase usando
FingerprintManager.AuthenticationCallback
e invalida los métodos
onAuthenticationError
onAuthenticationHelp
onAuthenticationSucceeded
onAuthenticationFailed
Para comenzar
Para comenzar, escuchar el método de autentificación de llamada de evento fingerPrint con crypto
fingerprintManager
.authenticate(cryptoObject, mCancellationSignal, 0 , this, null);
Cancelar
para dejar de escuchar la llamada del escáner
android.os.CancellationSignal;
Una vez que se verifica la huella digital (o contraseña), se devuelve la devolución de llamada FingerprintManager.AuthenticationCallback # onAuthenticationSucceeded ().
@Override
public void onAuthenticationSucceeded(AuthenticationResult result) {
}
Cómo utilizar la API de huellas digitales de Android para guardar las contraseñas de los usuarios
Esta clase auxiliar de ejemplo interactúa con el administrador de huellas digitales y realiza el cifrado y descifrado de la contraseña. Tenga en cuenta que el método utilizado para el cifrado en este ejemplo es AES. Esta no es la única forma de cifrar y existen otros ejemplos . En este ejemplo, los datos se cifran y descifran de la siguiente manera:
Cifrado
- El usuario le da al ayudante la contraseña no encriptada deseada.
- El usuario debe proporcionar huella digital.
- Una vez autenticado, el ayudante obtiene una clave del
KeyStore
y cifra la contraseña mediante unCipher
. - La contraseña y la sal IV (la IV se vuelve a crear para cada cifrado y no se reutiliza) se guardan en preferencias compartidas para usarlas más adelante en el proceso de descifrado.
Descifrado:
- El usuario solicita descifrar la contraseña.
- El usuario debe proporcionar huella digital.
- El ayudante construye un
Cipher
utilizando el IV y una vez que el usuario se autentica, KeyStore obtiene una clave delKeyStore
y descifra la contraseña.
public class FingerPrintAuthHelper {
private static final String FINGER_PRINT_HELPER = "FingerPrintAuthHelper";
private static final String ENCRYPTED_PASS_SHARED_PREF_KEY = "ENCRYPTED_PASS_SHARED_PREF_KEY";
private static final String LAST_USED_IV_SHARED_PREF_KEY = "LAST_USED_IV_SHARED_PREF_KEY";
private static final String MY_APP_ALIAS = "MY_APP_ALIAS";
private KeyguardManager keyguardManager;
private FingerprintManager fingerprintManager;
private final Context context;
private KeyStore keyStore;
private KeyGenerator keyGenerator;
private String lastError;
public interface Callback {
void onSuccess(String savedPass);
void onFailure(String message);
void onHelp(int helpCode, String helpString);
}
public FingerPrintAuthHelper(Context context) {
this.context = context;
}
public String getLastError() {
return lastError;
}
@TargetApi(Build.VERSION_CODES.M)
public boolean init() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
setError("This Android version does not support fingerprint authentication");
return false;
}
keyguardManager = (KeyguardManager) context.getSystemService(KEYGUARD_SERVICE);
fingerprintManager = (FingerprintManager) context.getSystemService(FINGERPRINT_SERVICE);
if (!keyguardManager.isKeyguardSecure()) {
setError("User hasn't enabled Lock Screen");
return false;
}
if (!hasPermission()) {
setError("User hasn't granted permission to use Fingerprint");
return false;
}
if (!fingerprintManager.hasEnrolledFingerprints()) {
setError("User hasn't registered any fingerprints");
return false;
}
if (!initKeyStore()) {
return false;
}
return false;
}
@Nullable
@RequiresApi(api = Build.VERSION_CODES.M)
private Cipher createCipher(int mode) throws NoSuchPaddingException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, InvalidKeyException, InvalidAlgorithmParameterException {
Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" +
KeyProperties.BLOCK_MODE_CBC + "/" +
KeyProperties.ENCRYPTION_PADDING_PKCS7);
Key key = keyStore.getKey(MY_APP_ALIAS, null);
if (key == null) {
return null;
}
if(mode == Cipher.ENCRYPT_MODE) {
cipher.init(mode, key);
byte[] iv = cipher.getIV();
saveIv(iv);
} else {
byte[] lastIv = getLastIv();
cipher.init(mode, key, new IvParameterSpec(lastIv));
}
return cipher;
}
@NonNull
@RequiresApi(api = Build.VERSION_CODES.M)
private KeyGenParameterSpec createKeyGenParameterSpec() {
return new KeyGenParameterSpec.Builder(MY_APP_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build();
}
@RequiresApi(api = Build.VERSION_CODES.M)
private boolean initKeyStore() {
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
keyStore.load(null);
if (getLastIv() == null) {
KeyGenParameterSpec keyGeneratorSpec = createKeyGenParameterSpec();
keyGenerator.init(keyGeneratorSpec);
keyGenerator.generateKey();
}
} catch (Throwable t) {
setError("Failed init of keyStore & keyGenerator: " + t.getMessage());
return false;
}
return true;
}
@RequiresApi(api = Build.VERSION_CODES.M)
private void authenticate(CancellationSignal cancellationSignal, FingerPrintAuthenticationListener authListener, int mode) {
try {
if (hasPermission()) {
Cipher cipher = createCipher(mode);
FingerprintManager.CryptoObject crypto = new FingerprintManager.CryptoObject(cipher);
fingerprintManager.authenticate(crypto, cancellationSignal, 0, authListener, null);
} else {
authListener.getCallback().onFailure("User hasn't granted permission to use Fingerprint");
}
} catch (Throwable t) {
authListener.getCallback().onFailure("An error occurred: " + t.getMessage());
}
}
private String getSavedEncryptedPassword() {
SharedPreferences sharedPreferences = getSharedPreferences();
if (sharedPreferences != null) {
return sharedPreferences.getString(ENCRYPTED_PASS_SHARED_PREF_KEY, null);
}
return null;
}
private void saveEncryptedPassword(String encryptedPassword) {
SharedPreferences.Editor edit = getSharedPreferences().edit();
edit.putString(ENCRYPTED_PASS_SHARED_PREF_KEY, encryptedPassword);
edit.commit();
}
private byte[] getLastIv() {
SharedPreferences sharedPreferences = getSharedPreferences();
if (sharedPreferences != null) {
String ivString = sharedPreferences.getString(LAST_USED_IV_SHARED_PREF_KEY, null);
if (ivString != null) {
return decodeBytes(ivString);
}
}
return null;
}
private void saveIv(byte[] iv) {
SharedPreferences.Editor edit = getSharedPreferences().edit();
String string = encodeBytes(iv);
edit.putString(LAST_USED_IV_SHARED_PREF_KEY, string);
edit.commit();
}
private SharedPreferences getSharedPreferences() {
return context.getSharedPreferences(FINGER_PRINT_HELPER, 0);
}
@RequiresApi(api = Build.VERSION_CODES.M)
private boolean hasPermission() {
return ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) == PackageManager.PERMISSION_GRANTED;
}
@RequiresApi(api = Build.VERSION_CODES.M)
public void savePassword(@NonNull String password, CancellationSignal cancellationSignal, Callback callback) {
authenticate(cancellationSignal, new FingerPrintEncryptPasswordListener(callback, password), Cipher.ENCRYPT_MODE);
}
@RequiresApi(api = Build.VERSION_CODES.M)
public void getPassword(CancellationSignal cancellationSignal, Callback callback) {
authenticate(cancellationSignal, new FingerPrintDecryptPasswordListener(callback), Cipher.DECRYPT_MODE);
}
@RequiresApi(api = Build.VERSION_CODES.M)
public boolean encryptPassword(Cipher cipher, String password) {
try {
// Encrypt the text
if(password.isEmpty()) {
setError("Password is empty");
return false;
}
if (cipher == null) {
setError("Could not create cipher");
return false;
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher);
byte[] bytes = password.getBytes(Charset.defaultCharset());
cipherOutputStream.write(bytes);
cipherOutputStream.flush();
cipherOutputStream.close();
saveEncryptedPassword(encodeBytes(outputStream.toByteArray()));
} catch (Throwable t) {
setError("Encryption failed " + t.getMessage());
return false;
}
return true;
}
private byte[] decodeBytes(String s) {
final int len = s.length();
// "111" is not a valid hex encoding.
if( len%2 != 0 )
throw new IllegalArgumentException("hexBinary needs to be even-length: "+s);
byte[] out = new byte[len/2];
for( int i=0; i<len; i+=2 ) {
int h = hexToBin(s.charAt(i ));
int l = hexToBin(s.charAt(i+1));
if( h==-1 || l==-1 )
throw new IllegalArgumentException("contains illegal character for hexBinary: "+s);
out[i/2] = (byte)(h*16+l);
}
return out;
}
private static int hexToBin( char ch ) {
if( '0'<=ch && ch<='9' ) return ch-'0';
if( 'A'<=ch && ch<='F' ) return ch-'A'+10;
if( 'a'<=ch && ch<='f' ) return ch-'a'+10;
return -1;
}
private static final char[] hexCode = "0123456789ABCDEF".toCharArray();
public String encodeBytes(byte[] data) {
StringBuilder r = new StringBuilder(data.length*2);
for ( byte b : data) {
r.append(hexCode[(b >> 4) & 0xF]);
r.append(hexCode[(b & 0xF)]);
}
return r.toString();
}
@NonNull
private String decipher(Cipher cipher) throws IOException, IllegalBlockSizeException, BadPaddingException {
String retVal = null;
String savedEncryptedPassword = getSavedEncryptedPassword();
if (savedEncryptedPassword != null) {
byte[] decodedPassword = decodeBytes(savedEncryptedPassword);
CipherInputStream cipherInputStream = new CipherInputStream(new ByteArrayInputStream(decodedPassword), cipher);
ArrayList<Byte> values = new ArrayList<>();
int nextByte;
while ((nextByte = cipherInputStream.read()) != -1) {
values.add((byte) nextByte);
}
cipherInputStream.close();
byte[] bytes = new byte[values.size()];
for (int i = 0; i < values.size(); i++) {
bytes[i] = values.get(i).byteValue();
}
retVal = new String(bytes, Charset.defaultCharset());
}
return retVal;
}
private void setError(String error) {
lastError = error;
Log.w(FINGER_PRINT_HELPER, lastError);
}
@RequiresApi(Build.VERSION_CODES.M)
protected class FingerPrintAuthenticationListener extends FingerprintManager.AuthenticationCallback {
protected final Callback callback;
public FingerPrintAuthenticationListener(@NonNull Callback callback) {
this.callback = callback;
}
public void onAuthenticationError(int errorCode, CharSequence errString) {
callback.onFailure("Authentication error [" + errorCode + "] " + errString);
}
/**
* Called when a recoverable error has been encountered during authentication. The help
* string is provided to give the user guidance for what went wrong, such as
* "Sensor dirty, please clean it."
* @param helpCode An integer identifying the error message
* @param helpString A human-readable string that can be shown in UI
*/
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
callback.onHelp(helpCode, helpString.toString());
}
/**
* Called when a fingerprint is recognized.
* @param result An object containing authentication-related data
*/
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
}
/**
* Called when a fingerprint is valid but not recognized.
*/
public void onAuthenticationFailed() {
callback.onFailure("Authentication failed");
}
public @NonNull
Callback getCallback() {
return callback;
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
private class FingerPrintEncryptPasswordListener extends FingerPrintAuthenticationListener {
private final String password;
public FingerPrintEncryptPasswordListener(Callback callback, String password) {
super(callback);
this.password = password;
}
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
Cipher cipher = result.getCryptoObject().getCipher();
try {
if (encryptPassword(cipher, password)) {
callback.onSuccess("Encrypted");
} else {
callback.onFailure("Encryption failed");
}
} catch (Exception e) {
callback.onFailure("Encryption failed " + e.getMessage());
}
}
}
@RequiresApi(Build.VERSION_CODES.M)
protected class FingerPrintDecryptPasswordListener extends FingerPrintAuthenticationListener {
public FingerPrintDecryptPasswordListener(@NonNull Callback callback) {
super(callback);
}
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
Cipher cipher = result.getCryptoObject().getCipher();
try {
String savedPass = decipher(cipher);
if (savedPass != null) {
callback.onSuccess(savedPass);
} else {
callback.onFailure("Failed deciphering");
}
} catch (Exception e) {
callback.onFailure("Deciphering failed " + e.getMessage());
}
}
}
}
Esta actividad a continuación es un ejemplo muy básico de cómo obtener una contraseña guardada por el usuario e interactuar con el ayudante.
public class MainActivity extends AppCompatActivity {
private TextView passwordTextView;
private FingerPrintAuthHelper fingerPrintAuthHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
passwordTextView = (TextView) findViewById(R.id.password);
errorTextView = (TextView) findViewById(R.id.error);
View savePasswordButton = findViewById(R.id.set_password_button);
savePasswordButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
fingerPrintAuthHelper.savePassword(passwordTextView.getText().toString(), new CancellationSignal(), getAuthListener(false));
}
}
});
View getPasswordButton = findViewById(R.id.get_password_button);
getPasswordButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
fingerPrintAuthHelper.getPassword(new CancellationSignal(), getAuthListener(true));
}
}
});
}
// Start the finger print helper. In case this fails show error to user
private void startFingerPrintAuthHelper() {
fingerPrintAuthHelper = new FingerPrintAuthHelper(this);
if (!fingerPrintAuthHelper.init()) {
errorTextView.setText(fingerPrintAuthHelper.getLastError());
}
}
@NonNull
private FingerPrintAuthHelper.Callback getAuthListener(final boolean isGetPass) {
return new FingerPrintAuthHelper.Callback() {
@Override
public void onSuccess(String result) {
if (isGetPass) {
errorTextView.setText("Success!!! Pass = " + result);
} else {
errorTextView.setText("Encrypted pass = " + result);
}
}
@Override
public void onFailure(String message) {
errorTextView.setText("Failed - " + message);
}
@Override
public void onHelp(int helpCode, String helpString) {
errorTextView.setText("Help needed - " + helpString);
}
};
}
}