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);
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:
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));
}
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);