2014-12-05 15 views
29

Vorrei poter memorizzare in modo sicuro alcune stringhe sensibili nel KeyStore di Android. Ottengo le stringhe dal server ma ho un caso d'uso che mi richiede di persisterle. KeyStore consentirà l'accesso solo dallo stesso UID di quello assegnato alla mia app, e crittograferà i dati con la password principale del dispositivo, quindi è a mia conoscenza che non devo fare alcuna crittografia aggiuntiva per proteggere i miei dati. Il mio problema è che mi manca qualcosa su come scrivere i dati. Il codice che ho qui sotto funziona perfettamente, purché la chiamata a KeyStore.store (null) sia omessa. Quel codice ha esito negativo e, finché non riesco a memorizzare i dati dopo averlo inserito nel KeyStore, non posso persisterlo.Come posso utilizzare il KeyStore Android per archiviare in modo sicuro stringhe arbitrarie?

Penso che mi manca qualcosa sull'API KeyStore, ma non so cosa. Qualsiasi aiuto apprezzato!

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

risposta

48

Ho iniziato con la premessa che ho potuto usare per fissare AndroidKeyStore blob arbitrari di dati, e li chiamano "chiavi". Tuttavia, più approfondivo l'ho approfondito, più chiaro è diventato che l'API KeyStore è profondamente intrecciata con oggetti relativi alla sicurezza: certificati, KeySpecs, provider, ecc. Non è progettato per archiviare dati arbitrari e non vedo un semplice percorso per piegarlo a tale scopo.

Tuttavia, AndroidKeyStore può essere utilizzato per aiutarmi a proteggere i miei dati sensibili. Posso usarlo per gestire le chiavi crittografiche che userò per crittografare i dati locali nell'app. Usando una combinazione di AndroidKeyStore, CipherOutputStream, e CipherInputStream, siamo in grado di:

  • generare, archiviare in modo sicuro, e recuperare le chiavi di crittografia sul dispositivo
  • crittografare i dati arbitrari e salvarla sul dispositivo (nella directory del app , dove sarà ulteriormente protetto dalle autorizzazioni del file system)
  • Accesso e decrittografia dei dati per uso successivo.

Ecco alcuni esempi di codice che dimostrano come questo è stato raggiunto.

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)); 
} 
+2

setKeyType richiede api 19, mentre KeyPairGeneratorSpec è API 18. Come supporto API 18? –

+7

Ottimo post. Mi ha aiutato molto Ho dovuto rimuovere il provider nella chiamata 'Cipher.getInstance (trasformazione stringa, provider provider)' per farlo funzionare, ovvero chiamare 'Cipher.getInstance (trasformazione stringa)'. In caso contrario, verrà visualizzato un messaggio 'java.security.InvalidKeyException' con il messaggio' Need RSA private o public key'. –

+0

Ho pensato di documentarlo da qualche parte anche se non lo trovavo da nessuna parte nei documenti ufficiali. ** Attenzione che l'alias chiave non può essere troppo lungo. ** Non ho provato la lunghezza ma stavo sbattendo la testa contro il muro girando tutti gli interruttori finché non ho scoperto che il mio alias era troppo grande ... –

3

Avrete notato che ci sono problemi nel gestire diversi livelli API con il Keystore Android.

Scytale è una libreria open source che fornisce un comodo wrapper attorno al Keystore Android in modo da non avere una piastra di scrittura e può immergersi direttamente in enryption/decryption.

codice di esempio:

// 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); 
Problemi correlati