2014-12-05 11 views
29

Chciałbym być w stanie bezpiecznie przechowywać niektóre wrażliwe ciągi w Android KeyStore. Dostaję ciągi z serwera, ale mam przypadek użycia, który wymaga, abym je utrzymał. KeyStore zezwoli na dostęp tylko z tego samego identyfikatora UID, który jest przypisany do mojej aplikacji, i zaszyfruje dane za pomocą hasła głównego urządzenia, więc rozumiem, że nie muszę wykonywać dodatkowego szyfrowania w celu ochrony moich danych. Mój kłopot polega na tym, że brakuje mi czegoś, jak pisać dane. Poniższy kod działa idealnie, o ile pominięto wywołanie KeyStore.store (null). Ten kod kończy się niepowodzeniem i dopóki nie mogę przechowywać danych po umieszczeniu go w KeyStore, nie mogę go utrzymać.Jak mogę użyć Android KeyStore, aby bezpiecznie przechowywać dowolne ciągi?

Chyba brakuje mi czegoś o KeyStore API, ale nie wiem co. Każda pomoc doceniona!

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

Odpowiedz

48

Zacząłem od założenia, że ​​mogę użyć AndroidKeyStore do zabezpieczenia dowolnych blobów danych i nazwać je "kluczami". Jednak im głębiej w to zagłębiałem, tym wyraźniej stało się, że interfejs API KeyStore jest głęboko uwikłany w obiekty związane z bezpieczeństwem: certyfikaty, kluczowe cechy, dostawcy itp. Nie jest przeznaczony do przechowywania arbitralnych danych i nie widzę prostego ścieżkę do zginania go w tym celu.

Jednak AndroidKeyStore może pomóc mi zabezpieczyć poufne dane. Mogę go użyć do zarządzania kluczami kryptograficznymi, które będę używał do szyfrowania danych lokalnych w aplikacji. Dzięki zastosowaniu kombinacji AndroidKeyStore, CipherOutputStream i CipherInputStream możemy:

  • Generowanie, bezpieczne przechowywanie i pobieranie kluczy szyfrowania na urządzeniu
  • Szyfrowanie dowolnych danych i zapisać go na urządzeniu (w katalogu aplikacji , gdzie będzie dalej chroniony przez uprawnienia systemu plików)
  • Dostęp i odszyfrowanie danych do późniejszego wykorzystania.

Oto przykład kodu, który pokazuje, jak to osiągnąć.

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 wymagają interfejsu API 19, natomiast KeyPairGeneratorSpec jest API 18. Jak wspierać api 18? –

+7

Świetny post. Bardzo mi pomogło. Musiałem usunąć dostawcę z wywołania 'Cipher.getInstance (transformacja łańcucha, dostawca Provider)', aby go uruchomić, tj. Wywołać 'Cipher.getInstance (transformacja łańcuchów)'. W przeciwnym razie pojawi się komunikat 'java.security.InvalidKeyException' zgłaszany z komunikatem' Need RSA private or public key'. –

+0

Pomyślałem, że będę to gdzieś dokumentował, nawet gdybym nie mógł go nigdzie znaleźć w oficjalnych dokumentach. ** Strzeż się, że kluczowy alias nie może być zbyt długi. ** Nie testowałem długości, ale waliłem głową o ścianę, obracając wszystkie przełączniki, dopóki nie dowiedziałem się, że mój kluczowy ciąg znaków jest zbyt duży ... –

3

Być może zauważyłeś, że występują problemy z obsługą różnych poziomów interfejsu API za pomocą magazynu kluczy Android.

Scytale to biblioteka z otwartym dostępem do kodu źródłowego, która zapewnia wygodną otokę w magazynie kluczy systemu Android, dzięki czemu nie można pisać płyty kotła i można nurkować bezpośrednio w trybie szyfrowania/deszyfrowania.

Przykładowy kod:

// 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); 
Powiązane problemy