第一次,我正在使用Android Keystore。我需要用android keystore保存数据。在浏览文档和很少的互联网教程后。我想出了一个java类,它将处理Post-M和Pre-M android设备的Keystore操作,如下所示:
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.security.KeyPairGeneratorSpec;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.support.annotation.RequiresApi;
import android.util.Base64;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.UnrecoverableEntryException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Calendar;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.x500.X500Principal;
public class Cryptography {
private static final String TAG = Cryptography.class.getSimpleName();
private static final String ANDROID_KEY_STORE_NAME = "AndroidKeyStore";
private static final String AES_MODE_M_OR_GREATER = "AES/GCM/NoPadding";
private static final String AES_MODE_LESS_THAN_M = "AES/CBC/PKCS5Padding";
// TODO update these bytes to be random for IV of encryption
private static final byte[] FIXED_IV = new byte[]{ 55, 54, 53, 52, 51, 50,
49, 48, 47,
46, 45, 44 };
private static final String CHARSET_NAME = "UTF-8";
private static final String RSA_ALGORITHM_NAME = "RSA";
private static final String RSA_MODE = "RSA/ECB/PKCS1Padding";
private static final String CIPHER_PROVIDER_NAME_ENCRYPTION_DECRYPTION_RSA = "AndroidOpenSSL";
private static final String CIPHER_PROVIDER_NAME_ENCRYPTION_DECRYPTION_AES = "BC";
private static final String SHARED_PREFERENCE_NAME = "XXXXXXXXXXXXXXX";
private static final String PRE_M_KEY_NAME = "XXXXXXXX";
private final String ENCRYPTED_KEY_NAME;
private final String KEY_ALIAS;
private final Context mContext;
public Cryptography(Context context, String Encrypted_Key_Name,String Key_Alias) {
this.mContext = context;
this.ENCRYPTED_KEY_NAME = Encrypted_Key_Name;
this.KEY_ALIAS = Key_Alias;
}
private void initKeys() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, NoSuchProviderException, InvalidAlgorithmParameterException,
UnrecoverableEntryException, NoSuchPaddingException, InvalidKeyException {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_NAME);
keyStore.load(null);
if (!keyStore.containsAlias(KEY_ALIAS)) {
initValidKeys();
} else {
boolean keyValid = false;
KeyStore.Entry keyEntry = keyStore.getEntry(KEY_ALIAS, null);
if (keyEntry instanceof KeyStore.SecretKeyEntry &&
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
keyValid = true;
}
if (keyEntry instanceof KeyStore.PrivateKeyEntry && android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) {
keyValid = true;
}
if (!keyValid) {
// System upgrade or something made key invalid
removeKeys(keyStore);
initValidKeys();
}
}
}
protected void removeKeys(KeyStore keyStore) throws KeyStoreException {
keyStore.deleteEntry(KEY_ALIAS);
removeSavedSharedPreferences();
}
private void initValidKeys() throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, CertificateException, UnrecoverableEntryException, NoSuchPaddingException, KeyStoreException, InvalidKeyException, IOException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
generateKeysForAPIMOrGreater();
} else {
generateKeysForAPILessThanM();
}
}
@SuppressLint("ApplySharedPref")
private void removeSavedSharedPreferences() {
SharedPreferences pref = mContext.getSharedPreferences(SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE);
pref.edit().clear().commit();
}
private void generateKeysForAPILessThanM() throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, CertificateException, UnrecoverableEntryException, NoSuchPaddingException, KeyStoreException, InvalidKeyException, IOException {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// Generate a key pair for encryption
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 30);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(mContext)
.setAlias(KEY_ALIAS)
.setSubject(new X500Principal("CN=" + KEY_ALIAS))
.setSerialNumber(BigInteger.TEN)
.setStartDate(start.getTime())
.setEndDate(end.getTime())
.build();
KeyPairGenerator kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM_NAME, ANDROID_KEY_STORE_NAME);
kpg.initialize(spec);
kpg.generateKeyPair();
saveEncryptedKey();
}
}
@SuppressLint("ApplySharedPref")
private void saveEncryptedKey() throws CertificateException, NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, KeyStoreException, NoSuchProviderException, UnrecoverableEntryException, IOException {
SharedPreferences pref = mContext.getSharedPreferences(SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE);
String encryptedKeyBase64encoded = pref.getString(ENCRYPTED_KEY_NAME, null);
if (encryptedKeyBase64encoded == null) {
Log.w(TAG, "saveEncryptedKey");
byte[] key = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(key);
byte[] encryptedKey = rsaEncryptKey(key);
encryptedKeyBase64encoded = Base64.encodeToString(encryptedKey, Base64.DEFAULT);
SharedPreferences.Editor edit = pref.edit();
edit.putString(PRE_M_KEY_NAME, encryptedKeyBase64encoded);
edit.commit();
Log.w(TAG, "Data Committed");
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
protected void generateKeysForAPIMOrGreater() throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
KeyGenerator keyGenerator;
keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE_NAME);
keyGenerator.init(
new KeyGenParameterSpec.Builder(KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
// NOTE no Random IV. According to above this is less secure but acceptably so.
.setRandomizedEncryptionRequired(false)
.build());
// Note according to [docs](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.html)
// this generation will also add it to the keystore.
keyGenerator.generateKey();
}
public String encryptData(String stringDataToEncrypt) throws NoSuchPaddingException, NoSuchAlgorithmException, UnrecoverableEntryException, CertificateException,
KeyStoreException, IOException, InvalidAlgorithmParameterException, InvalidKeyException, NoSuchProviderException, BadPaddingException, IllegalBlockSizeException {
initKeys();
Log.w(TAG, "encryptData: stringDataToEncrypt - "+stringDataToEncrypt);
if (stringDataToEncrypt == null) {
throw new IllegalArgumentException("Data to be decrypted must be non null");
}
Cipher cipher;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
cipher = Cipher.getInstance(AES_MODE_M_OR_GREATER);
cipher.init(Cipher.ENCRYPT_MODE, getSecretKeyAPIMorGreater(),
new GCMParameterSpec(128, FIXED_IV));
} else {
cipher = Cipher.getInstance(AES_MODE_LESS_THAN_M, CIPHER_PROVIDER_NAME_ENCRYPTION_DECRYPTION_AES);
try {
SecureRandom random = new SecureRandom();
// byte[] iv = random.generateSeed(16);
byte[] iv = new byte[16];
random.nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, getSecretKeyAPILessThanM(),ivSpec);
} catch (InvalidKeyException | IOException e) {
// Since the keys can become bad (perhaps because of lock screen change)
// drop keys in this case.
removeKeys();
throw e;
}
}
byte[] encodedBytes = cipher.doFinal(stringDataToEncrypt.getBytes(CHARSET_NAME));
String encryptedBase64Encoded = Base64.encodeToString(encodedBytes, Base64.DEFAULT);
Log.w(TAG, "encryptData: after encrypt - "+encryptedBase64Encoded);
return encryptedBase64Encoded;
}
public String decryptData(String encryptedData) throws NoSuchPaddingException, NoSuchAlgorithmException, UnrecoverableEntryException, CertificateException, KeyStoreException, IOException, InvalidAlgorithmParameterException, InvalidKeyException, NoSuchProviderException, BadPaddingException, IllegalBlockSizeException {
initKeys();
Log.w(TAG, "decryptData: encryptedData - "+encryptedData);
if (encryptedData == null) {
throw new IllegalArgumentException("Data to be decrypted must be non null");
}
byte[] encryptedDecodedData = Base64.decode(encryptedData, Base64.DEFAULT);
Cipher c;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
c = Cipher.getInstance(AES_MODE_M_OR_GREATER);
c.init(Cipher.DECRYPT_MODE, getSecretKeyAPIMorGreater(), new GCMParameterSpec(128, FIXED_IV));
} else {
c = Cipher.getInstance(AES_MODE_LESS_THAN_M, CIPHER_PROVIDER_NAME_ENCRYPTION_DECRYPTION_AES);
SecureRandom random = new SecureRandom();
byte[] iv = new byte[16];
random.nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
c.init(Cipher.DECRYPT_MODE, getSecretKeyAPILessThanM(),ivSpec);
//decodedBytes = c.doFinal(encryptedDecodedData,0,16);
}
} catch (InvalidKeyException | IOException e) {
// Since the keys can become bad (perhaps because of lock screen change)
// drop keys in this case.
removeKeys();
throw e;
}
final byte[] decodedBytes = c.doFinal(encryptedDecodedData);
Log.w(TAG, "encryptData: after decrypt - "+new String(decodedBytes, CHARSET_NAME));
return new String(decodedBytes, CHARSET_NAME);
}
private Key getSecretKeyAPIMorGreater() throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException, UnrecoverableKeyException {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_NAME);
keyStore.load(null);
try {
return keyStore.getKey(KEY_ALIAS, null);
} catch (UnrecoverableKeyException e) {
throw new UnsupportedOperationException();
}
}
private Key getSecretKeyAPILessThanM() throws CertificateException, NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, KeyStoreException, NoSuchProviderException, UnrecoverableEntryException, IOException {
SharedPreferences pref = mContext.getSharedPreferences(SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE);
String encryptedKeyBase64Encoded = pref.getString(PRE_M_KEY_NAME, null);
Log.w(TAG, "getSecretKeyAPILessThanM: encryptedKeyBase64Encoded - "+encryptedKeyBase64Encoded);
// need to check null, omitted here
byte[] encryptedKey = Base64.decode(encryptedKeyBase64Encoded, Base64.DEFAULT);
byte[] key = rsaDecryptKey(encryptedKey);
return new SecretKeySpec(key, "AES");
}
private byte[] rsaEncryptKey(byte[] secret) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, NoSuchProviderException, NoSuchPaddingException, UnrecoverableEntryException, InvalidKeyException {
Log.w(TAG, "rsaEncryptKe y: secret - "+secret);
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_NAME);
keyStore.load(null);
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null);
RSAPublicKey publicKey = (RSAPublicKey) privateKeyEntry.getCertificate().getPublicKey();
Cipher inputCipher = Cipher.getInstance(RSA_MODE, CIPHER_PROVIDER_NAME_ENCRYPTION_DECRYPTION_RSA);
inputCipher.init(Cipher.ENCRYPT_MODE, publicKey );
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, inputCipher);
cipherOutputStream.write(secret);
cipherOutputStream.close();
byte[] encryptedKeyAsByteArray = outputStream.toByteArray();
Log.w(TAG, "rsaEncryptKey: encryptedKeyAsByteArray - "+encryptedKeyAsByteArray);
return encryptedKeyAsByteArray;
}
private byte[] rsaDecryptKey(byte[] encrypted) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException,
UnrecoverableEntryException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException {
Log.w(TAG, "rsaDecryptKey: encrypted - "+encrypted);
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_NAME);
keyStore.load(null);
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(KEY_ALIAS, null);
RSAPrivateKey privateKey = (RSAPrivateKey)privateKeyEntry.getPrivateKey();
Cipher output = Cipher.getInstance(RSA_MODE, CIPHER_PROVIDER_NAME_ENCRYPTION_DECRYPTION_RSA);
output.init(Cipher.DECRYPT_MODE, privateKey);
ByteArrayInputStream inputStream = new ByteArrayInputStream(encrypted);
CipherInputStream cipherInputStream = new CipherInputStream(inputStream,output);
ArrayList<Byte> values = new ArrayList<>();
int nextByte;
while ((nextByte = cipherInputStream.read())!=-1) {
values.add((byte)nextByte);
}
byte[] decryptedKeyAsBytes = new byte[values.size()];
for(int i = 0; i < decryptedKeyAsBytes.length; i++) {
decryptedKeyAsBytes[i] = values.get(i) ;
}
inputStream.close();
cipherInputStream.close();
Log.w(TAG, "rsaDecryptKey: decryptedKeyAsBytes - "+decryptedKeyAsBytes);
return decryptedKeyAsBytes;
}
public void removeKeys() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_NAME);
keyStore.load(null);
removeKeys(keyStore);
}
}
这对于Post-M设备来说非常合适,但我遇到了Pre-M设备的问题。我有RSA解密问题,logcat如下所示:
java.io.IOException: Error while finalizing cipher
at javax.crypto.CipherInputStream.fillBuffer(CipherInputStream.java:104)
at javax.crypto.CipherInputStream.read(CipherInputStream.java:130)
at com.xxxx.XXXXXXX.data.storage.Cryptography.rsaDecryptKey(Cryptography.java:337)
at com.xxxx.XXXXXXX.data.storage.Cryptography.getSecretKeyAPILessThanM(Cryptography.java:290)
at com.xxxx.XXXXXXX.data.storage.Cryptography.decryptData(Cryptography.java:256)
at com.xxxx.XXXXXXX.ui.activities.SecurePinAuthentication.securePinLogin(SecurePinAuthentication.java:286)
at com.xxxx.XXXXXXX.ui.activities.SecurePinAuthentication.onClick(SecurePinAuthentication.java:191)
at android.view.View.performClick(View.java:5197)
at android.view.View$PerformClick.run(View.java:20909)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:5944)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)
Caused by: javax.crypto.BadPaddingException: error:0407106B:rsa routines:RSA_padding_check_PKCS1_type_2:block type is not 02
at com.android.org.conscrypt.NativeCrypto.RSA_private_decrypt(Native Method)
at com.android.org.conscrypt.OpenSSLCipherRSA.engineDoFinal(OpenSSLCipherRSA.java:273)
at com.android.org.conscrypt.OpenSSLCipherRSA.engineDoFinal(OpenSSLCipherRSA.java:297)
at javax.crypto.Cipher.doFinal(Cipher.java:1314)
at javax.crypto.CipherInputStream.fillBuffer(CipherInputStream.java:102) ... 16 more
javax.crypto.BadPaddingException: pad block corrupted
01-28 17:17:53.274 at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(BaseBlockCipher.java:854)
01-28 17:17:53.274 at javax.crypto.Cipher.doFinal(Cipher.java:1340)
01-28 17:17:53.274 at com.xxxx.XXXXXXX.data.storage.Cryptography.decryptData(Cryptography.java:266)
01-28 17:17:53.274 at com.xxxx.XXXXXXX.ui.activities.SecurePinAuthentication.securePinLogin(SecurePinAuthentication.java:286)
01-28 17:17:53.274 at com.xxxx.XXXXXXX.ui.activities.SecurePinAuthentication.onClick(SecurePinAuthentication.java:191)
01-28 17:17:53.274 at android.view.View.performClick(View.java:5197)
01-28 17:17:53.274 at android.view.View$PerformClick.run(View.java:20909)
01-28 17:17:53.274 at android.os.Handler.handleCallback(Handler.java:739)
01-28 17:17:53.274 at android.os.Handler.dispatchMessage(Handler.java:95)
01-28 17:17:53.274 at android.os.Looper.loop(Looper.java:145)
01-28 17:17:53.274 at android.app.ActivityThread.main(ActivityThread.java:5944)
01-28 17:17:53.274 at java.lang.reflect.Method.invoke(Native Method)
01-28 17:17:53.274 at java.lang.reflect.Method.invoke(Method.java:372)
01-28 17:17:53.274 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)
01-28 17:17:53.274 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)
我在互联网上搜索并尝试了很多方法&amp;失败。最后,我在这里发帖。非常感谢帮助。