web-dev-qa-db-fra.com

Comment puis-je utiliser le Android KeyStore pour stocker en toute sécurité des chaînes arbitraires?

Je voudrais pouvoir stocker en toute sécurité certaines chaînes sensibles dans le Android KeyStore. J'obtiens les chaînes du serveur mais j'ai un cas d'utilisation qui m'oblige à les conserver. KeyStore n'autorisera que l'accès à partir du même UID que celui attribué à mon application, et il cryptera les données avec le mot de passe principal de l'appareil, donc je comprends que je n'ai pas à faire de cryptage supplémentaire pour protéger mes données. Mon problème est, je suis manque quelque chose sur la façon d'écrire les données. Le code ci-dessous fonctionne parfaitement, tant que l'appel à KeyStore.store (null) est omis. Ce code échoue, et tant que je ne peux pas stocker les données après l'avoir mis au KeyStore, je ne peux pas le conserver.

Je pense que je manque quelque chose sur l'API KeyStore, mais je ne sais pas quoi. Toute aide appréciée!

String metaKey = "ourSecretKey";
String encodedKey = "this is supposed to be a secret";
byte[] encodedKeyBytes = new byte[(int)encodedKey.length()];
encodedKeyBytes = encodedKey.getBytes("UTF-8");
KeyStoreParameter ksp = null;

//String algorithm = "DES";
String algorithm = "DESede";
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm);
SecretKeySpec secretKeySpec = new SecretKeySpec(encodedKeyBytes, algorithm);
SecretKey secretKey = secretKeyFactory.generateSecret(secretKeySpec);

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());

keyStore.load(null);

KeyStore.SecretKeyEntry secretKeyEntry = new KeyStore.SecretKeyEntry(secretKey);
keyStore.setEntry(metaKey, secretKeyEntry, ksp);

keyStore.store(null);

String recoveredSecret = "";
if (keyStore.containsAlias(metaKey)) {
    KeyStore.SecretKeyEntry recoveredEntry = (KeyStore.SecretKeyEntry)keyStore.getEntry(metaKey, ksp);
    byte[] bytes = recoveredEntry.getSecretKey().getEncoded();
    for (byte b : bytes) {
        recoveredSecret += (char)b;
     }
}
Log.v(TAG, "recovered " + recoveredSecret);
41
Patrick Brennan

J'ai commencé avec la prémisse que je pouvais utiliser AndroidKeyStore pour sécuriser des blobs arbitraires de données, et les appeler "clés". Cependant, plus j'y approfondissais, plus il devenait clair que l'API KeyStore est profondément enchevêtrée avec des objets liés à la sécurité: certificats, KeySpecs, fournisseurs, etc. Elle n'est pas conçue pour stocker des données arbitraires et je ne vois pas chemin pour le plier à cette fin.

Cependant, AndroidKeyStore peut être utilisé pour m'aider à sécuriser mes données sensibles. Je peux l'utiliser pour gérer les clés cryptographiques que j'utiliserai pour crypter les données locales de l'application. En utilisant une combinaison d'AndroidKeyStore, CipherOutputStream et CipherInputStream, nous pouvons:

  • Générez, stockez en toute sécurité et récupérez les clés de chiffrement sur l'appareil
  • Chiffrez des données arbitraires et enregistrez-les sur l'appareil (dans le répertoire de l'application, où elles seront davantage protégées par les autorisations du système de fichiers)
  • Accédez et déchiffrez les données pour une utilisation ultérieure.

Voici un exemple de code qui montre comment cela est réalisé.

try {
    KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
    keyStore.load(null);

    String alias = "key3";

    int nBefore = keyStore.size();

    // Create the keys if necessary
    if (!keyStore.containsAlias(alias)) {

        Calendar notBefore = Calendar.getInstance();
        Calendar notAfter = Calendar.getInstance();
        notAfter.add(Calendar.YEAR, 1);
        KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(this)
            .setAlias(alias)
            .setKeyType("RSA")
            .setKeySize(2048)
            .setSubject(new X500Principal("CN=test"))
            .setSerialNumber(BigInteger.ONE)
            .setStartDate(notBefore.getTime())
            .setEndDate(notAfter.getTime())
            .build();
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
        generator.initialize(spec);

        KeyPair keyPair = generator.generateKeyPair();
    }
    int nAfter = keyStore.size();
    Log.v(TAG, "Before = " + nBefore + " After = " + nAfter);

    // Retrieve the keys
    KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(alias, null);
    RSAPrivateKey privateKey = (RSAPrivateKey) privateKeyEntry.getPrivateKey();
    RSAPublicKey publicKey = (RSAPublicKey) privateKeyEntry.getCertificate().getPublicKey();

    Log.v(TAG, "private key = " + privateKey.toString());
    Log.v(TAG, "public key = " + publicKey.toString());

    // Encrypt the text
    String plainText = "This text is supposed to be a secret!";
    String dataDirectory = getApplicationInfo().dataDir;
    String filesDirectory = getFilesDir().getAbsolutePath();
    String encryptedDataFilePath = filesDirectory + File.separator + "keep_yer_secrets_here";

    Log.v(TAG, "plainText = " + plainText);
    Log.v(TAG, "dataDirectory = " + dataDirectory);
    Log.v(TAG, "filesDirectory = " + filesDirectory);
    Log.v(TAG, "encryptedDataFilePath = " + encryptedDataFilePath);

    Cipher inCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL");
    inCipher.init(Cipher.ENCRYPT_MODE, publicKey);

    Cipher outCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL");
    outCipher.init(Cipher.DECRYPT_MODE, privateKey);

    CipherOutputStream cipherOutputStream = 
        new CipherOutputStream(
            new FileOutputStream(encryptedDataFilePath), inCipher);
    cipherOutputStream.write(plainText.getBytes("UTF-8"));
    cipherOutputStream.close();

    CipherInputStream cipherInputStream = 
        new CipherInputStream(new FileInputStream(encryptedDataFilePath),
            outCipher);
    byte [] roundTrippedBytes = new byte[1000]; // TODO: dynamically resize as we get more data

    int index = 0;
    int nextByte;
    while ((nextByte = cipherInputStream.read()) != -1) {
        roundTrippedBytes[index] = (byte)nextByte;
        index++;
    }
    String roundTrippedString = new String(roundTrippedBytes, 0, index, "UTF-8");
    Log.v(TAG, "round tripped string = " + roundTrippedString);

} catch (NoSuchAlgorithmException e) {
    Log.e(TAG, Log.getStackTraceString(e));
} catch (NoSuchProviderException e) {
    Log.e(TAG, Log.getStackTraceString(e));
} catch (InvalidAlgorithmParameterException e) {
    Log.e(TAG, Log.getStackTraceString(e));
} catch (KeyStoreException e) {
    Log.e(TAG, Log.getStackTraceString(e));
} catch (CertificateException e) {
    Log.e(TAG, Log.getStackTraceString(e));
} catch (IOException e) {
    Log.e(TAG, Log.getStackTraceString(e));
} catch (UnrecoverableEntryException e) {
    Log.e(TAG, Log.getStackTraceString(e));
} catch (NoSuchPaddingException e) {
    Log.e(TAG, Log.getStackTraceString(e));
} catch (InvalidKeyException e) {
    Log.e(TAG, Log.getStackTraceString(e));
} catch (BadPaddingException e) {
    Log.e(TAG, Log.getStackTraceString(e));
} catch (IllegalBlockSizeException e) {
    Log.e(TAG, Log.getStackTraceString(e));
} catch (UnsupportedOperationException e) {
    Log.e(TAG, Log.getStackTraceString(e));
}
63
Patrick Brennan

Vous avez peut-être remarqué qu'il y a des problèmes de gestion de différents niveaux d'API avec le magasin de clés Android.

Scytale est une bibliothèque open source qui fournit un wrapper pratique autour du magasin de clés Android afin que vous n'ayez pas d'écriture de plaque de chaudière et que vous puissiez plonger directement dans le cryptage/décryptage.

Exemple de code:

// Create and save key
Store store = new Store(getApplicationContext());
if (!store.hasKey("test")) {
   SecretKey key = store.generateSymmetricKey("test", null);
}
...

// Get key
SecretKey key = store.getSymmetricKey("test", null);

// Encrypt/Decrypt data
Crypto crypto = new Crypto(Options.TRANSFORMATION_SYMMETRIC);
String text = "Sample text";

String encryptedData = crypto.encrypt(text, key);
Log.i("Scytale", "Encrypted data: " + encryptedData);

String decryptedData = crypto.decrypt(encryptedData, key);
Log.i("Scytale", "Decrypted data: " + decryptedData);
8
David Rawson