Annoso problema. Ho delle informazioni sensibili, magari delle chiavi di cifratura o delle password applicative e non so dove memorizzarle.
Java, ci mette a disposizione un luogo molto interessante, dove riporre al sicuro i nostri secret: il KeyStore.
Il KeyStore è un file binario, cifrato in maniera simmetrica, solitamente utilizzato per memorizzare i certificati.
Dalla documentazione Oracle, abbiamo una definizione più ampia dello scopo con cui è stato progettato il KeyStore:
A database called a “keystore” can be used to manage a repository of keys and certificates. Keystores are available to applications that need data for authentication, encryption, or signing purposes.
Ogni applicazione, implementando la classe KeyStore, nel package java.security, può scegliere 3 diversi formati per il file:
Il nostro keystore, non è altro che un file binario, cifrato con una chiave attraverso un 3DES. Essendo questa, crittografia simmetrica, la stessa chiave usata per cifrare dovrà essere usata per decifrare il file.
Spostiamo quindi un po’ il problema. Usiamo un keystore per memorizzare dei segreti, ma abbiamo bisogno di un segreto per sbloccare il keystore. Già, bizzarro. Non storcete però il naso. La chiave del keystore, che a questo punto è quello che dovete proteggere, può essere:
Ogni approccio ha i suoi pro e i suoi contro. Io credo sinceramente che l’approccio della chiave nel codice, offuscato sia quello più versatile. Ci sono molti tool per rendere il bytecode java difficilmente comprensibile. Attenzione, nessuno di essi offre il 100% di sicurezza contro il reverse.
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
private static KeyStore createKeyStore(String fileName, String pw) throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
File file = new File(fileName);
final KeyStore keyStore = KeyStore.getInstance("JCEKS");
if (file.exists()) {
keyStore.load(new FileInputStream(file), pw.toCharArray());
} else {
keyStore.load(null, null);
keyStore.store(new FileOutputStream(fileName), pw.toCharArray());
}
return keyStore;
}
Per salvare un valore nel keystore, utilizzo la classe javax.crypto.PBEKeySpec, che offusca il valore garantendo un livello di protezione adeguato per una password. Ricordiamoci che comunque il keystore è cifrato con triple des.
Raccomando la lettura della documentazione Oracle, circa il motore Password Based Encryption.
public static void setKey(String key, String value, String keyStoreLocation, String keyStorePassword) throws Exception {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBE");
SecretKey generatedSecret = factory.generateSecret(new PBEKeySpec(value.toCharArray()));
KeyStore ks = KeyStore.getInstance("JCEKS");
ks.load(null, keyStorePassword.toCharArray());
KeyStore.PasswordProtection keyStorePP = new KeyStore.PasswordProtection(keyStorePassword.toCharArray());
ks.setEntry(key, new KeyStore.SecretKeyEntry( generatedSecret), keyStorePP);
FileOutputStream fos = new java.io.FileOutputStream(keyStoreLocation);
ks.store(fos, keyStorePassword.toCharArray());
}
Il metodo getKey, recupera la chiave specificata come parametro, dal keystore con una particolare password.
public static String getKey(String key, String keystoreLocation, String keyStorePassword) throws Exception{
KeyStore ks = KeyStore.getInstance("JCEKS");
ks.load(null, keyStorePassword.toCharArray());
KeyStore.PasswordProtection keyStorePP = new KeyStore.PasswordProtection(keyStorePassword.toCharArray());
FileInputStream fIn = new FileInputStream(keystoreLocation);
ks.load(fIn, keyStorePassword.toCharArray());
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBE");
KeyStore.SecretKeyEntry ske =
(KeyStore.SecretKeyEntry)ks.getEntry(key, keyStorePP);
PBEKeySpec keySpec = (PBEKeySpec)factory.getKeySpec(
ske.getSecretKey(),
PBEKeySpec.class);
char[] password = keySpec.getPassword();
return new String(password);
}
Ecco il nostro main che mostra come usare le routine per memorizzare una chiave nel nostro keystore. A questo punto diventa critica la password con cui cifriamo il keystore. Non usate test123 mi raccomando!
public static void main(String[] args) throws Exception {
final String keyStoreFile = "./codiceinsicuro.keystore";
KeyStore keyStore = createKeyStore(keyStoreFile, "test123");
setKey("test", "leggi questo blog ogni giorno", keyStoreFile, "test123");
System.out.println("Found Key: " + getKey("test", keyStoreFile, "test123" ));
}
Questo è il contenuto del nostro keystore. L’unica cosa in chiaro, a parte la chiave “test” è il tipo dell’oggetto memorizzato, java/lang/String. Sinceramente, accettabile come perdita di confidenzialità.
$ hexdump -C codiceinsicuro.keystore
00000000 ce ce ce ce 00 00 00 02 00 00 00 01 00 00 00 03 |................|
00000010 00 04 74 65 73 74 00 00 01 53 9d 38 dc ff ac ed |..test...S.8....|
00000020 00 05 73 72 00 33 63 6f 6d 2e 73 75 6e 2e 63 72 |..sr.3com.sun.cr|
00000030 79 70 74 6f 2e 70 72 6f 76 69 64 65 72 2e 53 65 |ypto.provider.Se|
00000040 61 6c 65 64 4f 62 6a 65 63 74 46 6f 72 4b 65 79 |aledObjectForKey|
00000050 50 72 6f 74 65 63 74 6f 72 cd 57 ca 59 e7 30 bb |Protector.W.Y.0.|
00000060 53 02 00 00 78 72 00 19 6a 61 76 61 78 2e 63 72 |S...xr..javax.cr|
00000070 79 70 74 6f 2e 53 65 61 6c 65 64 4f 62 6a 65 63 |ypto.SealedObjec|
00000080 74 3e 36 3d a6 c3 b7 54 70 02 00 04 5b 00 0d 65 |t>6=...Tp...[..e|
00000090 6e 63 6f 64 65 64 50 61 72 61 6d 73 74 00 02 5b |ncodedParamst..[|
000000a0 42 5b 00 10 65 6e 63 72 79 70 74 65 64 43 6f 6e |B[..encryptedCon|
000000b0 74 65 6e 74 71 00 7e 00 02 4c 00 09 70 61 72 61 |tentq.~..L..para|
000000c0 6d 73 41 6c 67 74 00 12 4c 6a 61 76 61 2f 6c 61 |msAlgt..Ljava/la|
000000d0 6e 67 2f 53 74 72 69 6e 67 3b 4c 00 07 73 65 61 |ng/String;L..sea|
000000e0 6c 41 6c 67 71 00 7e 00 03 78 70 75 72 00 02 5b |lAlgq.~..xpur..[|
000000f0 42 ac f3 17 f8 06 08 54 e0 02 00 00 78 70 00 00 |B......T....xp..|
00000100 00 0f 30 0d 04 08 8c 57 09 3e 2d 54 1f 4b 02 01 |..0....W.>-T.K..|
00000110 14 75 71 00 7e 00 05 00 00 01 30 51 74 ed 88 de |.uq.~.....0Qt...|
00000120 09 f4 88 b9 ab 2b 2d 43 87 06 a2 99 c6 9c 0d 6a |.....+-C.......j|
00000130 4e 00 fe 26 be 06 71 d6 d8 d1 52 a4 ee 90 51 e1 |N..&..q...R...Q.|
00000140 66 7a 66 54 93 07 ea ba 57 85 4e 59 cd 99 b6 af |fzfT....W.NY....|
00000150 59 60 e9 99 a7 97 dc 7d 8c 52 10 9d a2 ce a9 37 |Y`.....}.R.....7|
00000160 cd b2 bd 6c 38 0c 0d 3a d9 f6 3f 02 c8 95 e7 16 |...l8..:..?.....|
00000170 34 82 05 be 07 99 46 64 ae f4 da 84 b5 1d cb 25 |4.....Fd.......%|
00000180 7c 75 ee ac d0 49 28 de 1d 55 81 f3 d0 37 9c 57 ||u...I(..U...7.W|
00000190 f1 e4 d4 ef 93 54 55 ab e8 ad 25 6c 99 3c cd 40 |.....TU...%l.<.@|
000001a0 c8 30 8c 4c 12 47 8a fa 71 a5 bf 13 6c df 86 55 |.0.L.G..q...l..U|
000001b0 2d bf 80 81 45 ed 30 f3 8e b0 a9 c4 28 d6 c0 55 |-...E.0.....(..U|
000001c0 10 05 4b 46 12 02 5e b1 ed 3c 51 6f 78 03 07 35 |..KF..^..<Qox..5|
000001d0 94 f0 cf ec b7 e1 05 27 98 db 17 00 a5 00 bd 9c |.......'........|
000001e0 5c ce 12 24 c3 67 ec 91 b6 02 0c 19 ff dd 02 cc |\..$.g..........|
000001f0 4b d6 87 c7 12 07 db 90 b6 8d f9 2e 42 11 f9 92 |K...........B...|
00000200 03 5f ab 24 f6 99 4f a1 e7 7c 39 bf 63 f8 f1 e5 |._.$..O..|9.c...|
00000210 fd 21 9e fc 34 ae 0a f5 f6 20 8a 48 ba 21 89 fa |.!..4.... .H.!..|
00000220 bf 60 bc a9 11 f2 c2 a4 e7 e8 d7 f2 f1 a8 c2 ed |.`..............|
00000230 5b df 12 f6 a9 fa 7e 5f 64 35 dc 5d 91 41 d4 75 |[.....~_d5.].A.u|
00000240 53 5e 54 6c 86 e5 f7 18 97 28 75 74 00 16 50 42 |S^Tl.....(ut..PB|
00000250 45 57 69 74 68 4d 44 35 41 6e 64 54 72 69 70 6c |EWithMD5AndTripl|
00000260 65 44 45 53 74 00 16 50 42 45 57 69 74 68 4d 44 |eDESt..PBEWithMD|
00000270 35 41 6e 64 54 72 69 70 6c 65 44 45 53 4d 5b 3e |5AndTripleDESM[>|
00000280 40 dd d8 a6 f3 01 6d d8 3f 76 fa a9 5f 16 62 6f |@.....m.?v.._.bo|
00000290 af |.|
00000291
Il KeyStore può essere usato per memorizzare delle coppie chiave valore, come parametri o credenziali. Il tutto deve essere protetto da una password per la cifratura simmetica dell’archivio utilizzato.
A questo punto, come dicevamo nell’introduzione, non resta che scegliere la strategia migliore per proteggere quest’unica parola chiave. Per quanto ho avuto modo di vedere, il KeyStore è un po’ sottovalutato come store sicuro per riporre le credenziali. Perché non integrarlo in Android ed evitare password memorizzate in file di configurazione?
Disclaimer
Il codice è stato preso ed adattato da questo post su StackOverflow. Avevo in mente di cambiare l’algoritmo per l’offuscamento del secret nel KeyStore, usando AES invece del Password Based Encryption. Poi ho preferito lasciar stare, non tanto per pigrizia, visto che per i fatti miei avevo già le chiamate pronte, ma perché sinceramente non mi dispiace usare quest’API.
Ho aggiunto il codice per creare il file del KeyStore.
A questo punto, l’evoluzione è memorizzare tutto in una classe ed incorporarla nei vostri progetti.
Enjoy it!