I am trying to have secret (String) in the app, anywhere, save to be! So I came up with this idea to use the keyStore to store the key and only use it for encryption and decryption of my secret. Here is how I save (encrypt) my secret:
public static boolean setKeyStoreString(String strToStore, Context context) {
if (strToStore == null) return false;
if (strToStore.length() == 0) return false;
Log.e(TAG, strToStore);
try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
int nBefore = keyStore.size();
// Create the keys if necessary
if (!keyStore.containsAlias("phrase")) {
KeyGenerator generator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder("phrase", KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setKeySize(256)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationValidityDurationSeconds(-1)
.setRandomizedEncryptionRequired(false)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setUserAuthenticationRequired(false)
.build();
generator.init(spec);
generator.generateKey();
}
int nAfter = keyStore.size();
Log.v(TAG, "Before = " + nBefore + " After = " + nAfter);
String filesDirectory = context.getFilesDir().getAbsolutePath();
String encryptedDataFilePath = filesDirectory + File.separator + "my_phrase";
// Log.v(TAG, "strPhrase = " + strToStore);
// Log.v(TAG, "dataDirectory = " + dataDirectory);
// Log.v(TAG, "filesDirectory = " + filesDirectory);
// Log.v(TAG, "encryptedDataFilePath = " + encryptedDataFilePath);
SecretKey secret = (SecretKey) keyStore.getKey("phrase", null);
Cipher inCipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
inCipher.init(Cipher.ENCRYPT_MODE, secret);
CipherOutputStream cipherOutputStream = new CipherOutputStream(
new FileOutputStream(encryptedDataFilePath), inCipher);
byte[] bytesToStore = strToStore.getBytes("UTF-8");
cipherOutputStream.write(bytesToStore);
try {
cipherOutputStream.close();
} catch (Exception ex) {
ex.printStackTrace();
}
return true;
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
}
return false;
}
and here is how I try to retrieve it:
public static String getKeyStoreString(final Context context) {
KeyStore keyStore;
String recoveredSecret = "";
String filesDirectory = context.getFilesDir().getAbsolutePath();
String encryptedDataFilePath = filesDirectory + File.separator + "my_phrase";
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
SecretKey secretKey = (SecretKey)
keyStore.getKey("phrase", null);
if (secretKey == null) throw new RuntimeException("secretKey is null");
Cipher outCipher;
outCipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
outCipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(
new byte[outCipher.getBlockSize()]));
CipherInputStream cipherInputStream = new CipherInputStream(
new FileInputStream(encryptedDataFilePath), outCipher);
byte[] roundTrippedBytes = new byte[1000]; //TODO: dynamically resize as we get more data
int index = 0;
int nextByte;
while ((nextByte = cipherInputStream.read()) != -1) {
roundTrippedBytes[index] = (byte) nextByte;
index++;
}
recoveredSecret = new String(roundTrippedBytes, 0, index, "UTF-8");
Log.e(TAG, "round tripped string = " + recoveredSecret);
} catch (Exception e) {
e.printStackTrace();
}
Log.e(TAG, "recovered: " + recoveredSecret);
return recoveredSecret;
}
the problem is - the result comes a little damaged in the beginning,
i.e: some cool text for extraction >>>>> �k��X�&�ALqM,A� text for
extraction
i have the same problem i used Base64.encode(your byte[], Base64.DEFAULT);to encode and Base64.decode(your byte[], Base64.DEFAULT) to decode
try to put byte[] encode = Base64.encode(bytesToStore, Base64.DEFAULT)
after byte[] bytesToStore = strToStore.getBytes("UTF-8");
in the public static boolean setKeyStoreString() and check in the "encode" log
Related
I need to encrypt the ACCESS_TOKEN. While decrypting, getting error:
** error:0407806d:RSA routines:decrypt:DATA_LEN_NOT_EQUAL_TO_MOD_LEN**
private String ACCESS_TOKEN = "bearer
nG25Uokr3eF0WAisEcoS4hb1isLwR2qbOGu3UnwARGfeBNlP7RToSf3DCmowl99-TX0nrwL1qElIRZALFNbBXQPL6weVhJk9LRjJAoD9oBlTPtfDNMAZXlLqBqWnYZoxNyfQoPUE_Y0iMBcj_j6RqOfJc4Npid7Wo1AoipXOPYt1JLMfdHN9TZvtn6SxNP9UFipDANkcnHsurDwjPV_X0PdzyqsgXuoIjfAQLd7IonVYGZYmB_SYO68q5CorhH7hA01iIm7TDeUrOAM1p2C9W84rV6nMzMZS-7LPoweMWPxaLHcj15ex3TR16PGNGwbfiRPMLxNjmpqQEi3Mfqax2mk9qHL6LNb-OQK_5y9Zo9w1nC55iQhM-PbF96kgYa5zM2o94yI1IhcWAs-fJEe5tPsT3Dj_QfLWeNVblzDysfNwNajCGnauuPLzG-5qrGgNRtw0Dou8eNhk1lplDXxqu-G9kRyK1KKnPtuyCawzEJ_-4aEHdeA3-QSEqWCphu6w";
Please refer the code:
import android.util.Base64;
import com.itc.classmate.application.MyApplication;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
/**
*
*/
public enum EncryptMoreThn256Byte {
INSTANCE;
private SecretKey secKey;
// private String ACCESS_TOKEN = "bearer nG25Uokr3eF0WAisEcoS4hb1isLwR2qbOGu3UnwARGfeBNlP7RToSf3DCmowl99-TX0nrwL1qElIRZALFNbBXQPL6weVhJk9LRjJAoD9oBlTPtfDNMAZXlLqBqWnYZoxNyfQoPUE_Y0iMBcj_j6RqOfJc4Npid7Wo1AoipXOPYt1JLMfdHN9TZvtn6SxNP9UFipDANkcnHsurDwjPV_X0PdzyqsgXuoIjfAQLd7IonVYGZYmB_SYO68q5CorhH7hA01iIm7TDeUrOAM1p2C9W84rV6nMzMZS-7LPoweMWPxaLHcj15ex3TR16PGNGwbfiRPMLxNjmpqQEi3Mfqax2mk9qHL6LNb-OQK_5y9Zo9w1nC55iQhM-PbF96kgYa5zM2o94yI1IhcWAs-fJEe5tPsT3Dj_QfLWeNVblzDysfNwNajCGnauuPLzG-5qrGgNRtw0Dou8eNhk1lplDXxqu-G9kRyK1KKnPtuyCawzEJ_-4aEHdeA3-QSEqWCphu6w";
// private String initialText = "this is working";
private EncryptMoreThn256Byte() {
AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "called constructor of EncryptMoreThn256Byte");
KeyGenerator generator = null;
try {
generator = KeyGenerator.getInstance("AES");
generator.init(128); // The AES key size in number of bits
secKey = generator.generateKey();
AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "EncryptMoreThn256Byte: secKey+++ " + secKey.getEncoded());
// SecureRandom random = new SecureRandom();
// byte[] salt = new byte[(256/8)];
// random.nextBytes(salt);
// KeySpec keySpec = new PBEKeySpec(initialText.toCharArray(), salt, 1000, 256);
// SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
// byte[] decrptedSecrateKey = keyFactory.generateSecret(keySpec).getEncoded();
// // secKey = new SecretKeySpec(decrptedSecrateKey, "AES");
// secKey = new SecretKeySpec(decrptedSecrateKey, 0, decrptedSecrateKey.length, "AES");
} catch (Exception e) {
AppLog.errLog(EncryptMoreThn256Byte.class.getSimpleName(), e.getMessage());
}
}
public String encryptAccessTokenUsingAES(String plainText) {
AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "encryptAccessTokenUsingAES: plainText " + plainText);
if (plainText != null || !plainText.isEmpty()) {
String encrytedSecretKey = SharedPreferences.getInstance(MyApplication.getInstance().getApplicationContext()).getEncrytedSecretKey();
if (encrytedSecretKey != null) {
AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "encryptAccessTokenUsingAES: encrytedSecretKey >+++ " + encrytedSecretKey);
byte[] decrptedSecrateKey = decryptSecretKeyUsingRSA(encrytedSecretKey);
if (decrptedSecrateKey != null) {
secKey = new SecretKeySpec(decrptedSecrateKey, 0, decrptedSecrateKey.length, "AES");
}
} else {
encryptSecretKeyUsingRSA(secKey);
}
String str = new String(encryptText(secKey, plainText));
AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "final encryptAccessTokenUsingAES is: " + str);
return str;
}
AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "encryptAccessTokenUsingAES: value " + null);
return null;
}
public String decryptAccessTokenUsingAES(String encrptedAssessToken) {
String encrpted_secretkey = SharedPreferences.getInstance(MyApplication.getInstance().getApplicationContext()).getEncrytedSecretKey();
AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "decryptAccessTokenUsingAES encrpted_secretkey:: " + encrpted_secretkey);
AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "decryptAccessTokenUsingAES accesstoken:: " + encrptedAssessToken);
if (encrpted_secretkey != null) {
byte[] decrptedSecrateKey = decryptSecretKeyUsingRSA(encrpted_secretkey);
if (decrptedSecrateKey != null) {
SecretKey secKey = new SecretKeySpec(decrptedSecrateKey, 0, decrptedSecrateKey.length, "AES");
AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "decryptAccessTokenUsingAES secKey.getEncoded():: " + secKey.getEncoded());
String str = decryptText(secKey, encrptedAssessToken);
AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "final decryptAccessTokenUsingAES is: " + str);
return str;
}
}
return null;
}
private byte[] encryptSecretKeyUsingRSA(SecretKey secKey) {
AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "encryptSecretKeyUsingRSA SecretKey:: " + secKey);
KeyPairGenerator kpg = null;
byte[] encryptedSecrteKey = null;
try {
kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair keyPair = kpg.generateKeyPair();
PublicKey puKey = keyPair.getPublic();
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.PUBLIC_KEY, puKey);
//AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "encryptSecretKeyUsingRSA secKey.getEncoded():: " + secKey.getEncoded());
//AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "encryptSecretKeyUsingRSA secKey.getEncoded().length:: " + secKey.getEncoded().length);
//byte[] encryptedSecrteKey = cipher.doFinal(secKey.getEncoded());
//byte[] encryptedSecrteKey=cipher.doFinal(initialText.getBytes("UTF-8"));
encryptedSecrteKey = cipher.doFinal(secKey.getEncoded());
// String str = Base64.encodeToString(encryptedSecrteKey, Base64.DEFAULT);
AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "encryptSecretKeyUsingRSA encryptedSecrteKey[]:: " + encryptedSecrteKey.length);
//AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "encryptSecretKeyUsingRSA encrytionSecretKey:: " + str);
//AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "encryptSecretKeyUsingRSA encrytionSecretKey length:: " + str.length());
SharedPreferences.getInstance(MyApplication.getInstance().getApplicationContext()).setEncrytedSecretKey(encryptedSecrteKey.toString());
} catch (Exception e) {
AppLog.errLog(EncryptMoreThn256Byte.class.getSimpleName(), "encryptSecretKeyUsingRSA::+" + e.getMessage());
} finally {
return encryptedSecrteKey;
}
}
private byte[] decryptSecretKeyUsingRSA(String encryptSecretKey) {
KeyPairGenerator kpg = null;
byte[] bytes = null;
try {
kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair keyPair = kpg.generateKeyPair();
PrivateKey prKey = keyPair.getPrivate();
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.PRIVATE_KEY, prKey);
AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "decryptSecretKeyUsingRSA encryptSecretKey.getBytes():: " + encryptSecretKey.getBytes());
AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "decryptSecretKeyUsingRSA encryptSecretKey.getBytes().length:: " + encryptSecretKey.getBytes().length);
// bytes = cipher.doFinal(Base64.decode(encryptSecretKey, Base64.DEFAULT));
bytes = cipher.doFinal(encryptSecretKey.getBytes());
AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "decryptSecretKeyUsingRSA cipher.doFinal(encryptSecretKey.getBytes():: " + bytes.toString());
} catch (Exception e) {
AppLog.errLog(EncryptMoreThn256Byte.class.getSimpleName(), "decryptSecretKeyUsingRSA++++ " + e.getMessage());
}
return bytes;
}
private byte[] encryptText(SecretKey pSecKey, String plainText) {
byte[] encryptAccessToken = null;
try {
AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "encryptText::secKey: " + pSecKey.getEncoded());
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.ENCRYPT_MODE, pSecKey);
encryptAccessToken = aesCipher.doFinal(plainText.getBytes());
AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "encryptText:::: " + encryptAccessToken.toString());
} catch (Exception e) {
AppLog.errLog(EncryptMoreThn256Byte.class.getSimpleName(), "encryptTextUsingAES " + e.getMessage());
}
return encryptAccessToken;
}
/**
* Convert bytes to AES SecertKey so we can decrypt access token
*
* #return
*/
private String decryptText(SecretKey originalKey, String decryptedAccessToken) {
byte[] bytePlainText = null;
try {
AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "encryptText::originalKey: " + originalKey.getEncoded());
//SecretKey originalKey = new SecretKeySpec(decryptedKey, 0, decryptedKey.length, "AES");
Cipher aesCipher = null;
aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.DECRYPT_MODE, originalKey);
bytePlainText = aesCipher.doFinal(decryptedAccessToken.getBytes());
AppLog.log(EncryptMoreThn256Byte.class.getSimpleName(), "decryptText bytePlainText:: " + bytePlainText.toString());
} catch (Exception e) {
AppLog.errLog(EncryptMoreThn256Byte.class.getSimpleName(), "encryptTextUsingAES " + e.getMessage());
}
return new String(bytePlainText);
}
private byte[] encrytAT(String plaintext, String password) throws InvalidAlgorithmParameterException, InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, UnsupportedEncodingException, BadPaddingException, IllegalBlockSizeException {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[(256 / 8)];
random.nextBytes(salt);
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 1000, 256);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
SecretKey key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[cipher.getBlockSize()];
random.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivParams);
return cipher.doFinal(plaintext.getBytes("UTF-8"));
}
}
Also find the log for the same code:
03-28 11:47:59.536 12795-12795/? D/debug_log: EncryptMoreThn256Byte :encryptAccessTokenUsingAES: encrytedSecretKey >+++ [B#b56ec6b 03-2811:48:00.803 12795-12795/? D/debug_log: EncryptMoreThn256Byte :decryptSecretKeyUsingRSA encryptSecretKey.getBytes():: [B#43f8244
03-28 11:48:00.804 12795-12795/? D/debug_log: EncryptMoreThn256Byte :decryptSecretKeyUsingRSA encryptSecretKey.getBytes().length:: 10 03-2811:48:00.804 12795-12795/? E/error_log: Exception from:EncryptMoreThn256Byte : decryptSecretKeyUsingRSA++++error:0407806d:RSA routines:decrypt:DATA_LEN_NOT_EQUAL_TO_MOD_LEN
03-28 11:48:00.805 12795-12795/? D/debug_log: EncryptMoreThn256Byte :encryptText::secKey: [B#d535f2d
03-28 11:48:00.808 12795-12795/? D/debug_log: EncryptMoreThn256Byte : encryptText:::: [B#d4d6c62 03-2811:48:00.808 12795-12795/? D/debug_log: EncryptMoreThn256Byte : finalencryptAccessTokenUsingAES is: (���(z��RW�D
03-28 11:48:00.808 12795-12795/? D/debug_log: MyApplication : EncryptMoreThn256Byte +++str (���(z��RW�D
03-28 11:48:00.808 12795-12795/? D/debug_log:EncryptMoreThn256Byte : decryptAccessTokenUsingAESencrpted_secretkey:: [B#b56ec6b
03-28 11:48:00.808 12795-12795/? D/debug_log: EncryptMoreThn256Byte : decryptAccessTokenUsingAESaccesstoken:: (���(z��RW�D
03-28 11:48:03.539 12795-12795/? D/debug_log: EncryptMoreThn256Byte : decryptSecretKeyUsingRSAencryptSecretKey.getBytes():: [B#6c931f3
03-28 11:48:03.540 12795-12795/? D/debug_log: EncryptMoreThn256Byte :decryptSecretKeyUsingRSA encryptSecretKey.getBytes().length:: 10 03-2811:48:03.540 12795-12795/? E/error_log: Exception from:EncryptMoreThn256Byte : decryptSecretKeyUsingRSA++++error:0407806d:RSA routines:decrypt:DATA_LEN_NOT_EQUAL_TO_MOD_LEN
03-28 11:48:03.540 12795-12795/? D/debug_log: MyApplication :EncryptMoreThn256Byte +++ dep null
Plase, review this.
public class AESEncryptor {
private static final String CIPHER_TYPE = "RSA/ECB/PKCS1Padding";//AES/GCM/NoPadding
public final String ANDROID_KEY_STORE = "AndroidKeyStore";
private static AESEncryptor instance;
private String alias_ = "classmate123";
private KeyStore keyStore;
private AESEncryptor() {
}
public static AESEncryptor getInstance() {
if (instance == null) {
instance = new AESEncryptor();
}
return instance;
}
public KeyStore initAndroidKeyStore(Context pContext) {
try {
if (keyStore == null) {
keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
keyStore.load(null);
createNewKeys(pContext);
}
} catch (Exception e) {
AppLog.errLog("AESEncrytion", "initAndroidKeyStore : " + e.getMessage());
}
return keyStore;
}
public String encrypt(String key, String cleartext) throws Exception {
//AppLog.log("AESEncrytion", "encrypt+++ " + cleartext);
if (cleartext == null || cleartext.trim().length()==0||cleartext.isEmpty()) {
return "";
}
return encryptString(alias_, cleartext);
}
public String decrypt(String key, String encryptedValue) throws Exception {
//AppLog.log("AESEncrytion", "encryptedValue+++ " + encryptedValue);
if (encryptedValue == null || encryptedValue.trim().length()==0||encryptedValue.isEmpty()) {
return "";
}
return decryptString(alias_, encryptedValue);
}
public void createNewKeys(Context pContext) {
String alias = alias_;
try {
// Create new key if needed
if (!keyStore.containsAlias(alias)) {
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 1);
AlgorithmParameterSpec spec = null;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
spec = new KeyPairGeneratorSpec.Builder(pContext)
// You'll use the alias later to retrieve the key. It's a key for the key!
.setAlias(alias)
// The subject used for the self-signed certificate of the generated pair
.setSubject(new X500Principal("CN=" + alias))
.setSerialNumber(BigInteger.ONE)
.setStartDate(start.getTime())
.setEndDate(end.getTime())
.build();
}else {
// On Android M or above, use the KeyGenparameterSpec.Builder and specify permitted
// properties and restrictions of the key.
spec = new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_SIGN)
.setCertificateSubject(new X500Principal("CN=" + alias))
.setDigests(KeyProperties.DIGEST_SHA256)
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.setCertificateSerialNumber(BigInteger.ONE)
.setCertificateNotBefore(start.getTime())
.setCertificateNotAfter(end.getTime())
.build();
}
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", ANDROID_KEY_STORE);
generator.initialize(spec);
KeyPair keyPair = generator.generateKeyPair();
// AppLog.log("AESEncrytion", "createNewKeys keyPair.getPrivate(): " + keyPair.getPrivate());
// AppLog.log("AESEncrytion", "createNewKeys keyPair.getPublic(): " + keyPair.getPublic());
} else {
AppLog.log("AESEncrytion", "KeyStore already containsAlias alias: " + keyStore.containsAlias(alias));
}
} catch (Exception e) {
AppLog.errLog("AESEncrytion", "createNewKeys: " + e.getMessage());
}
//refreshKeys();
}
public String encryptString(String alias, String initialText) {
try {
//AppLog.log("AESEncrytion", "initialText+++ " + initialText);
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, null);
PublicKey publicKey = privateKeyEntry.getCertificate().getPublicKey();
//AppLog.log("AESEncrytion", "encryptString keyPair.getPublic(): " + publicKey.getEncoded());
// RSAPublicKey publicKey = (RSAPublicKey) privateKeyEntry.getCertificate().getPublicKey();
Cipher input = Cipher.getInstance(CIPHER_TYPE);
input.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encrypted=input.doFinal(initialText.getBytes("UTF-8"));
// ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, input);
// cipherOutputStream.write(initialText.getBytes("UTF-8"));
// cipherOutputStream.close();
// byte[] vals = outputStream.toByteArray();
// AppLog.log("AESEncrytion", "decryptString+++ " + Base64.encodeToString(vals, Base64.DEFAULT));
return Base64.encodeToString(encrypted, Base64.DEFAULT);
} catch (Exception e) {
AppLog.errLog("AESEncrytion", "encryptString " + e.getMessage() + " occured");
}
return "";
}
public String decryptString(String alias, String cipherText) {
try {
//AppLog.log("AESEncrytion", "cipherText : " + cipherText);
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, null);
// RSAPrivateKey privateKey = (RSAPrivateKey) privateKeyEntry.getPrivateKey();
PrivateKey privateKey = privateKeyEntry.getPrivateKey();
//AppLog.log("AESEncrytion", "decryptString keyPair.getPrivate(): " + privateKey.getEncoded());
Cipher output = Cipher.getInstance(CIPHER_TYPE);
output.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decrypted=output.doFinal(Base64.decode(cipherText, Base64.DEFAULT));
// CipherInputStream cipherInputStream = new CipherInputStream(
// new ByteArrayInputStream(Base64.decode(cipherText, Base64.DEFAULT)), 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();
// }
String finalText = new String(decrypted, 0, decrypted.length, "UTF-8");
// AppLog.log("AESEncrytion", "decryptString : " + finalText);
return finalText;
} catch (Exception e) {
AppLog.errLog("AESEncrytion", "decryptString:: " + e.getMessage() + " occured");
}
return "";
}
public boolean isKeysAvail() throws KeyStoreException {
return keyStore.containsAlias(alias_);
}
public void deleteKey() throws KeyStoreException {
keyStore.deleteEntry(alias_);
}
}
It has solved my problem. I have changed my approach to encrypt and decrypt.
Im trying to implement AES in an Android application. The server is a PHP server.
This is the AES code :
public class AES{
private String SecretKey = "89432hjfsd891787";
private String iv = "fedcba9876543210";
private IvParameterSpec ivspec;
private SecretKeySpec keyspec;
private Cipher cipher;
public AES()
{
ivspec = new IvParameterSpec(iv.getBytes());
keyspec = new SecretKeySpec(SecretKey.getBytes(), "AES");
try {
cipher = Cipher.getInstance("AES/CBC/NoPadding");
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public byte[] encrypt(String text) throws Exception
{
if(text == null || text.length() == 0)
throw new Exception("Empty string");
byte[] encrypted = null;
try {
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
encrypted = cipher.doFinal(padString(text).getBytes());
} catch (Exception e)
{
throw new Exception("[encrypt] " + e.getMessage());
}
return encrypted;
}
public byte[] decrypt(String code) throws Exception
{
if(code == null || code.length() == 0)
throw new Exception("Empty string");
byte[] decrypted = null;
try {
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
decrypted = cipher.doFinal(hexToBytes(code));
} catch (Exception e)
{
throw new Exception("[decrypt] " + e.getMessage());
}
return decrypted;
}
public static String bytesToHex(byte[] data)
{
if (data==null)
{
return null;
}
int len = data.length;
String str = "";
for (int i=0; i<len; i++) {
if ((data[i]&0xFF)<16)
str = str + "0" + java.lang.Integer.toHexString(data[i]&0xFF);
else
str = str + java.lang.Integer.toHexString(data[i]&0xFF);
}
return str;
}
public static byte[] hexToBytes(String str) {
if (str==null) {
return null;
} else if (str.length() < 2) {
return null;
} else {
int len = str.length() / 2;
byte[] buffer = new byte[len];
for (int i=0; i<len; i++) {
buffer[i] = (byte) Integer.parseInt(str.substring(i*2,i*2+2),16);
}
return buffer;
}
}
private static String padString(String source)
{
char paddingChar = ' ';
int size = 16;
int x = source.length() % size;
int padLength = size - x;
for (int i = 0; i < padLength; i++) {
source += paddingChar;
}
return source;
}
}
I get an error while decrypting the incoming string : Cipher functions:EVP_DecryptFinal_ex:DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH
How do i go about this?
Manually padding the String representation of the plaintext with spaces is not a good padding mechanism. Instead, you should use AES/CBC/PKCS5Padding which will handle the padding as prescribed in PKCS #5/#7.
You should also:
generate a unique IV per encryption operation rather than re-using the same value for all messages
Use an authenticated mode of operation (GCM, HMAC/SHA-256 MAC over cipher text, etc.)
Store the key in hexadecimal representation of the raw bytes rather than an ASCII/UTF-8 String encoding (not specified in your code)
Encode the cipher text in Base64 or Hex before serializing/transmitting. Encoding errors can contribute to padding exceptions during decryption.
I am using encryption - decryption in Android using public and private key respectively. I have both the keys saved in the RAW folder as a file. The encryption works perfectly, but for the decryption it always gives the following error:
java.security.spec.InvalidKeySpecException: java.lang.RuntimeException: error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag
I am enclosing the code snippet I have used:
public class AppUtils {
public static String encryptString(String value, Context context){
byte[] encodedBytes = null;
try {
//Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL");
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(context) );
encodedBytes = cipher.doFinal(value.getBytes());
} catch (Exception e) {
e.printStackTrace();
}
return Base64.encodeToString(encodedBytes, Base64.DEFAULT);
}
public static String decryptString(String value, Context context){
byte[] decodedBytes = null;
try {
//Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL");
Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
c.init(Cipher.DECRYPT_MODE, getPrivateKey(context) );
decodedBytes = c.doFinal(Base64.decode(value, Base64.DEFAULT));
} catch (Exception e) {
e.printStackTrace();
}
return new String(decodedBytes);
}
public static PrivateKey getPrivateKey(Context context){
// reads the key_public key stored in a file
InputStream is = context.getResources().openRawResource(R.raw.key_private);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
List<String> lines = new ArrayList<String>();
String line = null;
try {
while ((line = br.readLine()) != null)
lines.add(line);
// removes the first and last lines of the file (comments)
if (lines.size() > 1 && lines.get(0).startsWith("-----") && lines.get(lines.size()-1).startsWith("-----")) {
lines.remove(0);
lines.remove(lines.size()-1);
}
// concats the remaining lines to a single String
StringBuilder sb = new StringBuilder();
for (String aLine: lines)
sb.append(aLine);
String keyString = sb.toString();
byte [] encoded = Base64.decode(keyString, Base64.DEFAULT);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
PrivateKey myPrivKey = keyFactory.generatePrivate(keySpec);
return myPrivKey;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
public static PublicKey getPublicKey(Context context){
// reads the key_public key stored in a file
InputStream is = context.getResources().openRawResource(R.raw.key_public);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
List<String> lines = new ArrayList<String>();
String line = null;
try {
while ((line = br.readLine()) != null)
lines.add(line);
// removes the first and last lines of the file (comments)
if (lines.size() > 1 && lines.get(0).startsWith("-----") && lines.get(lines.size()-1).startsWith("-----")) {
lines.remove(0);
lines.remove(lines.size()-1);
}
// concats the remaining lines to a single String
StringBuilder sb = new StringBuilder();
for (String aLine: lines)
sb.append(aLine);
String keyString = sb.toString();
// converts the String to a PublicKey instance
byte[] keyBytes = Base64.decode(keyString, Base64.DEFAULT);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey key = keyFactory.generatePublic(spec);
return key;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
Finally, I call the required function from MainActivity.java like this:
String encryptedString = AppUtils.encryptString("SHANKAR", MainActivity.this);
Log.d("DX1", " Encrypted String " + encryptedString );
String decryptedString = AppUtils.decryptString(encryptedString, MainActivity.this);
Log.d("DX1", " decrypted String " + decryptedString );
I see that, I get the encrypted string properly, but at the time of decryption the statement :
PrivateKey myPrivKey = keyFactory.generatePrivate(keySpec);
is throwing me the above error.But I can decrypt the encrypted string using the private key in any online tool.
Can someone kindly help me out.
My public key:
-----BEGIN PUBLIC KEY-----
MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgEac6cgM4Ch5vY2Rqvzw2ARaNEHv
PCbXWW1nPy3ft8CNFyLoIltwrnouY0azYECclONARh48qQUQ+UG62wNUtciLq9yX
3m0ePE7u/RYmNUnGWok6LMNZK1gGBu6TBnzNjDWi3CuR00xFzZ2TBtUMDowOa/+b
tfGTywGDLPJjgbtPAgMBAAE=
-----END PUBLIC KEY-----
My private key:
-----BEGIN RSA PRIVATE KEY-----
MIICWgIBAAKBgEac6cgM4Ch5vY2Rqvzw2ARaNEHvPCbXWW1nPy3ft8CNFyLoIltw
rnouY0azYECclONARh48qQUQ+UG62wNUtciLq9yX3m0ePE7u/RYmNUnGWok6LMNZ
K1gGBu6TBnzNjDWi3CuR00xFzZ2TBtUMDowOa/+btfGTywGDLPJjgbtPAgMBAAEC
gYAAxTd7ukA70NAzmjI+XjZNHdrSGVUTq2fLXMQAsR8lF6T3+YZebwVISfdFTzGu
osaxEANz0v+ZEY1WnT5EdAkjqwtYauj0tDnYmuciju2uVnEYLPOaOvpkmM7e9y+a
NWTvG/C0qAXtTT/EJgAwfCyrhuigyxzwUIyqhW2xgQ8MKQJBAIgM5xPKd30HU98t
jOHzouwSjsv5NIFOM9RnLPGA6HazQGu/0h2S27UnzaU8KKjln1X2q22GMa1cSvIL
q06e+MsCQQCE3oZudjOBgJaKtk7iETiIIoVVk3K1RMSGT/56SrglB1K1kNuFOpg+
obs+8j366gQk47ZgaIZwSRfro0VhTysNAkBiLEVWv6QHgZEhG6Jsrb1j8mQ+hd5A
bGj0HVuODYIxnVmgJvP8yStnhohbcpS4g7G9e1jqmIoiWdXu4ULFYeuPAkAIcKYz
cBi3ejaV2xzJqXRg2WiE1hfsQdEGAyDUHdjyqTNsyyXWobE4EUf2qKadQK5AtaJJ
H3qiuVHmqvlmRAQlAkB4Cl2nYBpK1IdusfwtjfaKppLa/r5k1D35cybxLn1uS3Xz
wwqRUuXrDkb4+TCD3B7Lkuym/kfFE2iIpANAVkeN
-----END RSA PRIVATE KEY-----
I had the same problem and tried a couple of days to figure out how it works and finally, I got it!
Your private key not in PKCS#8 format.
A PKCS8 key starts with
-----BEGIN PRIVATE KEY-----
instead of
-----BEGIN RSA PRIVATE KEY-----.
Java works with PKCS#8 formats.
So you have two ways to fix it:
First:
Regenerate/convert your key on the server side in/to PCKS#8 format and use it in android application
Or Second:
Convert your private key in the application.
For this way you need include third party library
to the project.
Add this line into your app Gradle
compile 'com.madgag.spongycastle:core:1.56.0.0'
And replace your getPrivateKey method with this:
public static PrivateKey getPrivateKey(Context context) throws
GeneralSecurityException, IOException {
InputStream is = context.getResources().openRawResource(R.raw.rsa_2048_priv);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
List<String> lines = new ArrayList<String>();
String line = null;
while ((line = br.readLine()) != null)
lines.add(line);
if (lines.size() > 1 && lines.get(0).startsWith("-----") && lines.get(lines.size() - 1).startsWith("-----")) {
lines.remove(0);
lines.remove(lines.size() - 1);
}
StringBuilder sb = new StringBuilder();
for (String aLine : lines)
sb.append(aLine);
String keyString = sb.toString();
byte[] encodedPrivateKey = Base64.decode(keyString, Base64.DEFAULT);
try {
ASN1Sequence primitive = (ASN1Sequence) ASN1Sequence
.fromByteArray(encodedPrivateKey);
Enumeration<?> e = primitive.getObjects();
BigInteger v = ((ASN1Integer) e.nextElement()).getValue();
int version = v.intValue();
if (version != 0 && version != 1) {
throw new IllegalArgumentException("wrong version for RSA private key");
}
/**
* In fact only modulus and private exponent are in use.
*/
BigInteger modulus = ((ASN1Integer) e.nextElement()).getValue();
BigInteger publicExponent = ((ASN1Integer) e.nextElement()).getValue();
BigInteger privateExponent = ((ASN1Integer) e.nextElement()).getValue();
RSAPrivateKeySpec spec = new RSAPrivateKeySpec(modulus, privateExponent);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(spec);
} catch (IOException e2) {
throw new IllegalStateException();
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
} catch (InvalidKeySpecException e) {
throw new IllegalStateException(e);
}
}
So it will convert private key to the PKCS#8,
and you can decode your text without error of wrong tag
Hope it help :)
Here the links where I found solutions:
Getting RSA private key from PEM BASE64 Encoded private key file
JAVA RSA Decryption not working, throws InvalidKeySpecException
Convert PEM traditional private key to PKCS8 private key
How to include the Spongy Castle JAR in Android?
I would like to be able securely store some sensitive strings in the Android KeyStore. I get the strings from the server but I have a use case which requires me to persist them. KeyStore will only allow access from the same UID as that assigned to my app, and it will encrypt the data with the device master password, so it's my understanding that I don't have to do any additional encryption to protect my data. My trouble is, I'm missing something about how to write the data. The code I have below works perfectly, as long as the call to KeyStore.store(null) is omitted. That code fails, and as long as I can't store the data after putting it to the KeyStore, then I can't persist it.
I think I'm missing something about the KeyStore API, but I don't know what. Any help appreciated!
String metaKey = "ourSecretKey";
String encodedKey = "this is supposed to be a secret";
byte[] encodedKeyBytes = new byte[(int)encodedKey.length()];
encodedKeyBytes = encodedKey.getBytes("UTF-8");
KeyStoreParameter ksp = null;
//String algorithm = "DES";
String algorithm = "DESede";
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm);
SecretKeySpec secretKeySpec = new SecretKeySpec(encodedKeyBytes, algorithm);
SecretKey secretKey = secretKeyFactory.generateSecret(secretKeySpec);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
KeyStore.SecretKeyEntry secretKeyEntry = new KeyStore.SecretKeyEntry(secretKey);
keyStore.setEntry(metaKey, secretKeyEntry, ksp);
keyStore.store(null);
String recoveredSecret = "";
if (keyStore.containsAlias(metaKey)) {
KeyStore.SecretKeyEntry recoveredEntry = (KeyStore.SecretKeyEntry)keyStore.getEntry(metaKey, ksp);
byte[] bytes = recoveredEntry.getSecretKey().getEncoded();
for (byte b : bytes) {
recoveredSecret += (char)b;
}
}
Log.v(TAG, "recovered " + recoveredSecret);
I started with the premise that I could use AndroidKeyStore to secure arbitrary blobs of data, and call them "keys". However, the deeper I delved into this, the clearer it became that the KeyStore API is deeply entangled with Security-related objects: Certificates, KeySpecs, Providers, etc. It's not designed to store arbitrary data, and I don't see a straightforward path to bending it to that purpose.
However, the AndroidKeyStore can be used to help me to secure my sensitive data. I can use it to manage the cryptographic keys which I will use to encrypt data local to the app. By using a combination of AndroidKeyStore, CipherOutputStream, and CipherInputStream, we can:
Generate, securely store, and retrieve encryption keys on the device
Encrypt arbitrary data and save it on the device (in the app's directory, where it will be further protected by the file system permissions)
Access and decrypt the data for subsequent use.
Here is some example code which demonstrates how this is achieved.
try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
String alias = "key3";
int nBefore = keyStore.size();
// Create the keys if necessary
if (!keyStore.containsAlias(alias)) {
Calendar notBefore = Calendar.getInstance();
Calendar notAfter = Calendar.getInstance();
notAfter.add(Calendar.YEAR, 1);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(this)
.setAlias(alias)
.setKeyType("RSA")
.setKeySize(2048)
.setSubject(new X500Principal("CN=test"))
.setSerialNumber(BigInteger.ONE)
.setStartDate(notBefore.getTime())
.setEndDate(notAfter.getTime())
.build();
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
generator.initialize(spec);
KeyPair keyPair = generator.generateKeyPair();
}
int nAfter = keyStore.size();
Log.v(TAG, "Before = " + nBefore + " After = " + nAfter);
// Retrieve the keys
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(alias, null);
RSAPrivateKey privateKey = (RSAPrivateKey) privateKeyEntry.getPrivateKey();
RSAPublicKey publicKey = (RSAPublicKey) privateKeyEntry.getCertificate().getPublicKey();
Log.v(TAG, "private key = " + privateKey.toString());
Log.v(TAG, "public key = " + publicKey.toString());
// Encrypt the text
String plainText = "This text is supposed to be a secret!";
String dataDirectory = getApplicationInfo().dataDir;
String filesDirectory = getFilesDir().getAbsolutePath();
String encryptedDataFilePath = filesDirectory + File.separator + "keep_yer_secrets_here";
Log.v(TAG, "plainText = " + plainText);
Log.v(TAG, "dataDirectory = " + dataDirectory);
Log.v(TAG, "filesDirectory = " + filesDirectory);
Log.v(TAG, "encryptedDataFilePath = " + encryptedDataFilePath);
Cipher inCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL");
inCipher.init(Cipher.ENCRYPT_MODE, publicKey);
Cipher outCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL");
outCipher.init(Cipher.DECRYPT_MODE, privateKey);
CipherOutputStream cipherOutputStream =
new CipherOutputStream(
new FileOutputStream(encryptedDataFilePath), inCipher);
cipherOutputStream.write(plainText.getBytes("UTF-8"));
cipherOutputStream.close();
CipherInputStream cipherInputStream =
new CipherInputStream(new FileInputStream(encryptedDataFilePath),
outCipher);
byte [] roundTrippedBytes = new byte[1000]; // TODO: dynamically resize as we get more data
int index = 0;
int nextByte;
while ((nextByte = cipherInputStream.read()) != -1) {
roundTrippedBytes[index] = (byte)nextByte;
index++;
}
String roundTrippedString = new String(roundTrippedBytes, 0, index, "UTF-8");
Log.v(TAG, "round tripped string = " + roundTrippedString);
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, Log.getStackTraceString(e));
} catch (NoSuchProviderException e) {
Log.e(TAG, Log.getStackTraceString(e));
} catch (InvalidAlgorithmParameterException e) {
Log.e(TAG, Log.getStackTraceString(e));
} catch (KeyStoreException e) {
Log.e(TAG, Log.getStackTraceString(e));
} catch (CertificateException e) {
Log.e(TAG, Log.getStackTraceString(e));
} catch (IOException e) {
Log.e(TAG, Log.getStackTraceString(e));
} catch (UnrecoverableEntryException e) {
Log.e(TAG, Log.getStackTraceString(e));
} catch (NoSuchPaddingException e) {
Log.e(TAG, Log.getStackTraceString(e));
} catch (InvalidKeyException e) {
Log.e(TAG, Log.getStackTraceString(e));
} catch (BadPaddingException e) {
Log.e(TAG, Log.getStackTraceString(e));
} catch (IllegalBlockSizeException e) {
Log.e(TAG, Log.getStackTraceString(e));
} catch (UnsupportedOperationException e) {
Log.e(TAG, Log.getStackTraceString(e));
}
You may have noticed that there are problems handling different API levels with the Android Keystore.
Scytale is an open source library that provides a convenient wrapper around the Android Keystore so that you don't have write boiler plate and can dive straight into enryption/decryption.
Sample code:
// Create and save key
Store store = new Store(getApplicationContext());
if (!store.hasKey("test")) {
SecretKey key = store.generateSymmetricKey("test", null);
}
...
// Get key
SecretKey key = store.getSymmetricKey("test", null);
// Encrypt/Decrypt data
Crypto crypto = new Crypto(Options.TRANSFORMATION_SYMMETRIC);
String text = "Sample text";
String encryptedData = crypto.encrypt(text, key);
Log.i("Scytale", "Encrypted data: " + encryptedData);
String decryptedData = crypto.decrypt(encryptedData, key);
Log.i("Scytale", "Decrypted data: " + decryptedData);
I have reworked the accepted answer by Patrick Brennan. on Android 9, it was yielding a NoSuchAlgorithmException. The deprecated KeyPairGeneratorSpec has been replaced with KeyPairGenerator. There was also some work required to address an exception regarding the padding.
The code is annotated with the changes made: "***"
#RequiresApi(api = Build.VERSION_CODES.M)
public static void storeExistingKey(Context context) {
final String TAG = "KEY-UTIL";
try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
String alias = "key11";
int nBefore = keyStore.size();
// Create the keys if necessary
if (!keyStore.containsAlias(alias)) {
Calendar notBefore = Calendar.getInstance();
Calendar notAfter = Calendar.getInstance();
notAfter.add(Calendar.YEAR, 1);
// *** Replaced deprecated KeyPairGeneratorSpec with KeyPairGenerator
KeyPairGenerator spec = KeyPairGenerator.getInstance(
// *** Specified algorithm here
// *** Specified: Purpose of key here
KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
spec.initialize(new KeyGenParameterSpec.Builder(
alias, KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) // RSA/ECB/PKCS1Padding
.setKeySize(2048)
// *** Replaced: setStartDate
.setKeyValidityStart(notBefore.getTime())
// *** Replaced: setEndDate
.setKeyValidityEnd(notAfter.getTime())
// *** Replaced: setSubject
.setCertificateSubject(new X500Principal("CN=test"))
// *** Replaced: setSerialNumber
.setCertificateSerialNumber(BigInteger.ONE)
.build());
KeyPair keyPair = spec.generateKeyPair();
Log.i(TAG, keyPair.toString());
}
int nAfter = keyStore.size();
Log.v(TAG, "Before = " + nBefore + " After = " + nAfter);
// Retrieve the keys
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, null);
PrivateKey privateKey = privateKeyEntry.getPrivateKey();
PublicKey publicKey = privateKeyEntry.getCertificate().getPublicKey();
Log.v(TAG, "private key = " + privateKey.toString());
Log.v(TAG, "public key = " + publicKey.toString());
// Encrypt the text
String plainText = "This text is supposed to be a secret!";
String dataDirectory = context.getApplicationInfo().dataDir;
String filesDirectory = context.getFilesDir().getAbsolutePath();
String encryptedDataFilePath = filesDirectory + File.separator + "keep_yer_secrets_here";
Log.v(TAG, "plainText = " + plainText);
Log.v(TAG, "dataDirectory = " + dataDirectory);
Log.v(TAG, "filesDirectory = " + filesDirectory);
Log.v(TAG, "encryptedDataFilePath = " + encryptedDataFilePath);
// *** Changed the padding type here and changed to AndroidKeyStoreBCWorkaround
Cipher inCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidKeyStoreBCWorkaround");
inCipher.init(Cipher.ENCRYPT_MODE, publicKey);
// *** Changed the padding type here and changed to AndroidKeyStoreBCWorkaround
Cipher outCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidKeyStoreBCWorkaround");
outCipher.init(Cipher.DECRYPT_MODE, privateKey);
CipherOutputStream cipherOutputStream =
new CipherOutputStream(
new FileOutputStream(encryptedDataFilePath), inCipher);
// *** Replaced string literal with StandardCharsets.UTF_8
cipherOutputStream.write(plainText.getBytes(StandardCharsets.UTF_8));
cipherOutputStream.close();
CipherInputStream cipherInputStream =
new CipherInputStream(new FileInputStream(encryptedDataFilePath),
outCipher);
byte[] roundTrippedBytes = new byte[1000]; // TODO: dynamically resize as we get more data
int index = 0;
int nextByte;
while ((nextByte = cipherInputStream.read()) != -1) {
roundTrippedBytes[index] = (byte) nextByte;
index++;
}
// *** Replaced string literal with StandardCharsets.UTF_8
String roundTrippedString = new String(roundTrippedBytes, 0, index, StandardCharsets.UTF_8);
Log.v(TAG, "round tripped string = " + roundTrippedString);
} catch (NoSuchAlgorithmException | UnsupportedOperationException | InvalidKeyException | NoSuchPaddingException | UnrecoverableEntryException | NoSuchProviderException | KeyStoreException | CertificateException | IOException e | InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
Note: “AndroidKeyStoreBCWorkaround” allows the code to work across different APIs.
I would be grateful if anyone can comment on any shortcomings in this updated solution. Else if anyone with more Crypto knowledge feels confident to update Patrick's answer then I will remove this one.
I get a 64 uncompressed public key and need to run ECDH to generate a shared secret.
In order to call ECDH I need to convert the byte array to PublicKey and I am using the following code I have found in this forum:
public static void setOtherPublicKey(byte[] publicKeyBytes) throws NoSuchAlgorithmException, InvalidKeySpecException
{
try {
//EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
KeyFactory generator = KeyFactory.getInstance("EC");
//PrivateKey privateKey = generator.generatePrivate(privateKeySpec);
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
blePubKey = generator.generatePublic(publicKeySpec);
} catch (Exception e) {
throw new IllegalArgumentException("Failed to create KeyPair from provided encoded keys", e);
}
}
This code throws an InvalidKeySpecException.
As example, the public key of the other party is:
9b5e9a5a971877530c9cadbbea93c2ee2483d65052678f745bad79f110173520
54019832e11376537a76c4defd0b3dfdc667a974239147f323cdcfd2baa39892
Adding the code after getting the answers below:
public static void setOtherPublicKey() throws NoSuchAlgorithmException, InvalidKeySpecException
{
// first generate key pair of your own
ECPublicKey pubKey = (ECPublicKey) SecPage.g_kpA.getPublic();
ECParameterSpec params = pubKey.getParams();
int keySizeBytes = params.getOrder().bitLength() / Byte.SIZE;
// get the other party 64 bytes
//byte [] otherPub = crypto.getBlePubKeyBytes();
byte[] otherPub = hexStringToByteArray("ac2bdd28fce5c7b181b34f098b0934742281246ed907a5f646940c1edcb724e7c7358356aebea810322a8e324cc77f376df4cabd754110ad41ec178c0a6b8e5f");
ByteArrayBuffer xBytes = new ByteArrayBuffer(33);
ByteArrayBuffer yBytes = new ByteArrayBuffer(33);
byte[] zero = {(byte)0x00};
xBytes.append(zero, 0, 1);
xBytes.append(otherPub, 0, 32);
yBytes.append(zero, 0, 1);
yBytes.append(otherPub, 32, 32);
// generate the public key point
BigInteger x = new BigInteger(xBytes.buffer());
BigInteger y = new BigInteger(yBytes.buffer());
ECPoint w = new ECPoint(x, y);
// generate the key of the other side
ECPublicKeySpec otherKeySpec = new ECPublicKeySpec(w , params);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
blePubKey = (ECPublicKey) keyFactory.generatePublic(otherKeySpec);
}
Well, whaty'know, you can actually do this... explanation in the comments.
public class ECDHPub {
private static ECPublicKey decodeECPublicKey(ECParameterSpec params,
final byte[] pubkey) throws NoSuchAlgorithmException,
InvalidKeySpecException {
int keySizeBytes = params.getOrder().bitLength() / Byte.SIZE;
int offset = 0;
BigInteger x = new BigInteger(1, Arrays.copyOfRange(pubkey, offset,
offset + keySizeBytes));
offset += keySizeBytes;
BigInteger y = new BigInteger(1, Arrays.copyOfRange(pubkey, offset,
offset + keySizeBytes));
ECPoint w = new ECPoint(x, y);
ECPublicKeySpec otherKeySpec = new ECPublicKeySpec(w, params);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
ECPublicKey otherKey = (ECPublicKey) keyFactory
.generatePublic(otherKeySpec);
return otherKey;
}
private static byte[] encodeECPublicKey(ECPublicKey pubKey) {
int keyLengthBytes = pubKey.getParams().getOrder().bitLength()
/ Byte.SIZE;
byte[] publicKeyEncoded = new byte[2 * keyLengthBytes];
int offset = 0;
BigInteger x = pubKey.getW().getAffineX();
byte[] xba = x.toByteArray();
if (xba.length > keyLengthBytes + 1 || xba.length == keyLengthBytes + 1
&& xba[0] != 0) {
throw new IllegalStateException(
"X coordinate of EC public key has wrong size");
}
if (xba.length == keyLengthBytes + 1) {
System.arraycopy(xba, 1, publicKeyEncoded, offset, keyLengthBytes);
} else {
System.arraycopy(xba, 0, publicKeyEncoded, offset + keyLengthBytes
- xba.length, xba.length);
}
offset += keyLengthBytes;
BigInteger y = pubKey.getW().getAffineY();
byte[] yba = y.toByteArray();
if (yba.length > keyLengthBytes + 1 || yba.length == keyLengthBytes + 1
&& yba[0] != 0) {
throw new IllegalStateException(
"Y coordinate of EC public key has wrong size");
}
if (yba.length == keyLengthBytes + 1) {
System.arraycopy(yba, 1, publicKeyEncoded, offset, keyLengthBytes);
} else {
System.arraycopy(yba, 0, publicKeyEncoded, offset + keyLengthBytes
- yba.length, yba.length);
}
return publicKeyEncoded;
}
public static void main(String[] args) throws Exception {
// (only) required for named curves other than those used in JCE
Security.addProvider(new BouncyCastleProvider());
// create local and remote key
KeyPairGenerator kpgen = KeyPairGenerator.getInstance("ECDH", "BC");
ECGenParameterSpec genspec = new ECGenParameterSpec("brainpoolp256r1");
kpgen.initialize(genspec);
KeyPair localKeyPair = kpgen.generateKeyPair();
KeyPair remoteKeyPair = kpgen.generateKeyPair();
// test generation
byte[] encodedRemotePublicKey = encodeECPublicKey((ECPublicKey) remoteKeyPair
.getPublic());
// test creation
ECPublicKey remoteKey = decodeECPublicKey(
((ECPublicKey) localKeyPair.getPublic()).getParams(),
encodedRemotePublicKey);
// local key agreement
KeyAgreement localKA = KeyAgreement.getInstance("ECDH");
localKA.init(localKeyPair.getPrivate());
localKA.doPhase(remoteKey, true);
byte[] localSecret = localKA.generateSecret();
// remote key agreement
KeyAgreement remoteKA = KeyAgreement.getInstance("ECDH");
remoteKA.init(remoteKeyPair.getPrivate());
remoteKA.doPhase((ECPublicKey) localKeyPair.getPublic(), true);
byte[] remoteSecret = localKA.generateSecret();
// validation
System.out.println(Arrays.equals(localSecret, remoteSecret));
}
}