Skip to content

MASTG-KNOW-0002: FingerprintManager

Deprecated

Reason: A classe FingerprintManager foi descontinuada no Android 9 (API level 28) e não deve ser usada em novos aplicativos. Em vez disso, use a API BiometricPrompt ou a biblioteca Biometric para Android.

Use instead:

O Android 6.0 (API level 23) introduziu APIs públicas para autenticação de usuários por impressão digital, mas foi deprecado no Android 9 (API level 28). O acesso ao hardware de impressão digital é fornecido através da classe FingerprintManager. Um app pode solicitar autenticação por impressão digital instanciando um objeto FingerprintManager e chamando seu método authenticate. O chamador registra métodos de callback para lidar com os possíveis resultados do processo de autenticação (ou seja, sucesso, falha ou erro). Observe que este método não constitui uma prova robusta de que a autenticação por impressão digital foi realmente realizada - por exemplo, a etapa de autenticação pode ser removida via patch por um atacante, ou o callback de "sucesso" pode ser sobrescrito usando instrumentação dinâmica.

Você pode obter melhor segurança usando a API de impressão digital em conjunto com a classe KeyGenerator do Android. Com esta abordagem, uma chave simétrica é armazenada no Android KeyStore e desbloqueada com a impressão digital do usuário. Por exemplo, para permitir o acesso do usuário a um serviço remoto, uma chave AES é criada para criptografar o token de autenticação. Ao chamar setUserAuthenticationRequired(true) durante a criação da chave, garante-se que o usuário deve reautenticar para recuperá-la. O token de autenticação criptografado pode então ser salvo diretamente no dispositivo (por exemplo, via Shared Preferences). Este design é uma forma relativamente segura de garantir que o usuário realmente inseriu uma impressão digital autorizada.

Uma opção ainda mais segura é usar criptografia assimétrica. Aqui, o aplicativo móvel cria um par de chaves assimétricas no KeyStore e registra a chave pública no backend do servidor. Transações posteriores são então assinadas com a chave privada e verificadas pelo servidor usando a chave pública.

Implementação

Esta seção descreve como implementar autenticação biométrica usando a classe FingerprintManager. Lembre-se que esta classe está deprecada e a biblioteca Biometric deve ser usada como melhor prática. Esta seção é apenas para referência, caso você encontre tal implementação e precise analisá-la.

Comece procurando por chamadas FingerprintManager.authenticate. O primeiro parâmetro passado para este método deve ser uma instância de CryptoObject, que é uma classe wrapper para objetos criptográficos suportados pelo FingerprintManager. Se o parâmetro estiver definido como null, isso significa que a autorização por impressão digital é puramente vinculada a eventos, provavelmente criando um problema de segurança.

A criação da chave usada para inicializar o wrapper de cifra pode ser rastreada até o CryptoObject. Verifique se a chave foi criada usando a classe KeyGenerator além de ter setUserAuthenticationRequired(true) chamado durante a criação do objeto KeyGenParameterSpec (veja exemplos de código abaixo).

Certifique-se de verificar a lógica de autenticação. Para que a autenticação seja bem-sucedida, o endpoint remoto deve exigir que o cliente apresente o segredo recuperado do KeyStore, um valor derivado do segredo, ou um valor assinado com a chave privada do cliente (conforme descrito acima).

Implementar com segurança a autenticação por impressão digital requer seguir alguns princípios simples, começando por verificar se esse tipo de autenticação está disponível. No nível mais básico, o dispositivo deve executar Android 6.0 ou superior (API 23+). Quatro outros pré-requisitos também devem ser verificados:

  • A permissão deve ser solicitada no Android Manifest:

    <uses-permission
        android:name="android.permission.USE_FINGERPRINT" />
    
  • O hardware de impressão digital deve estar disponível:

    FingerprintManager fingerprintManager = (FingerprintManager)
                    context.getSystemService(Context.FINGERPRINT_SERVICE);
    fingerprintManager.isHardwareDetected();
    
  • O usuário deve ter uma tela de bloqueio protegida:

    KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
    keyguardManager.isKeyguardSecure();  //nota: se não for o caso, solicite ao usuário configurar uma tela de bloqueio protegida
    
  • Pelo menos uma impressão digital deve estar registrada:

    fingerprintManager.hasEnrolledFingerprints();
    
  • O aplicativo deve ter permissão para solicitar a impressão digital do usuário:

    context.checkSelfPermission(Manifest.permission.USE_FINGERPRINT) == PermissionResult.PERMISSION_GRANTED;
    

Se qualquer uma das verificações acima falhar, a opção de autenticação por impressão digital não deve ser oferecida.

É importante lembrar que nem todo dispositivo Android oferece armazenamento de chaves com suporte de hardware. A classe KeyInfo pode ser usada para descobrir se a chave reside dentro de hardware seguro, como um Trusted Execution Environment (TEE) ou Secure Element (SE).

SecretKeyFactory factory = SecretKeyFactory.getInstance(getEncryptionKey().getAlgorithm(), ANDROID_KEYSTORE);
KeyInfo secetkeyInfo = (KeyInfo) factory.getKeySpec(yourencryptionkeyhere, KeyInfo.class);
secetkeyInfo.isInsideSecureHardware()

Em certos sistemas, é possível impor a política de autenticação biométrica também via hardware. Isso é verificado por:

keyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware();

O seguinte descreve como fazer autenticação por impressão digital usando um par de chaves simétricas.

A autenticação por impressão digital pode ser implementada criando uma nova chave AES usando a classe KeyGenerator e adicionando setUserAuthenticationRequired(true) no KeyGenParameterSpec.Builder.

generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE);

generator.init(new KeyGenParameterSpec.Builder (KEY_ALIAS,
        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
        .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
        .setUserAuthenticationRequired(true)
        .build()
);

generator.generateKey();

Para realizar criptografia ou descriptografia com a chave protegida, crie um objeto Cipher e inicialize-o com o alias da chave.

SecretKey keyspec = (SecretKey)keyStore.getKey(KEY_ALIAS, null);

if (mode == Cipher.ENCRYPT_MODE) {
    cipher.init(mode, keyspec);

Lembre-se, uma nova chave não pode ser usada imediatamente - ela deve ser autenticada através do FingerprintManager primeiro. Isso envolve encapsular o objeto Cipher em FingerprintManager.CryptoObject, que é passado para FingerprintManager.authenticate antes de ser reconhecido.

cryptoObject = new FingerprintManager.CryptoObject(cipher);
fingerprintManager.authenticate(cryptoObject, new CancellationSignal(), 0, this, null);

O método de callback onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) é chamado quando a autenticação é bem-sucedida. O CryptoObject autenticado pode então ser recuperado do resultado.

public void authenticationSucceeded(FingerprintManager.AuthenticationResult result) {
    cipher = result.getCryptoObject().getCipher();

    //(... fazer algo com o objeto cipher autenticado ...)
}

O seguinte descreve como fazer autenticação por impressão digital usando um par de chaves assimétricas.

Para implementar autenticação por impressão digital usando criptografia assimétrica, primeiro crie uma chave de assinatura usando a classe KeyPairGenerator e registre a chave pública no servidor. Você pode então autenticar partes de dados assinando-as no cliente e verificando a assinatura no servidor. Um exemplo detalhado de autenticação em servidores remotos usando a API de impressão digital pode ser encontrado no Android Developers Blog.

Um par de chaves é gerado da seguinte forma:

KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
keyPairGenerator.initialize(
        new KeyGenParameterSpec.Builder(MY_KEY,
                KeyProperties.PURPOSE_SIGN)
                .setDigests(KeyProperties.DIGEST_SHA256)
                .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1"))
                .setUserAuthenticationRequired(true)
                .build());
keyPairGenerator.generateKeyPair();

Para usar a chave para assinatura, você precisa instanciar um CryptoObject e autenticá-lo através do FingerprintManager.

Signature.getInstance("SHA256withECDSA");
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
PrivateKey key = (PrivateKey) keyStore.getKey(MY_KEY, null);
signature.initSign(key);
CryptoObject cryptoObject = new FingerprintManager.CryptoObject(signature);

CancellationSignal cancellationSignal = new CancellationSignal();
FingerprintManager fingerprintManager =
        context.getSystemService(FingerprintManager.class);
fingerprintManager.authenticate(cryptoObject, cancellationSignal, 0, this, null);

Agora você pode assinar o conteúdo de um array de bytes inputBytes da seguinte forma.

Signature signature = cryptoObject.getSignature();
signature.update(inputBytes);
byte[] signed = signature.sign();
  • Observe que, em casos onde transações são assinadas, um nonce aleatório deve ser gerado e adicionado aos dados assinados. Caso contrário, um atacante poderia repetir a transação.
  • Para implementar autenticação usando autenticação por impressão digital simétrica, use um protocolo challenge-response.