【问题标题】:How to decrypt in Java (Android) text that was encrypted with Crypt in Laravel?如何解密在 Laravel 中使用 Crypt 加密的 Java (Android) 文本?
【发布时间】:2016-01-03 17:11:40
【问题描述】:

我需要解密我从服务器接收到的一些数据,制作 API 的程序员将我引导到这个 Encrypter 类,看看他用来加密的内容。

现在基于该类,我发现使用的算法是 AES128 CBC,并且我收到的字符串是 Base64 编码的,并且包含其他数据,而不仅仅是密文。

即如果我收到以下字符串:

eyJpdiI6InJsSzRlU3pDZTBBUVNwMzdXMjVcL0tBPT0iLCJ2YWx1ZSI6Ik5JOENsSVVWaWk2RGNhNlwvWjJNeG94UzVkclwvMGJOREQreWUyS1UzclRMND0iLCJtYWMiOiJhZTZkYjNkNGM2ZTliNmU0ZTc0MTRiNDBmMzFlZTJhNTczZWIxMjk4N2YwMjlhODA1NTIyMDEzODljNDY2OTk2In0

base64 解码后我得到:

{"iv":"rlK4eSzCe0AQSp37W25\/KA==","value":"NI8ClIUVii6Dca6\/Z2MxoxS5dr\/0bNDD+ye2KU3rTL4=","mac":"ae6db3d4c6e9b6e4e7414b40f31ee2a573eb12987f029a80552201389c466996"}

基于Encrypter 类(iv = base64_decode($payload['iv']);)的line 99,我对ivvalue 执行了另一个base64 解码,得到了长度为16 的iv。我作为参数传递的那些到下面的函数:

    public static String decrypt(String iv, String encryptedData) throws Exception {
    byte[] keyValue = "zy2dEd1pKG5i3WuWbvOBolFQR84AYbvN".getBytes();
    Key key = new SecretKeySpec(keyValue, "AES");        
    Cipher c = Cipher.getInstance("AES/CBC/PKCS7Padding");
    c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv.getBytes()));
    byte[] decordedValue = Base64.decode(encryptedData.getBytes(), Base64.DEFAULT);
    byte[] decValue = c.doFinal(decordedValue);
    return new String(decValue);
}

但我收到以下错误:

10-06 19:13:33.601 12895-12895/? W/System.err: java.security.InvalidAlgorithmParameterException: expected IV length of 16
10-06 19:13:33.601 12895-12895/? W/System.err:     at com.android.org.conscrypt.OpenSSLCipher.engineInitInternal(OpenSSLCipher.java:281)
10-06 19:13:33.601 12895-12895/? W/System.err:     at com.android.org.conscrypt.OpenSSLCipher.engineInit(OpenSSLCipher.java:323)
10-06 19:13:33.601 12895-12895/? W/System.err:     at javax.crypto.Cipher.init(Cipher.java:751)
10-06 19:13:33.601 12895-12895/? W/System.err:     at javax.crypto.Cipher.init(Cipher.java:701)
10-06 19:13:33.601 12895-12895/? W/System.err:     at com.example.kushtrim.testproject.MainActivity.decrypt(MainActivity.java:62)
10-06 19:13:33.601 12895-12895/? W/System.err:     at com.example.kushtrim.testproject.MainActivity.onCreate(MainActivity.java:45)
10-06 19:13:33.601 12895-12895/? W/System.err:     at android.app.Activity.performCreate(Activity.java:5990)
10-06 19:13:33.601 12895-12895/? W/System.err:     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
10-06 19:13:33.601 12895-12895/? W/System.err:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2278)
10-06 19:13:33.601 12895-12895/? W/System.err:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387)
10-06 19:13:33.601 12895-12895/? W/System.err:     at android.app.ActivityThread.access$800(ActivityThread.java:151)
10-06 19:13:33.601 12895-12895/? W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
10-06 19:13:33.602 12895-12895/? W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:102)
10-06 19:13:33.602 12895-12895/? W/System.err:     at android.os.Looper.loop(Looper.java:135)
10-06 19:13:33.602 12895-12895/? W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:5254)
10-06 19:13:33.602 12895-12895/? W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
10-06 19:13:33.602 12895-12895/? W/System.err:     at java.lang.reflect.Method.invoke(Method.java:372)
10-06 19:13:33.602 12895-12895/? W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
10-06 19:13:33.602 12895-12895/? W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

注意:字符串iv的长度为16,但iv.getBytes()返回一个长度为26的数组。

谁能指出我哪里出错了,我该如何解决。 谢谢/

编辑
评论后,我做了一些更改,解决了上述错误:
在我对iv 进行base64 解码之前,将字节转换为字符串,然后将该字符串传递给解密方法,该方法在其上调用getBytes()。不知何故,这使得字节数组的长度为 26。
将我base64解码后得到的字节数组发送到decrypt方法解决了这个问题。
现在方法如下:

public static String decrypt(byte[] iv, String encryptedData) throws Exception {
    byte[] keyValue = "zy2dEd1pKG5i3WuWbvOBolFQR84AYbvN".getBytes();
    Key key = new SecretKeySpec(keyValue, "AES");
    Cipher c = Cipher.getInstance("AES/CBC/PKCS7Padding");
    c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
    byte[] decordedValue = Base64.decode(encryptedData.getBytes(), Base64.DEFAULT);
    byte[] decValue = c.doFinal(decordedValue);
    return new String(decValue);
}

现在我遇到了另一个奇怪问题:
我首先加密的文本是 KushtrimPacaj ,但解密后的文本是 s:13:"KushtrimPacaj"; 。 那另一部分是从哪里来的? 13或许代表KushtrimPacaj的长度?

编辑
这是工作代码,以防万一有人需要它:
https://gist.github.com/KushtrimPacaj/43a383ab419fc222f80e

【问题讨论】:

  • 请提供一个完整的例子。如果iv 是一个字符串,它的值是多少?你忘记解码了吗?我看不出长度为 26 的原因。请记住,您不能将二进制/不可打印数据作为字符串传递。您需要使用字节数组。
  • @ArtjomB。感谢您的评论,您给了我一个导致长度问题的想法。虽然现在我有另一个奇怪的(见编辑过的问题)。关于如何解决它的任何想法?

标签: java php android encryption cryptography


【解决方案1】:

您可以在padAndMcrypt() function 中看到,给定的$value 是使用PHP 的serialize() function 序列化的。您可以在 Java 中 re-implement unserialize() 函数,或者如果您总是在 PHP 中加密字符串,您可以自己拆分字节数组。

int firstQuoteIndex = 0;
while(decValue[firstQuoteIndex] != (byte)'"') firstQuoteIndex++;
return new String(Arrays.copyOfRange(decValue, firstQuoteIndex + 1, decValue.length-2));

完整代码:

public static String decrypt(byte[] keyValue, String ivValue, String encryptedData) throws Exception {
    Key key = new SecretKeySpec(keyValue, "AES");
    byte[] iv = Base64.decode(ivValue.getBytes("UTF-8"), Base64.DEFAULT);
    byte[] decodedValue = Base64.decode(encryptedData.getBytes("UTF-8"), Base64.DEFAULT);

    Cipher c = Cipher.getInstance("AES/CBC/PKCS7Padding"); // or PKCS5Padding
    c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
    byte[] decValue = c.doFinal(decodedValue);

    int firstQuoteIndex = 0;
    while(decValue[firstQuoteIndex] != (byte)'"') firstQuoteIndex++;
    return new String(Arrays.copyOfRange(decValue, firstQuoteIndex + 1, decValue.length-2));
}

验证 MAC 始终是一个好主意,因为它可以防止某些攻击,例如填充预言攻击。这也是一种很好的检测密文一般修改的方法。

带有 MAC 验证的完整代码:

public static String decrypt(byte[] keyValue, String ivValue, String encryptedData, String macValue) throws Exception {
    Key key = new SecretKeySpec(keyValue, "AES");
    byte[] iv = Base64.decode(ivValue.getBytes("UTF-8"), Base64.DEFAULT);
    byte[] decodedValue = Base64.decode(encryptedData.getBytes("UTF-8"), Base64.DEFAULT);

    SecretKeySpec macKey = new SecretKeySpec(keyValue, "HmacSHA256");
    Mac hmacSha256 = Mac.getInstance("HmacSHA256");
    hmacSha256.init(macKey);
    hmacSha256.update(ivValue.getBytes("UTF-8"));
    byte[] calcMac = hmacSha256.doFinal(encryptedData.getBytes("UTF-8"));
    byte[] mac = Hex.decodeHex(macValue.toCharArray());
    if (!secureEquals(calcMac, mac))
        return null; // or throw exception

    Cipher c = Cipher.getInstance("AES/CBC/PKCS7Padding"); // or PKCS5Padding
    c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
    byte[] decValue = c.doFinal(decodedValue);

    int firstQuoteIndex = 0;
    while(decValue[firstQuoteIndex] != (byte)'"') firstQuoteIndex++;
    return new String(Arrays.copyOfRange(decValue, firstQuoteIndex + 1, decValue.length-2));
}

/* Constant-time compare to prevent timing attacks on invalid authentication tags. */
public static boolean secureEquals(final byte[] known, final byte[] user) {
    int knownLen = known.length;
    int userLen = user.length;

    int result = knownLen ^ userLen;
    for (int i = 0; i < knownLen; i++) {
        result |= known[i] ^ user[i % userLen];
    }
    return result == 0;
}

【讨论】:

  • 谢谢伙计。我知道如何拆分 String 但不敢这样做,因为我不知道它为什么会发生,以及它是否会始终遵循相同的模式。您还可以编辑答案,并在 copyOfRange 中设置第二个参数:“firstQuoteIndex + 1”,因为起始索引包含在内。 Stackoverflow 不允许我进行少于 6 个字符的编辑。
  • 我添加了 MAC 验证。你能检查它是否按预期工作吗?
  • // TODO: use time-constant compare你愿意为此做一个 Github 项目吗?
  • @ScottArciszewski 暂时没有,因为......我没有时间正确地做这件事。
  • @VijaySankhat 好的,工资是 10000 欧元。合同工作完成后,您可以先寄给我 5000 欧元和后半部分。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-03-11
  • 2015-11-08
  • 1970-01-01
  • 1970-01-01
  • 2016-05-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多