MASTG-KNOW-0034: Vinculação de Dispositivo

O objetivo do vínculo de dispositivo (device binding) é impedir que um atacante copie um aplicativo e seu estado do dispositivo A para o dispositivo B e continue executando o aplicativo no dispositivo B. Após o dispositivo A ser considerado confiável, ele pode ter mais privilégios que o dispositivo B. Esses privilégios diferenciais não devem ser alterados quando um aplicativo é copiado do dispositivo A para o dispositivo B.

Antes de descrevermos os identificadores utilizáveis, vamos discutir rapidamente como eles podem ser usados para o vínculo. Existem três métodos que permitem o vínculo de dispositivo:

  • Reforçar as credenciais usadas para autenticação com identificadores de dispositivo. Isso faz sentido se o aplicativo precisar se reautenticar e/ou reautenticar o usuário com frequência.

  • Criptografar os dados armazenados no dispositivo com material de chave fortemente vinculado ao dispositivo pode fortalecer o vínculo de dispositivo. O Android Keystore oferece chaves privadas não exportáveis que podemos usar para isso. Quando um ator malicioso extrai esses dados de um dispositivo, não seria possível descriptografá-los, pois a chave não está acessível. A implementação disso segue as seguintes etapas:

    • Gerar o par de chaves no Android Keystore usando a API KeyGenParameterSpec.
    //Fonte: <https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.html>
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
            KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
    keyPairGenerator.initialize(
            new KeyGenParameterSpec.Builder(
                    "key1",
                    KeyProperties.PURPOSE_DECRYPT)
                    .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
                    .build());
    KeyPair keyPair = keyPairGenerator.generateKeyPair();
    Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
    cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
    ...
    
    // O par de chaves também pode ser obtido do Android Keystore a qualquer momento da seguinte forma:
    KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
    keyStore.load(null);
    PrivateKey privateKey = (PrivateKey) keyStore.getKey("key1", null);
    PublicKey publicKey = keyStore.getCertificate("key1").getPublicKey();
    
    • Gerar uma chave secreta para AES-GCM:
    //Fonte: <https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.html>
    KeyGenerator keyGenerator = KeyGenerator.getInstance(
            KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
    keyGenerator.init(
            new KeyGenParameterSpec.Builder("key2",
                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                    .build());
    SecretKey key = keyGenerator.generateKey();
    
    // A chave também pode ser obtida do Android Keystore a qualquer momento da seguinte forma:
    KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
    keyStore.load(null);
    key = (SecretKey) keyStore.getKey("key2", null);
    
    • Criptografar os dados de autenticação e outros dados sensíveis armazenados pelo aplicativo usando uma chave secreta por meio do cipher AES-GCM e usar parâmetros específicos do dispositivo, como Instance ID, etc., como dados associados (associated data):
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    final byte[] nonce = new byte[GCM_NONCE_LENGTH];
    random.nextBytes(nonce);
    GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, nonce);
    cipher.init(Cipher.ENCRYPT_MODE, key, spec);
    byte[] aad = "<deviceidentifierhere>".getBytes();;
    cipher.updateAAD(aad);
    cipher.init(Cipher.ENCRYPT_MODE, key);
    
    // usar o cipher para criptografar os dados de autenticação; consulte 0x50e para mais detalhes.
    
    • Criptografar a chave secreta usando a chave pública armazenada no Android Keystore e armazenar a chave secreta criptografada no armazenamento privado do aplicativo.
    • Sempre que dados de autenticação, como tokens de acesso ou outros dados sensíveis, forem necessários, descriptografar a chave secreta usando a chave privada armazenada no Android Keystore e, em seguida, usar a chave secreta descriptografada para descriptografar o texto cifrado.
  • Usar autenticação de dispositivo baseada em token (Instance ID) para garantir que a mesma instância do aplicativo seja usada.