I would like to be able securely store some sensitive strings in the Android KeyStore. I get the strings from the server but I have a use case which requires me to persist them. KeyStore will only allow access from the same UID as that assigned to my app, and it will encrypt the data with the device master password, so it's my understanding that I don't have to do any additional encryption to protect my data. My trouble is, I'm missing something about how to write the data. The code I have below works perfectly, as long as the call to KeyStore.store(null) is omitted. That code fails, and as long as I can't store the data after putting it to the KeyStore, then I can't persist it.
我希望能够在Android KeyStore中安全地存储一些敏感字符串。我从服务器获取字符串但我有一个用例需要我坚持它们。 KeyStore只允许从分配给我的应用程序的UID访问,并且它将使用设备主密码加密数据,因此我的理解是我不需要进行任何额外的加密来保护我的数据。我的麻烦是,我错过了一些关于如何写数据的东西。只要省略对KeyStore.store(null)的调用,我在下面的代码就可以完美地工作。该代码失败,只要我将数据放入KeyStore后就无法存储,那么我就无法坚持下去。
I think I'm missing something about the KeyStore API, but I don't know what. Any help appreciated!
我想我错过了关于KeyStore API的一些内容,但我不知道是什么。任何帮助赞赏!
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);
2 个解决方案
#1
57
I started with the premise that I could use AndroidKeyStore to secure arbitrary blobs of data, and call them "keys". However, the deeper I delved into this, the clearer it became that the KeyStore API is deeply entangled with Security-related objects: Certificates, KeySpecs, Providers, etc. It's not designed to store arbitrary data, and I don't see a straightforward path to bending it to that purpose.
我开始的前提是我可以使用AndroidKeyStore来保护任意数据blob,并称之为“密钥”。但是,我越深入研究这个问题就越明显,KeyStore API与安全相关的对象深深纠缠在一起:证书,KeySpecs,Providers等。它不是为了存储任意数据而设计的,我看不出直截了当将它弯曲到那个目的的道路。
However, the AndroidKeyStore can be used to help me to secure my sensitive data. I can use it to manage the cryptographic keys which I will use to encrypt data local to the app. By using a combination of AndroidKeyStore, CipherOutputStream, and CipherInputStream, we can:
但是,AndroidKeyStore可用于帮助我保护敏感数据。我可以用它来管理我将用来加密应用程序本地数据的加密密钥。通过结合使用AndroidKeyStore,CipherOutputStream和CipherInputStream,我们可以:
- Generate, securely store, and retrieve encryption keys on the device
- 在设备上生成,安全存储和检索加密密钥
- Encrypt arbitrary data and save it on the device (in the app's directory, where it will be further protected by the file system permissions)
- 加密任意数据并将其保存在设备上(在应用程序的目录中,它将受到文件系统权限的进一步保护)
- Access and decrypt the data for subsequent use.
- 访问和解密数据以供后续使用。
Here is some example code which demonstrates how this is achieved.
下面是一些示例代码,演示了如何实现这一目标。
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
6
You may have noticed that there are problems handling different API levels with the Android Keystore.
您可能已经注意到使用Android Keystore处理不同的API级别存在问题。
Scytale is an open source library that provides a convenient wrapper around the Android Keystore so that you don't have write boiler plate and can dive straight into enryption/decryption.
Scytale是一个开源库,它为Android Keystore提供了一个方便的包装器,因此您没有写入锅炉板,可以直接进入enryption / decryption。
Sample 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);
#1
57
I started with the premise that I could use AndroidKeyStore to secure arbitrary blobs of data, and call them "keys". However, the deeper I delved into this, the clearer it became that the KeyStore API is deeply entangled with Security-related objects: Certificates, KeySpecs, Providers, etc. It's not designed to store arbitrary data, and I don't see a straightforward path to bending it to that purpose.
我开始的前提是我可以使用AndroidKeyStore来保护任意数据blob,并称之为“密钥”。但是,我越深入研究这个问题就越明显,KeyStore API与安全相关的对象深深纠缠在一起:证书,KeySpecs,Providers等。它不是为了存储任意数据而设计的,我看不出直截了当将它弯曲到那个目的的道路。
However, the AndroidKeyStore can be used to help me to secure my sensitive data. I can use it to manage the cryptographic keys which I will use to encrypt data local to the app. By using a combination of AndroidKeyStore, CipherOutputStream, and CipherInputStream, we can:
但是,AndroidKeyStore可用于帮助我保护敏感数据。我可以用它来管理我将用来加密应用程序本地数据的加密密钥。通过结合使用AndroidKeyStore,CipherOutputStream和CipherInputStream,我们可以:
- Generate, securely store, and retrieve encryption keys on the device
- 在设备上生成,安全存储和检索加密密钥
- Encrypt arbitrary data and save it on the device (in the app's directory, where it will be further protected by the file system permissions)
- 加密任意数据并将其保存在设备上(在应用程序的目录中,它将受到文件系统权限的进一步保护)
- Access and decrypt the data for subsequent use.
- 访问和解密数据以供后续使用。
Here is some example code which demonstrates how this is achieved.
下面是一些示例代码,演示了如何实现这一目标。
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
6
You may have noticed that there are problems handling different API levels with the Android Keystore.
您可能已经注意到使用Android Keystore处理不同的API级别存在问题。
Scytale is an open source library that provides a convenient wrapper around the Android Keystore so that you don't have write boiler plate and can dive straight into enryption/decryption.
Scytale是一个开源库,它为Android Keystore提供了一个方便的包装器,因此您没有写入锅炉板,可以直接进入enryption / decryption。
Sample 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);