#android #encryption #android-keystore
#Android #шифрование #android-хранилище ключей
Вопрос:
Необходимо реализовать сквозное шифрование в приложении. Все отправленные и полученные данные будут зашифрованы. Необходимо отправить симметричный секретный ключ, сгенерированный в приложении и сохраненный в хранилище ключей Android, на сервер после шифрования его открытым ключом, который я получаю с сервера. Проблема: я не могу получить значение string / bytes секретного ключа, которое я хочу зашифровать.
У меня есть вспомогательный класс, который выполняет работу по генерации SecretKey и сохраняет и извлекает его при необходимости из хранилища ключей. Данные будут правильно зашифрованы и расшифрованы, однако из-за требования сквозного шифрования (все передаваемые данные также должны быть зашифрованы) мне нужно отправить secretkey на сервер, но я не могу получить к нему доступ. В классе SecretKey есть три метода получения. getAlgortithm — это возвращает «AES», как и ожидалось getEncoded — это возвращает null getFormat — это также возвращает null
public class EncryptionKeyGenerator {
public static final String ANDROID_KEY_STORE = "AndroidKeyStore";
public static final String KEY_ALIAS = "KEY_ALIAS";
private static final String KEY_STORE_FILE_NAME = "KEY_STORE";
private static final String KEY_STORE_PASSWORD = "KEY_STORE_PASSWORD";
@TargetApi(Build.VERSION_CODES.M)
static SecurityKey generateSecretKey(KeyStore keyStore) {
try {
if (!keyStore.containsAlias(KEY_ALIAS)) {
KeyGenerator keyGenerator =
KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT).setBlockModes(
KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setRandomizedEncryptionRequired(false)
.build());
return new SecurityKey(keyGenerator.generateKey());
}
} catch (KeyStoreException | NoSuchProviderException | NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
CommonMethods.printStackTrace(e);
}
try {
final KeyStore.SecretKeyEntry entry =
(KeyStore.SecretKeyEntry) keyStore.getEntry(KEY_ALIAS, null);
return new SecurityKey(entry.getSecretKey());
} catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableEntryException e) {
CommonMethods.printStackTrace(e);
}
return null;
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
static SecurityKey generateKeyPairPreM(Context context, KeyStore keyStore) {
try {
if (!keyStore.containsAlias(KEY_ALIAS)) {
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
//1 Year validity
end.add(Calendar.YEAR, 1);
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("RSA", ANDROID_KEY_STORE);
kpg.initialize(spec);
kpg.generateKeyPair();
}
} catch (KeyStoreException | NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchProviderException e) {
CommonMethods.printStackTrace(e);
}
try {
final KeyStore.PrivateKeyEntry entry =
(KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null);
return new SecurityKey(
new KeyPair(entry.getCertificate().getPublicKey(), entry.getPrivateKey()));
} catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableEntryException e) {
CommonMethods.printStackTrace(e);
}
return null;
}
static SecurityKey generateSecretKeyPre18(Context context) {
try {
KeyStore androidCAStore = KeyStore.getInstance(KeyStore.getDefaultType());
char[] password = KEY_STORE_PASSWORD.toCharArray();
boolean isKeyStoreLoaded = loadKeyStore(context, androidCAStore, password);
KeyStore.ProtectionParameter protParam = new KeyStore.PasswordProtection(password);
if (!isKeyStoreLoaded || !androidCAStore.containsAlias(KEY_ALIAS)) {
//Create and save new secret key
saveMyKeystore(context, androidCAStore, password, protParam);
}
// Fetch Secret Key
KeyStore.SecretKeyEntry pkEntry =
(KeyStore.SecretKeyEntry) androidCAStore.getEntry(KEY_ALIAS, protParam);
CommonMethods.printLog("e", "Secret Key Fetched :" new String(pkEntry.getSecretKey().getEncoded(), "UTF-8"), EncryptionKeyGenerator.class.getSimpleName());
return new SecurityKey(pkEntry.getSecretKey());
} catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException | UnrecoverableEntryException e) {
CommonMethods.printStackTrace(e);
}
return null;
}
private static boolean loadKeyStore(Context context, KeyStore androidCAStore, char[] password) {
java.io.FileInputStream fis;
try {
fis = context.openFileInput(KEY_STORE_FILE_NAME);
} catch (FileNotFoundException e) {
CommonMethods.printStackTrace(e);
return false;
}
try {
androidCAStore.load(fis, password);
return true;
} catch (IOException | NoSuchAlgorithmException | CertificateException e) {
CommonMethods.printStackTrace(e);
}
return false;
}
private static void saveMyKeystore(Context context, KeyStore androidCAStore, char[] password,
KeyStore.ProtectionParameter protParam)
throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException {
javax.crypto.SecretKey mySecretKey = KeyGenerator.getInstance("AES").generateKey();
KeyStore.SecretKeyEntry skEntry = new KeyStore.SecretKeyEntry(mySecretKey);
androidCAStore.load(null);
androidCAStore.setEntry(KEY_ALIAS, skEntry, protParam);
java.io.FileOutputStream fos = null;
try {
fos = context.openFileOutput(KEY_STORE_FILE_NAME, Context.MODE_PRIVATE);
androidCAStore.store(fos, password);
} finally {
if (fos != null) {
fos.close();
}
}
CommonMethods.printLog("e", "Secret Key Saved : " new String(mySecretKey.getEncoded(), "UTF-8"), EncryptionKeyGenerator.class.getSimpleName());
}
}
class SecurityKey {
private static final String RSA_MODE = "RSA/ECB/PKCS1Padding";
private static final String AES_MODE_FOR_POST_API_23 = "AES/GCM/NoPadding";
private static final String AES_MODE_FOR_PRE_API_18 = "AES/CBC/PKCS5Padding";
private SecretKey secretKey;
private KeyPair keyPair;
SecurityKey(SecretKey secretKey) {
this.secretKey = secretKey;
}
SecurityKey(KeyPair keyPair) {
this.keyPair = keyPair;
}
String encrypt(String token) {
if (token == null) return null;
try {
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE);
byte[] encrypted = cipher.doFinal(token.getBytes());
return Base64.encodeToString(encrypted, Base64.DEFAULT);
} catch (GeneralSecurityException e) {
CommonMethods.printStackTrace(e);
}
//Unable to encrypt Token
return null;
}
String decrypt(String encryptedToken) {
if (encryptedToken == null) return null;
try {
Cipher cipher = getCipher(Cipher.DECRYPT_MODE);
byte[] decoded = Base64.decode(encryptedToken, Base64.DEFAULT);
byte[] original = cipher.doFinal(decoded);
return new String(original);
} catch (GeneralSecurityException e) {
CommonMethods.printStackTrace(e);
}
//Unable to decrypt encrypted Token
return null;
}
private Cipher getCipher(int mode) throws GeneralSecurityException {
Cipher cipher;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
cipher = Cipher.getInstance(AES_MODE_FOR_POST_API_23);
cipher.init(mode, secretKey, new GCMParameterSpec(128, AES_MODE_FOR_POST_API_23.getBytes(), 0, 12));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
cipher = Cipher.getInstance(RSA_MODE);
cipher.init(mode, mode == Cipher.DECRYPT_MODE ? keyPair.getPublic() : keyPair.getPrivate());
} else {
cipher = Cipher.getInstance(AES_MODE_FOR_PRE_API_18);
cipher.init(mode, secretKey, new IvParameterSpec(new byte[cipher.getBlockSize()]));
}
return cipher;
}
}
Комментарии:
1. Не изобретайте заново, смотрите Протокол signal. Это с открытым исходным кодом.
2. Вместо того, чтобы изобретать протокол, лучше использовать существующие протоколы, такие как HTTPS, SFTP и т.д.
3. я прочитал это руководство: proandroiddev.com / … может быть полезно для вашего случая 🙂