Расшифровка зашифрованных строк SJCL в Android
У меня есть строка, которая была зашифрована серверной стороной SJCL и должна быть расшифрована в Android с использованием любых доступных библиотек. Я пробовал BouncyCastle, пока не столкнулся с проблемой неспособности сгенерировать ключ из PBKDF2. Сейчас я использую SpongyCastle, и у меня все еще есть проблемы. Вот мой код для генерации ключа и дешифрования строки:
private static byte[] decrypt(SecretKey key, byte[] encrypted, byte[] iv) throws Exception {
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
public static SecretKey generateKey(char[] passphraseOrPin, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
// Number of PBKDF2 hardening rounds to use. Larger values increase
// computation time. You should select a value that causes computation
// to take >100ms.
final int iterations = 1000;
// Generate a 128-bit key
final int outputKeyLength = 128;
/*SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec keySpec = new PBEKeySpec(passphraseOrPin, salt, iterations, outputKeyLength);
SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);*/
PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator(new SHA256Digest());
generator.init(PBEParametersGenerator.PKCS5PasswordToBytes(passphraseOrPin), salt, iterations);
KeyParameter key = (KeyParameter) generator.generateDerivedMacParameters(outputKeyLength);
SecretKey secretKey = new SecretKeySpec(key.getKey(), "AES");
return secretKey;
}
Вот как я называю это в моей функции:
char[] key = * put PBKDF2 password here *;
// Generate key from password
SecretKey decryptionKey = null;
try {
decryptionKey = generateKey(key, decodedObject.get("salt").getAsString().getBytes());
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
byte[] decryptedTicketBytes = null;
// Decrypt the ticket
try {
decryptedTicketBytes = decrypt(decryptionKey, decodedObject.get("ct").getAsString().getBytes(), decodedObject.get("iv").getAsString().getBytes());
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
DecodedObject - это строка из SJCL после ее запуска через JsonParser с UTF-8 и декодирования Base64. Я взял его и провел через SJCL Demo с паролем и без проблем расшифровал строку. Я, должно быть, здесь упускаю что-то простое.
Ошибка, которую я получаю, находится на шаге cipher.doFinal и выглядит следующим образом:
java.security.InvalidKeyException: nonce must have length from 7 to 13 octets
Я не думаю, что SJCL не использует заполнение в своем шифре, поэтому я попытался использовать "AES/CCM/PKCS5Padding" в getInstance, но затем получил эту ошибку:
javax.crypto.NoSuchPaddingException: Only NoPadding can be used with AEAD modes.
TLDR: я ищу самый простой способ расшифровки строк SJCL в Android. Предложения будут оценены.
Спасибо!
2 ответа
Хотя это не сделано в Java, на самом деле это делается в Android. Я знаю, что это не то решение, которое я хотел, но оно работает и является решением.
Вы можете использовать WebView для запуска SJCL в Android для расшифровки файла. Вам нужен интерфейс, с которым JavaScript WebView может общаться.
public static class JavaScriptInterface {
Context mContext;
/** Instantiate the interface and set the context */
JavaScriptInterface(Context c) {
mContext = c;
}
//This is the function that is callable from the Javascript.
@JavascriptInterface
public void doJavaStuff(String dycryptedData)
{
//Do what you want with the decrypted data
}
}
Оттуда вам нужно будет создать метод для получения скрипта с SJCL
public static String getSJCL() { return "<script>\n" + "SJCL_HERE" + "<script>\n";
Затем создайте HTML-код для запуска кода. Все это можно сделать с помощью файлов.html и.script, но я хотел, чтобы мой ключ и зашифрованные файлы были динамическими.
public static String getHtmlForDecrypting(String encryptedData, String key) {
return "<html>\n" +
" <head>\n" +
getSJCL() +
" <script type=\"text/javascript\">\n" +
" function displaymessage()\n" +
" {\n" +
" var obj = " + encryptedData + ";\n" +
" var objString = JSON.stringify(obj);\n" +
" var data = sjcl.decrypt(\"" + key + "\", objString);\n" +
" JSInterface.doJavaStuff(data);\n" +
" }\n" +
" </script>\n" +
" </head>\n" +
" <body onload=\"displaymessage()\"></body>\n" +
"</html>\n";
}
Затем создайте метод расшифровки.
private static WebView wv;
private static JavaScriptInterface JSInterface;
private static String key1 = "YOURKEY";
public static void decrypt(Context context, String data) {
JSInterface = new JavaScriptInterface(context);
wv = new WebView(context);
wv.addJavascriptInterface(JSInterface, "JSInterface");
wv.getSettings().setJavaScriptEnabled(true);
wv.loadData(getHtmlForDecrypting(data, key1), "text/html", null);
}
Проблема в том, что Java не работает с номером sclj iv по умолчанию, который использует 4 слова. Решение состоит в том, чтобы сказать sclj сгенерировать значение iv из 3 слов:
aesEncrypt:function (pass,data) {
var param = { // Java does not accepts the default 4 word size
iv: sjcl.random.randomWords(3, 0)
};
return sjcl.encrypt(pass,data,param);
}
не забудьте передать значения iv и salt вместе с зашифрованным сообщением. Если вы не можете сказать sclj генерировать меньшее число iv, вам придется изменить реализацию java или попытаться найти такую, которая поддерживает iv с 16 байтами.
Хотя использование WebView может помочь, я предпочитаю использовать ScriptEngine (SE) вместе с его ScriptEngineManager (SEM), который также загрузит WebView в конце, но, по крайней мере, код выглядит намного проще для понимания и обслуживания.
Первое, что нужно сделать, поскольку SE и SEM изначально не доступны на Android, вы можете импортировать "библиотеку носорога", которая решает эту проблему. Я узнал об этом здесь, для справки.
Итак, первым делом добавьте зависимость в файл build.gradle:
implementation 'io.apisense:rhino-android:1.0'
После этого все остальное довольно просто, вот закомментированный код о том, как я это сделал.:
package com.example;
import android.content.Context;
import java.io.InputStream;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import com.example.Constants;
public class SJCL {
private Invocable invocable;
public SJCL() {
}
public void init(Context c) {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
try {
//in this case the constant points to assets/SJCL.js
InputStream is = c.getAssets().open(Constants.SJCL_FILE_PATH);
//put that into a string (function further)
String sjcl = convertStreamToString(is);
// read script file
engine.evals(sjcl);
invocable = (Invocable) engine;
} catch (Exception e) {
e.printStackTrace();
}
}
//call js function from wrapper function, this is just one as example
public String encrypt(Object password, String plainText, String[] params) {
try {
//first argument is the function name, second is a Object... with your arguments
return invocable.invokeFunction("encrypt",password,plainText,params).toString();
} catch (ScriptException | NoSuchMethodException e) {
return null;
}
}
public String convertStreamToString(InputStream is) throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
reader.close();`enter code here
return sb.toString();
}
}
Я также опубликовал это как суть на github, вы можете посмотреть здесь
надеюсь, это поможет