25

During the creation of simple messaging android application that is to encrypt/decrypt messages and send them through internet, I decided to use RSA public/private key encryption. Question is how to store private key, so that even if phone is maliciously rooted, the key would stay safe? As far as I understood, KeyStore is used for certificates, and cannot be used for this? Should I encrypt private key as text file with AES? I have very little experience with security, so please feel free to correct my ideas, and give your opinion!

Kind Regards.

user32981
  • 251
  • 1
  • 3
  • 3
  • 3
    @LokiSinclair You can put the public key wherever you like and show it to whoever you like, that's why it's public. So storing it with the private key is fine. You just need to keep the private key in a safe place. – Peanut Nov 20 '13 at 22:46
  • @LokiSinclair Sorry but that is just wrong. Private keys are best stored on the device that should be able to decrypt messages. Backups can be made online but this poses a security risk. Also, there is no risk whatsoever in storing the public key at the same place as the private key. – joakimb Aug 18 '14 at 10:38

4 Answers4

11

I think KeyStore could be suitable for your use. It is able to store RSA keys and encrypts them using AES so even with root access, they cannot be extracted without the password or bruteforcing.

There's a good post here about using KeyStore: http://nelenkov.blogspot.fr/2012/05/storing-application-secrets-in-androids.html

gabrielwong
  • 301
  • 1
  • 6
  • Thanks for the reply, the blog you linked is super useful! There is post about password based encryption, which I found more appropriate and desirable than depending on KeyStore, at least for my application. Thank you again! – user32981 Nov 20 '13 at 20:52
4

You can persist your RSA public/private key using SharedPreference on android. In order to keep your keys safe when the phone is maliciously rooted, you can do the following steps:

1: When you want to ecrypt any data generate a key pair.
2: Prompt the user for a password.
3: Use that password to generate a symmetric key to encrypt your private key.
4: You can encrypt your data using the public key and decrypt using private key.
5: You can keep a session for the password prompted in step 2. During that session, you can use the symmetric key(generated from password) to encrypt/decrypt the private key.

The following code snippet shows to how to store & fetch the public key

public void setPublicKey(PublicKey publicKey, String key, Context context) {

    byte[] pubKey = publicKey.getEncoded();
    String pubKeyString = Base64.encodeBytes(pubKey);
    this.setString(key, pubKeyString, context);
}

public PublicKey getPublicKey(String key,Context context) {

    PublicKey pKey = null;
    try {

        String pubString = this.getString(key, context);

        if(pubString!=null) {
            byte[] binCpk = Base64.decode(pubString);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(binCpk);
            pKey = keyFactory.generatePublic(publicKeySpec);
        }
        }catch(Exception e){
    }
    return pKey;
}

The following code snippet shows how to store& fetch the private key.

public void setPrivateKey(PrivateKey privateKey, String key, Context context) {

    byte[] priKey = privateKey.getEncoded();
    String priKeyString = Base64.encodeBytes(priKey);
    this.setString(key, priKeyString, context);
}

public PrivateKey getPrivateKey(String key, Context context) {

    PrivateKey privateKey = null;

    try {
        String privateString = this.getString(key, context);
        if(privateString!=null){
            byte[] binCpk = Base64.decode(privateString);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(binCpk);
            privateKey = keyFactory.generatePrivate(privateKeySpec);
        }
    } 
    catch(Exception e){
    }
    return privateKey;
}
Vikas
  • 4,263
  • 1
  • 34
  • 39
  • 7
    Please don't do this for private keys. It's harder to get right than it looks. Either use the built-in keystore API (available in Android 4.3+) or use a regular `KeyStore` (BKS/JKS) to store your keys and certificates. The format has integrity protection and encryption and can be protected by a password. – Nikolay Elenkov Aug 19 '14 at 02:00
  • am I right when I am saying that you just encoding the key in the set method? – narancs Jul 31 '17 at 19:53
3

None of the keystores (P12, JKS, AKS) in the file system can be secure enough to hold RSA private keys. Only SmartCard or secure tokens can provide high-level security. Read this book: "Android Security Internals". In this book you will find good description of Android Security and JCA providers.

Josh Correia
  • 3,807
  • 3
  • 33
  • 50
Makko
  • 460
  • 1
  • 6
  • 25
  • 4
    Is interesting to realice that the author of "Android Security Internals" himself recommend to use "regular `KeyStore` (BKS/JKS) to store your keys and certificates". Just read the comments from previous answer. Still recommendation is valid. Very good book and blog from Nikolay Elenkov. – jfuentes Feb 19 '16 at 13:28
0

Yes, you can use KeyStore to keep your RSA PrivateKey in Android Studio, and retrieve it for signing as needed. The basic idea is that you use "AndroidKeystore" as the provider when generating the keys. This guy: https://stackoverflow.com/questions/49410575/keystore-operation-failed-with-rsa-sign-and-verify#= had the important point of making sure you set the signature padding. That got it working for me, as follows:

public void storeKeyAsymmetric(){    //Generate the keys (public and private together) using KeyStore
KeyPairGenerator kpGenerator = null;
    try {
        kpGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    catch (NoSuchProviderException e) {
        e.printStackTrace();
    }
    try {
        kpGenerator.initialize(new KeyGenParameterSpec.Builder("aliasOfYourChoice", KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
                .setDigests(KeyProperties.DIGEST_SHA512, KeyProperties.DIGEST_SHA256)
                .setKeySize(2048)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1, KeyProperties.ENCRYPTION_PADDING_RSA_OAEP, KeyProperties.ENCRYPTION_PADDING_NONE)
                .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1, KeyProperties.SIGNATURE_PADDING_RSA_PSS)
                .build());
        keyPairAsymmetric = kpGenerator.generateKeyPair();
        devicePublic = keyPairAsymmetric.getPublic();
        byte[] encoding = devicePublic.getEncoded();
        strDevicePublicPEM = Crypto.writePEM(encoding);
    } catch (InvalidAlgorithmParameterException e) {
        e.printStackTrace();
    }
}

Later, you can use that private key to sign a message as follows:

public static String verifiedDeviceSignature(String dataToSign){
    boolean verified = false;
    String signature = null;
    MessageDigest digest = null;
    try {
        digest = MessageDigest.getInstance("SHA-512");
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    digest.update(dataToSign.getBytes(StandardCharsets.UTF_8));
    byte[] hash = digest.digest();

    try {
        KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
        ks.load(null);
        //******This is a PrivateKeyEntry
        KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) ks.getEntry("aliasOfYourChoice", null);  //null if you don't have key locked up with password
        PrivateKey privateKey = privateKeyEntry.getPrivateKey();
        Signature s = Signature.getInstance("SHA512withRSA");
        s.initSign(privateKey);
        s.update(dataToSign.getBytes(StandardCharsets.UTF_8));  //TODO:  Change this to hash
        byte[] sig = s.sign();

        PublicKey publicKey = ks.getCertificate("aliasOfYourChoice").getPublicKey();

        Signature v = Signature.getInstance("SHA512withRSA");
        v.initVerify(publicKey);
        v.update(dataToSign.getBytes(StandardCharsets.UTF_8));  //TODO:  Change this to hash
        verified = v.verify(sig);
        String strSig = new String(Base64.encode(sig, 2));
        signature = strSig;
    } catch (KeyStoreException e) {
        e.printStackTrace();
    } catch (CertificateException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (UnrecoverableEntryException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (SignatureException e) {
        e.printStackTrace();
    }

    if(verified){
        Log.d("***verifiedDeviceSignature*: ", "Signature Verified");
        //TODO:  URL encode
        return signature;
    }else {
        return "Not verified.";
    }
}
Adam Winter
  • 1,680
  • 1
  • 12
  • 26