aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/github/daneren2005/dsub/util/KeyStoreUtil.java
blob: 8ff9ec694d664b4f6c263043e3c0a46db0013853 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package github.daneren2005.dsub.util;

import android.annotation.TargetApi;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.support.annotation.NonNull;
import android.util.Base64;
import android.util.Log;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;

@TargetApi(23)
public class KeyStoreUtil {
    private static String TAG = KeyStoreUtil.class.getSimpleName();
    private static final String KEYSTORE_ALIAS = "DSubKeyStoreAlias";
    private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
    private static final String KEYSTORE_CIPHER_PROVIDER = "AndroidKeyStoreBCWorkaround";
    private static final String KEYSTORE_TRANSFORM = "AES/CBC/PKCS7Padding";
    private static final String KEYSTORE_BYTE_ENCODING = "UTF-8";

    public static void loadKeyStore() throws KeyStoreException, IOException,
            CertificateException, NoSuchAlgorithmException {

        // Load keystore
        KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
        keyStore.load(null);

        // Check if keystore has been used before
        if (!keyStore.containsAlias(KEYSTORE_ALIAS)) {
            // If alias does not exist, keystore hasn't been used before
            // Create a new secret key to store in the keystore
            try {
                Log.w(TAG, "Generating keys.");
                generateKeys();
            } catch (Exception e) {
                Log.w(TAG, "Key generation failed.");
                Log.w(TAG, Log.getStackTraceString(e));
            }
        }
    }

    private static void generateKeys() throws InvalidAlgorithmParameterException,
            NoSuchAlgorithmException, NoSuchProviderException {
        KeyGenerator keyGen = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER);
        keyGen.init(new KeyGenParameterSpec.Builder(KEYSTORE_ALIAS,
                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                .build());
        keyGen.generateKey();
    }

    private static Key getKey() throws KeyStoreException, CertificateException,
            NoSuchAlgorithmException, IOException, UnrecoverableEntryException {

        // Attempt to load keystore
        KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
        keyStore.load(null);

        // Fetch and return secret key
        return keyStore.getKey(KEYSTORE_ALIAS, null);
    }

    public static String encrypt(@NonNull String plainTextString) {
        Log.d(TAG, "Encrypting password...");
        try {
            // Retrieve secret key
            final Key key = getKey();

            // Initialize cipher
            Cipher cipher = Cipher.getInstance(KEYSTORE_TRANSFORM, KEYSTORE_CIPHER_PROVIDER);
            cipher.init(Cipher.ENCRYPT_MODE, key);

            // Create stream for storing data
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

            // Write the IV length first so the IV can be split from the encrypted password
            outputStream.write(cipher.getIV().length);

            // Write the auto-generated IV
            outputStream.write(cipher.getIV());

            // Encrypt the plaintext and write the encrypted string
            outputStream.write(cipher.doFinal(plainTextString.getBytes(KEYSTORE_BYTE_ENCODING)));

            // Encode the return full stream for storage
            String encryptedPassword = Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP);
            if(decrypt((encryptedPassword)) == null) {
                Log.e(TAG, "We couldn't decrypt the password we just encypted!");
                return null;
            }

            Log.d(TAG, "Password encryption successful");
            return encryptedPassword;
        } catch (Exception e) {
            Log.w(TAG, "Password encryption failed");
            Log.d(TAG, Log.getStackTraceString(e));
            return null;
        }
    }

    public static String decrypt(@NonNull String encryptedString) {
        try {
            // Retrieve secret key
            final Key key = getKey();

            // Decode the string from Base64
            byte[] decodedBytes = Base64.decode(encryptedString, Base64.NO_WRAP);
            int ivLength = decodedBytes[0];
            int encryptedLength = decodedBytes.length - (ivLength + 1);

            // Get IV from decoded string
            byte[] ivBytes = new byte[ivLength];
            System.arraycopy(decodedBytes, 1, ivBytes, 0, ivLength);

            // Get encrypted password from decoded string
            byte[] encryptedBytes = new byte[encryptedLength];
            System.arraycopy(decodedBytes, ivLength + 1, encryptedBytes, 0, encryptedLength);

            // Initialize cipher using the IV from the dencoded string
            Cipher cipher = Cipher.getInstance(KEYSTORE_TRANSFORM, KEYSTORE_CIPHER_PROVIDER);
            IvParameterSpec ivParamSpec = new IvParameterSpec(ivBytes);
            cipher.init(Cipher.DECRYPT_MODE, key, ivParamSpec);

            // Decrypt the password
            String decryptedString = new String(cipher.doFinal(encryptedBytes));

            // Return the decrypted password string
            return decryptedString;
        } catch (Exception e) {
            Log.w(TAG, "Password decryption failed");
            Log.w(TAG, Log.getStackTraceString(e));
            return null;
        }
    }
}