Last active
October 13, 2017 02:20
-
-
Save Heilum/675de27e6ae59fd0bf20c49b87e72ed3 to your computer and use it in GitHub Desktop.
Use AES/RSA encryption algorithm to make a KeyChain on Android
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.swordfishsoft.security; | |
import android.content.Context; | |
import android.content.SharedPreferences; | |
import android.security.KeyPairGeneratorSpec; | |
import android.security.keystore.KeyProperties; | |
import android.util.Base64; | |
import android.util.Log; | |
import java.io.ByteArrayInputStream; | |
import java.io.ByteArrayOutputStream; | |
import java.math.BigInteger; | |
import java.nio.charset.StandardCharsets; | |
import java.security.KeyPairGenerator; | |
import java.security.KeyStore; | |
import java.security.SecureRandom; | |
import java.util.ArrayList; | |
import java.util.Calendar; | |
import javax.crypto.Cipher; | |
import javax.crypto.CipherInputStream; | |
import javax.crypto.CipherOutputStream; | |
import javax.crypto.spec.SecretKeySpec; | |
import javax.security.auth.x500.X500Principal; | |
/** | |
* Created by chenwanfei on 10/10/2017. | |
* | |
* | |
* | |
* How to use: | |
SecurityDataStore sds = new SecurityDataStore(aActivity); | |
sds.store("test","abc中国"); | |
String value = sds.retrieveString("test"); | |
Log.e("abc","value="+value); | |
sds.removeKey("test"); | |
inspired by => https://medium.com/@ericfu/securely-storing-secrets-in-an-android-application-501f030ae5a3 | |
* | |
*/ | |
public class SecurityDataStore { | |
private static final String AndroidKeyStore = "AndroidKeyStore"; | |
private static final String KEY_ALIAS = "RSA"; | |
private KeyStore mKeyStore; | |
private static final String RSA_MODE = "RSA/ECB/PKCS1Padding"; | |
private Context mContext; | |
private static final String AES_MODE = "AES/ECB/PKCS7Padding"; | |
private static final String RSA_PROVIDER = "AndroidKeyStoreBCWorkaround"; | |
public SecurityDataStore(Context context) { | |
try { | |
mKeyStore = KeyStore.getInstance(AndroidKeyStore); | |
mKeyStore.load(null); | |
// Generate the RSA key pairs | |
if (!mKeyStore.containsAlias(KEY_ALIAS)) { | |
// Generate a key pair for encryption | |
Calendar start = Calendar.getInstance(); | |
Calendar end = Calendar.getInstance(); | |
//start.add(Calendar.YEAR, -30); | |
end.add(Calendar.YEAR, 30); | |
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context) | |
.setAlias(KEY_ALIAS) | |
.setSubject(new X500Principal("CN=" + KEY_ALIAS)) | |
.setSerialNumber(BigInteger.TEN) | |
.setStartDate(start.getTime()) | |
.setEndDate(end.getTime()) | |
.build(); | |
KeyPairGenerator kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, AndroidKeyStore); | |
kpg.initialize(spec); | |
kpg.generateKeyPair(); | |
mContext = context; | |
} | |
} catch (Exception e) { | |
Log.e("123","init SecurityDataStore fail",e); | |
mKeyStore = null; | |
} | |
} | |
private byte[] rsaEncrypt(byte[] secret){ | |
if (mKeyStore != null){ | |
try{ | |
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) mKeyStore.getEntry(KEY_ALIAS, null); | |
// Encrypt the text | |
Cipher inputCipher = Cipher.getInstance(RSA_MODE, RSA_PROVIDER); | |
inputCipher.init(Cipher.ENCRYPT_MODE, privateKeyEntry.getCertificate().getPublicKey()); | |
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | |
CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, inputCipher); | |
cipherOutputStream.write(secret); | |
cipherOutputStream.close(); | |
byte[] vals = outputStream.toByteArray(); | |
return vals; | |
}catch (Exception e){ | |
Log.e("123","init SecurityDataStore fail",e); | |
return null; | |
} | |
}else{ | |
return null; | |
} | |
} | |
private byte[] rsaDecrypt(byte[] encrypted) { | |
if (mKeyStore != null){ | |
try{ | |
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)mKeyStore.getEntry(KEY_ALIAS, null); | |
Cipher output = Cipher.getInstance(RSA_MODE, RSA_PROVIDER); | |
output.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey()); | |
CipherInputStream cipherInputStream = new CipherInputStream( | |
new ByteArrayInputStream(encrypted), output); | |
ArrayList<Byte> values = new ArrayList<>(); | |
int nextByte; | |
while ((nextByte = cipherInputStream.read()) != -1) { | |
values.add((byte)nextByte); | |
} | |
byte[] bytes = new byte[values.size()]; | |
for(int i = 0; i < bytes.length; i++) { | |
bytes[i] = values.get(i).byteValue(); | |
} | |
return bytes; | |
}catch (Exception e){ | |
Log.e("123","init SecurityDataStore fail",e); | |
return null; | |
} | |
}else{ | |
return null; | |
} | |
} | |
private String dataKeyForKey(String key){ | |
return key +"_data"; | |
} | |
private String encryptedAESKeyForKey(String key){ | |
return key+"_encrypted_AESKey"; | |
} | |
public boolean store(String key,String value){ | |
return store(key,value.getBytes(StandardCharsets.UTF_8)); | |
} | |
public void removeKey(String key){ | |
SharedPreferences pref = mContext.getSharedPreferences(AndroidKeyStore, Context.MODE_PRIVATE); | |
SharedPreferences.Editor edit = pref.edit(); | |
edit.remove(dataKeyForKey(key)); | |
edit.remove(encryptedAESKeyForKey(key)); | |
edit.commit(); | |
} | |
public String retrieveString(String key){ | |
byte[] data = retrieve(key); | |
if (data != null){ | |
return new String(data, StandardCharsets.UTF_8); | |
}else{ | |
return null; | |
} | |
} | |
public byte[] retrieve(String key){ | |
SharedPreferences pref = mContext.getSharedPreferences(AndroidKeyStore, Context.MODE_PRIVATE); | |
String enryptedAESKeyBase64 = pref.getString(encryptedAESKeyForKey(key),null); | |
String dataBase64 = pref.getString(dataKeyForKey(key),null); | |
if (enryptedAESKeyBase64 != null && dataBase64 != null){ | |
byte[] encryptedAESKey = Base64.decode(enryptedAESKeyBase64,Base64.DEFAULT); | |
byte[] encryptedData = Base64.decode(dataBase64,Base64.DEFAULT); | |
byte[] decryptedAESKey = rsaDecrypt(encryptedAESKey); | |
if (decryptedAESKey != null && encryptedData != null){ | |
try{ | |
Cipher c = Cipher.getInstance(AES_MODE, "BC"); | |
c.init(Cipher.DECRYPT_MODE,new SecretKeySpec(decryptedAESKey, "AES")); | |
byte[] decodedBytes = c.doFinal(encryptedData); | |
return decodedBytes; | |
}catch (Exception e){ | |
Log.e("123","retrieve fail",e); | |
return null; | |
} | |
}else{ | |
return null; | |
} | |
}else{ | |
return null; | |
} | |
} | |
public boolean store(String key,byte[] value){ | |
try{ | |
byte[] AESkey = new byte[16]; | |
SecureRandom secureRandom = new SecureRandom(); | |
secureRandom.nextBytes(AESkey); | |
Cipher c = Cipher.getInstance(AES_MODE, "BC"); | |
c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(AESkey, "AES")); | |
byte[] encodedBytes = c.doFinal(value); | |
String encryptedBase64Encoded = Base64.encodeToString(encodedBytes, Base64.DEFAULT); | |
SharedPreferences pref = mContext.getSharedPreferences(AndroidKeyStore, Context.MODE_PRIVATE); | |
SharedPreferences.Editor edit = pref.edit(); | |
edit.putString(dataKeyForKey(key),encryptedBase64Encoded); | |
byte[] encryptedAESKey = rsaEncrypt(AESkey); | |
String enryptedKeyB64 = Base64.encodeToString(encryptedAESKey, Base64.DEFAULT); | |
edit.putString(encryptedAESKeyForKey(key),enryptedKeyB64); | |
edit.commit(); | |
return true; | |
}catch (Exception e){ | |
Log.e("123","store fail",e); | |
return false; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment