расшифровывать зашифрованные данные php на андроид

Android Приложение клиента (4.2.1) отправляет открытый ключ через HttpPost запрос к PHP (5.6) API. Этот API шифрует данные с AES уступчивый RIJNDAEL_128затем шифрует ключ для шифрования AES с помощью открытого ключа клиента с помощью открытого шифрования OpenSSL и RSA_PKCS1_OAEP_PADDING, Отправляет эти данные base64 кодируется через XML вернуться к клиентскому приложению Android, которое должно зашифровать данные. Я установил базовый тестовый скрипт PHP, который тестирует весь процесс, он работает как положено.

В настоящее время я работаю над реализацией дешифрования в клиентском приложении Android, но уже дешифрование AES-ключа завершается неудачно. У меня есть другие вопросы помимо этой текущей проблемы (см. В конце).

Вот текстовое графическое резюме того, что происходит:

client -> public key -> API -> data -> AESencrypt(data), RSAencrypt(AES-key) -> base64encode[AES(data)], base64encode[RSA(AES-key)] -> <xml>base64[AES(data)], base64[RSA(AES-key)]</xml> -> client -> base64[AES(data)], base64[RSA(AES-key)] -> base64decode[AES(data)], base64decode[RSA(AES-key)] -> AESdecrypt(data), RSAdecrypt(AES-key) -> data

Я шифрую данные MCRYPT_RIJNDAEL_128 который я прочитал, совместим с AES (см. PHP документ для mycrypt).
Вот код:

<?php
$randomBytes = openssl_random_pseudo_bytes(32, $safe);
$randomKey = bin2hex($randomBytes);
$randomKeyPacked = pack('H*', $randomKey);
// test with fixed key:
// $randomKeyPacked = "12345678901234567890123456789012";
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$dataCrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $randomKeyPacked, $data, MCRYPT_MODE_CBC, $iv);

Выходящий из этого ключ AES кодируется openssl_public_encrypt и установка отступов OPENSSL_PKCS1_OAEP_PADDING, Чтение исходного кода (источник реализации PHP OpenSSL) это эквивалентно RSA_PKCS1_OAEP_PADDING описанный как

EME-OAEP, как определено в PKCS # 1 v2.0 с SHA-1, MGF1 и пустым параметром кодирования.

в документации OpenSSL нашел Вот. Потом я base64_encode данные, чтобы иметь возможность передавать его через XML-строку клиенту. Код выглядит так:

openssl_public_encrypt($randomKeyPacked, $cryptionKeyCrypted, $clientPublicKey, OPENSSL_PKCS1_OAEP_PADDING);
$content = array(
'cryptionKeyCryptedBase64' => base64_encode($cryptionKeyCrypted),
'cryptionIVBase64' => base64_encode($iv),
'dataCryptedBase64' => base64_encode($dataCrypted)
);
// $content gets parsed to a valid xml element here

Клиентское приложение Android получает данные через HttpPost запрос через BasicResponseHandler, Эта возвращенная строка XML верна и проанализирована с помощью просто в соответствующие объекты Java. В классе, содержащем фактическое содержание переданных данных, я в настоящее время пытаюсь расшифровать данные. Я расшифровываю ключ AES с преобразованием RSA/ECB/OAEPWithSHA-1AndMGF1Padding который из-за этот сайт (только я мог найти) является допустимой строкой и, по-видимому, эквивалентно заполнению, которое я использовал в PHP. Я включил способ генерации закрытого ключа, так же как и генерацию открытого ключа, который был отправлен в PHP API. Вот этот класс:

public class Content {

@Element
private String cryptionKeyCryptedBase64;

@Element
private String cryptionIVBase64;

@Element
private String dataCryptedBase64;

@SuppressLint("TrulyRandom")
public String getData() {
String dataDecrypted = null;
try {
PRNGFixes.apply(); // fix TrulyRandom
KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
keygen.initialize(2048);
KeyPair keypair = keygen.generateKeyPair();
PrivateKey privateKey = keypair.getPrivate();

byte[] cryptionKeyCrypted = Base64.decode(cryptionKeyCryptedBase64, Base64.DEFAULT);
//byte[] cryptionIV = Base64.decode(cryptionIVBase64, Base64.DEFAULT);

Cipher cipherRSA = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
cipherRSA.init(Cipher.DECRYPT_MODE, privateKey);
byte[] key = cipherRSA.doFinal(cryptionKeyCrypted);

byte[] dataCrytped = Base64.decode(dataCryptedBase64, Base64.DEFAULT);
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipherAES = Cipher.getInstance("AES");
cipherAES.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decryptedAESBytes = cipherAES.doFinal(dataCrytped);
dataDecrypted = new String(decryptedAESBytes, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return dataDecrypted;
}
}

Делая это, я в настоящее время терплю неудачу на линии

byte[] key = cipherRSA.doFinal(cryptionKeyCrypted);

с Bad padding exceptions почти для всего PHP openssl_public_encrypt параметр padding — комбинации строк преобразования Android Cipher, которые я пробовал. Используя стандартный параметр заполнения PHP, пропуская параметр заполнения в openssl_public_encrypt, который по умолчанию равен OPENSSL_PKCS1_PADDING и строка преобразования шифра просто Cipher.getInstance("RSA") Я не получаю исключение плохого заполнения. Но зашифрованный ключ, кажется, не является действительным, поскольку расшифровка AES не

java.security.InvalidKeyException: Key length not 128/192/256 bits.

Я попытался проверить это с помощью фиксированного ключа (см. Комментарий к коду в PHP-коде выше), и я не получаю тот же ключ после его расшифровки и преобразования в строку. Кажется, что это просто искаженные данные, хотя их длина составляет 256 бит, если я правильно прочитал отладчик Eclipse ADT.

Что может быть правильной строкой преобразования Cipher для использования в качестве эквивалента PHP OPENSSL_PKCS1_OAEP_PADDING, чтение эта документация Мне нужна строка преобразования в виде "algorithm/mode/padding"Я догадался, что алгоритм = RSA, но не смог выяснить, как перевести то, что в документации OpenSSL (выше) о заполнении, в допустимую строку преобразования шифра. То есть что такое mode например?
К сожалению это Расшифровка Android RSA (сбой) / шифрование на стороне сервера (openssl_public_encrypt) принятый ответ не решил мою проблему.

В любом случае это может решить мою проблему или моя проблема возникла в другом месте?

Как бы я дальше отлаживать это? Как правильно преобразовать декодированный, расшифрованный ключ base64 в удобочитаемую форму, чтобы я мог сравнить его с ключом, используемым для шифрования?
Я пробовал с:

String keyString =  new String(keyBytes, "UTF-8");

Но это не возвращает никакого читабельного текста, поэтому я предполагаю, что ключ неверен или мой метод его преобразования.

Также расшифровывать зашифрованные данные AES в PHP IV требуется в функции дешифрования mcrypt_decrypt. Как вы можете видеть в коде, который я отправляю, но в Android это не нужно? Почему так?

PS: Надеюсь, я предоставил всю необходимую информацию, могу добавить в комментариях.

PPS: Для полноты вот код клиента Android, который делает запрос HttpPost:

@SuppressLint("TrulyRandom")
protected String doInBackground(URI... urls) {
try {
System.setProperty("jsse.enableSNIExtension", "false");
HttpClient httpClient = createHttpClient();
HttpPost httpPost = new HttpPost(urls[0]);

PRNGFixes.apply(); // fix TrulyRandom
KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
keygen.initialize(2048);
KeyPair keypair = keygen.generateKeyPair();
PublicKey publickey = keypair.getPublic();
byte[] publicKeyBytes = publickey.getEncoded();
String pubkeystr = "-----BEGIN PUBLIC KEY-----\n"+Base64.encodeToString(publicKeyBytes,
Base64.DEFAULT)+"-----END PUBLIC KEY-----";

List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
nameValuePairs.add(new BasicNameValuePair("publickey", pubkeystr));
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));

// Execute HTTP Post Request
HttpResponse response = httpClient.execute(httpPost);
return new BasicResponseHandler().handleResponse(response);
} catch (Exception e) {
Toast toast = Toast.makeText(asyncResult.getContext(),
"unknown exception occured: " + e.getMessage(),
Toast.LENGTH_SHORT);
toast.show();
return "error";
}
}

2

Решение

Вы генерируете одну пару ключей RSA в doInBackground и указание хосту использовать открытую половину этой пары ключей для шифрования DEK (ключа шифрования данных). Затем вы генерируете совершенно другую пару ключей RSA в getData и попытка использовать частную половину этой пары ключей для расшифровки зашифрованной DEK. Способ шифрования с открытым ключом заключается в том, что вы шифруете с открытой половиной пары ключей и расшифровываете с помощью частной половины та же пара ключей; общественные и частные половины математически связаны. Вам необходимо сохранить и использовать как минимум частную половину пары ключей (необязательно пару ключей с обеими половинами), открытую половину которой вы отправляете.

Как только вы получите DEK правильно, чтобы расшифровать данные в режиме CBC, да, вам нужно использовать тот же IV для расшифровки как был использован для шифрования. Ваш приемник должен положить его в IvParameterSpec и передать это на Cipher.init(direction,key[,params]) вызов. В качестве альтернативы, если вы можете изменить PHP, так как вы используете новый DEK для каждого сообщения, можно использовать фиксированный IV; проще всего зашифровать с '\0'x16 и разрешить Java расшифровывать по умолчанию все ноль.


Дополнительно вам нужно установить Base64.decode с параметром Base64.NO_WRAPтак как PHP просто выпустит base64, разделенный \0, И для этого вам также нужно будет использовать "AES/CBC/ZeroBytePadding" преобразование шифра для расшифровки данных AES как функции PHP mycrypt_encrypt дополнит данные нулями.
Вот что getData Функция должна выглядеть так:

public String getData() {
String dataDecrypted = null;
try {
byte[] cryptionKeyCrypted = Base64.decode(cryptionKeyCryptedBase64, Base64.NO_WRAP);
byte[] cryptionIV = Base64.decode(cryptionIVBase64, Base64.NO_WRAP);

Cipher cipherRSA = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
// get private key from the pair used to grab the public key to send to the api
cipherRSA.init(Cipher.DECRYPT_MODE, rsaKeyPair.getPrivateKey());
byte[] key = cipherRSA.doFinal(cryptionKeyCrypted);

byte[] dataCrytped = Base64.decode(dataCryptedBase64, Base64.NO_WRAP);
IvParameterSpec ivSpec = new IvParameterSpec(cryptionIV);
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipherAES = Cipher.getInstance("AES/CBC/ZeroBytePadding");
cipherAES.init(Cipher.DECRYPT_MODE, skeySpec, ivSpec);
byte[] decryptedAESBytes = cipherAES.doFinal(dataCrytped);
dataDecrypted = new String(decryptedAESBytes, "UTF-8");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return dataDecrypted;
}
1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]