· 7 years ago · Aug 20, 2018, 03:20 PM
1package my.company.domain;
2
3import android.content.Context;
4import android.content.SharedPreferences;
5import android.security.keystore.KeyProperties;
6import android.security.keystore.KeyProtection;
7import android.support.annotation.NonNull;
8import android.util.Base64;
9import android.util.Log;
10
11import java.io.DataInputStream;
12import java.io.FileInputStream;
13import java.io.IOException;
14import java.nio.ByteBuffer;
15import java.nio.charset.StandardCharsets;
16import java.security.GeneralSecurityException;
17import java.security.InvalidKeyException;
18import java.security.KeyFactory;
19import java.security.KeyStore;
20import java.security.KeyStoreException;
21import java.security.NoSuchAlgorithmException;
22import java.security.NoSuchProviderException;
23import java.security.PublicKey;
24import java.security.SecureRandom;
25import java.security.spec.X509EncodedKeySpec;
26
27import javax.crypto.BadPaddingException;
28import javax.crypto.Cipher;
29import javax.crypto.IllegalBlockSizeException;
30import javax.crypto.NoSuchPaddingException;
31import javax.crypto.SecretKey;
32import javax.crypto.spec.GCMParameterSpec;
33import javax.crypto.spec.SecretKeySpec;
34
35public class CryptoHelper {
36
37 public static final String TAG = CryptoHelper.class.getSimpleName();
38
39 private static final String KEY_ALIAS = "OI1lTI1lLI1l0";
40 private static final char[] KEY_PASSWORD = "Il0VELI1lO".toCharArray();
41
42 private static final String PREF_NAME = "CryptoPrefs";
43 private static final String KEY_ENCRYPTED_SECRET = "encryptedSecret";
44
45 private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
46
47 private static final int IV_SIZE = 12;
48 private static final int IV_BIT_LEN = IV_SIZE * 8;
49
50
51 //generate 128 bit key (16), other possible values 192(24), 256(32)
52 private static final int AES_KEY_SIZE = 16;
53 private static final String AES = KeyProperties.KEY_ALGORITHM_AES;
54 private static final String AES_MODE = AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE;
55
56 private static final String RSA = KeyProperties.KEY_ALGORITHM_RSA;
57 private static final String RSA_MODE = KeyProperties.KEY_ALGORITHM_RSA + "/" + KeyProperties.BLOCK_MODE_ECB + "/" + KeyProperties.ENCRYPTION_PADDING_NONE;
58 private static final String RSA_PROVIDER = "AndroidOpenSSL";
59
60 private final Context mContext;
61 private final SharedPreferences mPrefs;
62
63 private SecureRandom mSecureRandom;
64 private KeyStore mAndroidKeyStore;
65 private PublicKey mPublicKey;
66 private byte[] mEncryptedSecretKey;
67
68 public CryptoHelper(Context context) {
69 mContext = context;
70 mSecureRandom = new SecureRandom();
71 mPrefs = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
72 try {
73 mAndroidKeyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
74 mAndroidKeyStore.load(null);
75
76 } catch (KeyStoreException e) {
77 Log.wtf(TAG, "Could not get AndroidKeyStore!", e);
78 } catch (Exception e) {
79 Log.wtf(TAG, "Could not load AndroidKeyStore!", e);
80 }
81 }
82
83 public void reset() throws KeyStoreException {
84 mAndroidKeyStore.deleteEntry(KEY_ALIAS);
85 }
86
87 public byte[] encrypt(byte[] message){
88 SecretKey secretKey = getSecretKey();
89 try {
90 Cipher cipher = Cipher.getInstance(AES_MODE);
91 cipher.init(Cipher.ENCRYPT_MODE, getSecretKey());
92
93 byte[] cryptedBytes = cipher.doFinal(message);
94 byte[] iv = cipher.getIV();
95 byte[] encryptedSecretKey = getEncryptedSecretKey();
96 ByteBuffer buffer = ByteBuffer.allocate(IV_BIT_LEN + encryptedSecretKey.length + cryptedBytes.length);
97 buffer
98 .put(iv)
99 .put(encryptedSecretKey)
100 .put(cryptedBytes);
101 return buffer.array();
102 } catch (GeneralSecurityException e) {
103 e.printStackTrace();
104 }
105 return null;
106 }
107
108 public byte[] encrypt(String message){
109 return encrypt(message.getBytes(StandardCharsets.UTF_8));
110 }
111
112 public byte[] decrypt(byte[] bytes){
113 ByteBuffer buffer = ByteBuffer.wrap(bytes);
114 byte[] iv = new byte[IV_SIZE];
115 buffer.get(iv);
116 byte[] unused = getEncryptedSecretKey();
117 buffer.get(unused);
118 byte[] encryptedMessage = new byte[bytes.length - IV_SIZE - unused.length];
119 buffer.get(encryptedMessage);
120 try {
121 Cipher cipher = Cipher.getInstance(AES_MODE);
122 GCMParameterSpec parameterSpec = new GCMParameterSpec(IV_BIT_LEN, iv);
123 cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), parameterSpec);
124 byte[] decryptedMessage = cipher.doFinal(encryptedMessage);
125 return decryptedMessage;
126 } catch (GeneralSecurityException e) {
127 e.printStackTrace();
128 }
129 return null;
130 }
131
132 public String decryptToString(byte[] bytes){
133 return new String(decrypt(bytes), StandardCharsets.UTF_8);
134 }
135
136 public byte[] decrypt(FileInputStream fileToDecrypt){
137 byte[] buffer = null;
138 try {
139 buffer = new byte[fileToDecrypt.available()];
140 fileToDecrypt.read(buffer);
141 buffer = decrypt(buffer);
142 } catch (IOException e) {
143 e.printStackTrace();
144 }
145 return buffer;
146 }
147
148
149 public PublicKey getPublicKey() {
150 if (null == mPublicKey) {
151 mPublicKey = readPublicKey();
152 }
153 return mPublicKey;
154 }
155
156 public byte[] getEncryptedSecretKey() {
157 if (null == mEncryptedSecretKey){
158 mEncryptedSecretKey = Base64.decode(mPrefs.getString(KEY_ENCRYPTED_SECRET, null), Base64.NO_WRAP);
159 }
160 return mEncryptedSecretKey;
161 }
162
163 private void saveEncryptedSecretKey(byte[] encryptedSecretKey){
164 String base64EncryptedKey = Base64.encodeToString(encryptedSecretKey, Base64.NO_WRAP);
165 mPrefs.edit().putString(KEY_ENCRYPTED_SECRET, base64EncryptedKey).apply();
166 }
167
168 protected SecretKey getSecretKey(){
169 SecretKey secretKey = null;
170 try {
171 if (!mAndroidKeyStore.containsAlias(KEY_ALIAS)){
172 generateAndStoreSecureKey();
173 }
174 secretKey = (SecretKey) mAndroidKeyStore.getKey(KEY_ALIAS, KEY_PASSWORD);
175 } catch (KeyStoreException e) {
176 Log.wtf(TAG, "Could not check AndroidKeyStore alias!", e);
177 secretKey = null;
178 } catch (GeneralSecurityException e) {
179 e.printStackTrace();
180 secretKey = null;
181 }
182 return secretKey;
183 }
184
185 private void generateAndStoreSecureKey()
186 throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, KeyStoreException, BadPaddingException, IllegalBlockSizeException {
187 SecretKey secretKey = generateSecureRandomKey();
188 PublicKey publicKey = getPublicKey();
189 Cipher keyCipher = Cipher.getInstance(RSA_MODE, RSA_PROVIDER);
190 keyCipher.init(Cipher.DECRYPT_MODE, publicKey);
191 byte[] encryptedSecretKeyBytes = keyCipher.doFinal(secretKey.getEncoded());
192
193 saveEncryptedSecretKey(encryptedSecretKeyBytes);
194
195 KeyProtection keyProtection = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_VERIFY)
196 .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
197 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
198 .build();
199 mAndroidKeyStore.setEntry(KEY_ALIAS, new KeyStore.SecretKeyEntry(secretKey), keyProtection);
200 }
201
202
203 protected PublicKey readPublicKey() {
204 DataInputStream dis = null;
205 PublicKey key = null;
206 try {
207 dis = new DataInputStream(mContext.getResources().getAssets().open("public_key.der"));
208 byte[] keyBytes = new byte[dis.available()];
209 dis.readFully(keyBytes);
210
211 X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
212 KeyFactory facotory = KeyFactory.getInstance(RSA);
213 key = facotory.generatePublic(spec);
214 } catch (Exception e) {
215 key = null;
216 } finally {
217 if (null != dis) {
218 try {
219 dis.close();
220 } catch (IOException e) {
221 Log.wtf(TAG, "Cannot Close Stream!", e);
222 }
223 }
224 }
225 return key;
226 }
227
228 @NonNull
229 protected SecretKey generateSecureRandomKey() {
230 return new SecretKeySpec(generateSecureRandomBytes(AES_KEY_SIZE), AES);
231 }
232
233 @NonNull
234 protected byte[] generateSecureRandomBytes(int byteCount) {
235 byte[] keyBytes = new byte[byteCount];
236 mSecureRandom.nextBytes(keyBytes);
237 return keyBytes;
238 }
239}
240
241@Test
242public void testCrypto() throws Exception {
243 CryptoHelper crypto = new CryptoHelper(InstrumentationRegistry.getTargetContext());
244 crypto.reset();
245 String verySecretOpinion = "we're all doomed";
246 byte[] encrypt = crypto.encrypt(verySecretOpinion);
247 Assert.assertNotNull("Encrypted secret is Null!", encrypt);
248 Assert.assertFalse("encrypted Bytes are the same as Input!", new String(encrypt, StandardCharsets.UTF_8).equals(verySecretOpinion));
249 String decryptedString = crypto.decryptToString(encrypt);
250 Assert.assertNotNull("Decrypted String must be Non-Null!", decryptedString);
251 Assert.assertEquals("Decrypted String doesn't equal encryption input!", verySecretOpinion, decryptedString);
252}