How to Use the Android Keystore to Securely Store Arbitrary Strings

How Can I Use the Android KeyStore to securely store arbitrary strings?

I have reworked the accepted answer by Patrick Brennan. on Android 9, it was yielding a NoSuchAlgorithmException. The deprecated KeyPairGeneratorSpec has been replaced with KeyPairGenerator. There was also some work required to address an exception regarding the padding.

The code is annotated with the changes made: "***"

@RequiresApi(api = Build.VERSION_CODES.M)
public static void storeExistingKey(Context context) {

final String TAG = "KEY-UTIL";

try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);

String alias = "key11";
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);

// *** Replaced deprecated KeyPairGeneratorSpec with KeyPairGenerator
KeyPairGenerator spec = KeyPairGenerator.getInstance(
// *** Specified algorithm here
// *** Specified: Purpose of key here
KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
spec.initialize(new KeyGenParameterSpec.Builder(
alias, KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) // RSA/ECB/PKCS1Padding
.setKeySize(2048)
// *** Replaced: setStartDate
.setKeyValidityStart(notBefore.getTime())
// *** Replaced: setEndDate
.setKeyValidityEnd(notAfter.getTime())
// *** Replaced: setSubject
.setCertificateSubject(new X500Principal("CN=test"))
// *** Replaced: setSerialNumber
.setCertificateSerialNumber(BigInteger.ONE)
.build());
KeyPair keyPair = spec.generateKeyPair();
Log.i(TAG, keyPair.toString());
}

int nAfter = keyStore.size();
Log.v(TAG, "Before = " + nBefore + " After = " + nAfter);

// Retrieve the keys
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, null);
PrivateKey privateKey = privateKeyEntry.getPrivateKey();
PublicKey publicKey = 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 = context.getApplicationInfo().dataDir;
String filesDirectory = context.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);

// *** Changed the padding type here and changed to AndroidKeyStoreBCWorkaround
Cipher inCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidKeyStoreBCWorkaround");
inCipher.init(Cipher.ENCRYPT_MODE, publicKey);

// *** Changed the padding type here and changed to AndroidKeyStoreBCWorkaround
Cipher outCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidKeyStoreBCWorkaround");
outCipher.init(Cipher.DECRYPT_MODE, privateKey);

CipherOutputStream cipherOutputStream =
new CipherOutputStream(
new FileOutputStream(encryptedDataFilePath), inCipher);
// *** Replaced string literal with StandardCharsets.UTF_8
cipherOutputStream.write(plainText.getBytes(StandardCharsets.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++;
}

// *** Replaced string literal with StandardCharsets.UTF_8
String roundTrippedString = new String(roundTrippedBytes, 0, index, StandardCharsets.UTF_8);
Log.v(TAG, "round tripped string = " + roundTrippedString);

} catch (NoSuchAlgorithmException | UnsupportedOperationException | InvalidKeyException | NoSuchPaddingException | UnrecoverableEntryException | NoSuchProviderException | KeyStoreException | CertificateException | IOException e | InvalidAlgorithmParameterException e) {
e.printStackTrace();
}

Note: “AndroidKeyStoreBCWorkaround” allows the code to work across different APIs.

I would be grateful if anyone can comment on any shortcomings in this updated solution. Else if anyone with more Crypto knowledge feels confident to update Patrick's answer then I will remove this one.

Can i Store a String in keystore in Android and retrieve it later?

You can store the AES key into AndroidKeyStore safely. The keying material can be used by your application without exposing it.

But a key in AndroidKeyStore is not extractable, so in order to be able to send the AES key to server you will need to generate the key outside, and wrap it using an additional encryption key managed by AndroidKeyStore. Then the encrypted AES key can be stored in the device or even in the server

Please see my answer here with all options explained: how to securely store encryption keys in android?

How to securely store a hardcoded API key on Android?

YOUR CHALLENGE

The purpose of it is to be able to send that key everytime I call a webservice that I've made, so I'm sure (or almost sure) that the call comes from the original app that I'm making and that will be published on the Play Store, and not from elsewhere.

This is a very hard task to achieve, but not impossible one and here is where one needs to make a deep dive in mobile API security and understand the mechanics behind it.

It's fundamental to have a clear understand between the difference of who is in the API request versus what is making that API request, otherwise any security solution you may devise/use may not have the intended results.

The Difference Between WHO and WHAT is Accessing the API Server

I wrote a series of articles around API and Mobile security, and in the article Why Does Your Mobile App Need An Api Key? you can read in detail the difference between who and what is accessing your API server, but I will extract here the main takes from it:

The what is the thing making the request to the API server. Is it really a genuine instance of your mobile app, or is it a bot, an automated script or an attacker manually poking around your API server with a tool like Postman?

The who is the user of the mobile app that we can authenticate, authorize and identify in several ways, like using OpenID Connect or OAUTH2 flows.

So, you need to think about the who as the user your API server will be able to Authenticate and Authorize access to the data, and you need to think about the what as the software making that request in behalf of the user.

REVERSE ENGINEERING

I also don't want to store my key in my code, also because it could easily be retrieved via reverse engineering.

That's very true, it's more or less easily achieved depending on the method used to hide the API key, as per the ones you mention:

I've also checked the other methods explained here, but they look like the API key could quite easily be retrieved thanks to reverse engineering.

No matter how secure the API key has been stored, be it in the Android Keystore, encrypted, obfuscated, etc, at some point the API key will need to be in plain text to be sent on the API request header, and in this moment it will be vulnerable to be extracted via static reverse engineering, via a MitM attack or via an instrumentation framework

I have wrote the article How to Extract an API key from a Mobile App with Static Binary Analysis to illustrate how easy it can be done:

The range of open source tools available for reverse engineering is huge, and we really can't scratch the surface of this topic in this article, but instead we will focus in using the Mobile Security Framework(MobSF) to demonstrate how to reverse engineer the APK of our mobile app. MobSF is a collection of open source tools that present their results in an attractive dashboard, but the same tools used under the hood within MobSF and elsewhere can be used individually to achieve the same results.

During this article we will use the Android Hide Secrets research repository that is a dummy mobile app with API keys hidden using several different techniques.

I also wrote another article to achieve it during runtime, Steal that Api Key with a Man in the Middle Attack:

In order to help to demonstrate how to steal an API key, I have built and released in Github the Currency Converter Demo app for Android, which uses the same JNI/NDK technique we used in the earlier Android Hide Secrets app to hide the API key.

So, in this article you will learn how to setup and run a MitM attack to intercept https traffic in a mobile device under your control, so that you can steal the API key. Finally, you will see at a high level how MitM attacks can be mitigated.

An instrumentation framework can also be used during runtime to hook into the code that uses the API key in order to extract it. For example with the popular Frida framework:

Inject your own scripts into black box processes. Hook any function, spy on crypto APIs or trace private application code, no source code needed. Edit, hit save, and instantly see the results. All without compilation steps or program restarts.

So, no matter what it's done to secure the API key, once it's on the API request will be vulnerable to be extracted.

MOBILE API SECURITY

Anything that runs on the client side and needs some secret to access an API can be abused in different ways and you can learn more on this series of articles about Mobile API Security Techniques. This articles will teach you how API Keys, User Access Tokens, HMAC and TLS Pinning can be used to protect the API and how they can be bypassed.

POSSIBLE SOLUTIONS

I recommend you to read this answer I gave to the question How to secure an API REST for mobile app?, especially the sections Hardening and Shielding the Mobile App, Securing the API Server and A Possible Better Solution.

The possible best solution for your problem is known by Mobile App Attestation, that will let your backend know that what is making the request is indeed a genuine and untampered version of your mobile app, as you wish to achieve:

The purpose of it is to be able to send that key everytime I call a webservice that I've made, so I'm sure (or almost sure) that the call comes from the original app that I'm making and that will be published on the Play Store, and not from elsewhere.

Do You Want To Go The Extra Mile?

In any response to a security question I always like to reference the excellent work from the OWASP foundation.

For APIS

OWASP API Security Top 10

The OWASP API Security Project seeks to provide value to software developers and security assessors by underscoring the potential risks in insecure APIs, and illustrating how these risks may be mitigated. In order to facilitate this goal, the OWASP API Security Project will create and maintain a Top 10 API Security Risks document, as well as a documentation portal for best practices when creating or assessing APIs.

For Mobile Apps

OWASP Mobile Security Project - Top 10 risks

The OWASP Mobile Security Project is a centralized resource intended to give developers and security teams the resources they need to build and maintain secure mobile applications. Through the project, our goal is to classify mobile security risks and provide developmental controls to reduce their impact or likelihood of exploitation.

OWASP - Mobile Security Testing Guide:

The Mobile Security Testing Guide (MSTG) is a comprehensive manual for mobile app security development, testing and reverse engineering.



Related Topics



Leave a reply



Submit