【问题标题】:Decrypting SJCL encrypted strings in Android在 Android 中解密 SJCL 加密字符串
【发布时间】:2014-09-27 10:55:16
【问题描述】:

我有一个由 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 的字符串,它在通过带有 UTF-8 的 JsonParser 并被 Base64 解码后运行。我拿了它并用密码通过SJCL Demo 运行它并解密字符串没问题。我一定在这里遗漏了一些简单的东西。

我在 cipher.doFinal 步骤中遇到错误,如下所示:

java.security.InvalidKeyException: nonce must have length from 7 to 13 octets

我认为 SJCL 没有在他们的密码上使用任何填充,因此我尝试在 getInstance 上使用“AES/CCM/PKCS5Padding”,但随后出现此错误:

javax.crypto.NoSuchPaddingException: Only NoPadding can be used with AEAD modes.

TLDR:我正在寻找在 Android 中解密 SJCL 字符串的最简单方法。建议将不胜感激。

谢谢!

【问题讨论】:

  • 您正在加密客户端和服务器之间的通信?这正是TLS 的设计目的。请注意,您的方案不保护任何东西。任何人都可以对客户端应用程序进行逆向工程以获取密码并继续解密其他客户端的通信。
  • @ntoskrnl,我没有加密通信,只是解密从 Web 服务调用返回的字符串。密码也不会在最终实现中那样存储,这只是在我可以让它工作之前。
  • 我在 Java 中创建了一个 SJCL 库,只是为了放弃它。他们很快在他们的便利代码中发现了一个可怕的错误——在我无法验证(没有验证 AAD)之后,我实际上不希望 SJCL 成功;维护不善,这种东西不应该用JS。
  • @owlstead 你的 SJCL 库还能用吗?我很想继续检查一下,看看我是否无法从中得到一些工作。
  • 呃,不知道 :) 我会试试看能不能找到/更新任何东西。

标签: android cryptography bouncycastle spongycastle sjcl


【解决方案1】:

虽然这不是在 Java 中完成的,但实际上是在 Android 中完成的。我知道这不是我想要的解决方案,但它有效并且是一个解决方案。

您可以使用 WebView 在 Android 中运行 SJCL 来解密文件。您需要一个 WebView JavaScript 可以与之通信的接口。

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);
}

【讨论】:

  • 我最终得到了这个工作,并修改了这个答案以节省其他人的时间。效果很好。
  • 就像我在自己的项目中维护这段代码时的想法一样。 JavaScriptInterface 不仅可以用于获取答案,还可以用于将 encryptedData 与密钥一起传递。您所要做的就是添加更多的@JavascriptInterface 方法,然后通过传入您需要的变量的 javascript 调用这些方法。
【解决方案2】:

问题是 Java 不能使用默认的 sclj iv 编号,它使用 4 个单词。 解决办法是告诉sclj用3个字生成iv值:

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 实现,或者尝试找到一个支持 16 字节 iv 的实现。

【讨论】:

【解决方案3】:

虽然使用 WebView 会有所帮助,但我更喜欢将 ScriptEngine (SE) 与其 ScriptEngineManager (SEM) 一起使用,它最终也会加载 WebView,但至少代码看起来更易于理解和维护。

首先要做的事情是,由于 SE 和 SEM 在 Android 上本机不可用,您可以导入“rhino-library”来解决这个问题。我发现了这个here,供参考。

所以第一步,在你的 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上发了一个gist,大家可以看看here

希望有帮助

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多