如何解密在 Laravel 中使用 Crypt 加密的 Java (Android) 文本?
我需要解密我从服务器接收到的一些数据,制作 API 的程序员将我引导到这个 Encrypter 类,看看他用来加密什么.
现在基于该类,我发现使用的算法是 AES128 CBC,并且我收到的字符串是 Base64 编码的,并且包含其他数据,而不仅仅是密文.
即如果我收到以下字符串:
<预> <代码> eyJpdiI6InJsSzRlU3pDZTBBUVNwMzdXMjVcL0tBPT0iLCJ2YWx1ZSI6Ik5JOENsSVVWaWk2RGNhNlwvWjJNeG94UzVkclwvMGJOREQreWUyS1UzclRMND0iLCJtYWMiOiJhZTZkYjNkNGM2ZTliNmU0ZTc0MTRiNDBmMzFlZTJhNTczZWIxMjk4N2YwMjlhODA1NTIyMDEzODljNDY2OTk2In0base64 解码后我得到:
{iv":rlK4eSzCe0AQSp37W25/KA==",值":NI8ClIUVii6Dca6/Z2MxoxS5dr/0bNDD+ye2KU3rTL4=",mac":"ae6db3d4c6e9b6e4e7414b40f31ee2a573eb12987f029a80552201389c466996"}
基于 Encrypter
类的 line 99
( iv = base64_decode($payload['iv']);
),我执行了另一个在 iv
和 value
上进行 base64 解码,得到长度为 16 的 iv
.我将这些作为参数传递给下面的函数:
public static String decrypt(String iv, String encryptedData) 抛出异常 {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);字节[] decValue = c.doFinal(decordedValue);返回新字符串(decValue);}
但我收到以下错误:
10-06 19:13:33.601 12895-12895/?W/System.err:java.security.InvalidAlgorithmParameterException:预期的 IV 长度为 1610-06 19:13:33.601 12895-12895/?W/System.err:在 com.android.org.conscrypt.OpenSSLCipher.engineInitInternal(OpenSSLCipher.java:281)10-06 19:13:33.601 12895-12895/?W/System.err:在 com.android.org.conscrypt.OpenSSLCipher.engineInit(OpenSSLCipher.java:323)10-06 19:13:33.601 12895-12895/?W/System.err:在 javax.crypto.Cipher.init(Cipher.java:751)10-06 19:13:33.601 12895-12895/?W/System.err:在 javax.crypto.Cipher.init(Cipher.java:701)10-06 19:13:33.601 12895-12895/?W/System.err:在 com.example.kushtrim.testproject.MainActivity.decrypt(MainActivity.java:62)10-06 19:13:33.601 12895-12895/?W/System.err:在 com.example.kushtrim.testproject.MainActivity.onCreate(MainActivity.java:45)10-06 19:13:33.601 12895-12895/?W/System.err:在 android.app.Activity.performCreate(Activity.java:5990)10-06 19:13:33.601 12895-12895/?W/System.err:在 android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)10-06 19:13:33.601 12895-12895/?W/System.err:在 android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2278)10-06 19:13:33.601 12895-12895/?W/System.err:在 android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387)10-06 19:13:33.601 12895-12895/?W/System.err:在 android.app.ActivityThread.access$800(ActivityThread.java:151)10-06 19:13:33.601 12895-12895/?W/System.err:在 android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)10-06 19:13:33.602 12895-12895/?W/System.err:在 android.os.Handler.dispatchMessage(Handler.java:102)10-06 19:13:33.602 12895-12895/?W/System.err:在 android.os.Looper.loop(Looper.java:135)10-06 19:13:33.602 12895-12895/?W/System.err:在 android.app.ActivityThread.main(ActivityThread.java:5254)10-06 19:13:33.602 12895-12895/?W/System.err:在 java.lang.reflect.Method.invoke(Native Method)10-06 19:13:33.602 12895-12895/?W/System.err:在 java.lang.reflect.Method.invoke(Method.java:372)10-06 19:13:33.602 12895-12895/?W/System.err:在 com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)10-06 19:13:33.602 12895-12895/?W/System.err:在 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) 抛出异常 {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);字节[] decValue = c.doFinal(decordedValue);返回新字符串(decValue);}
现在我又遇到了一个奇怪的问题:
我首先加密的文本是 KushtrimPacaj
,但解密后的文本是 s:13:"KushtrimPacaj";
.那另一部分是从哪里来的?13或许代表KushtrimPacaj
的长度?
编辑
这是工作代码,以防万一有人需要它:
https://gist.github.com/KushtrimPacaj/43a383ab419fc222f80e
可以在padAndMcrypt()
函数,即给定的 $value 使用 PHP 的 serialize()
函数.你可以重新实现 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) 抛出异常 {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);密码 c = Cipher.getInstance("AES/CBC/PKCS7Padding");//或 PKCS5Paddingc.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) 抛出异常 {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))返回空值;//或抛出异常密码 c = Cipher.getInstance("AES/CBC/PKCS7Padding");//或 PKCS5Paddingc.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));}/* 恒定时间比较以防止对无效身份验证标签的定时攻击.*/公共静态布尔secureEquals(最终字节[]已知,最终字节[]用户){int knownLen = known.length;int userLen = user.length;int 结果 = knownLen ^ userLen;for (int i = 0; i < knownLen; i++) {结果 |= 已知[i] ^ 用户[i % userLen];}返回结果 == 0;}
I need to decrypt some data that I receive from the server, and the programmer who made the API directed me to this Encrypter class, to see what he used to encrypt.
Now based on that class, I found that the algorithm used is AES128 CBC, and that the string I receive is Base64 encoded and contains other data, not just the ciphertext.
Namely that if I receive the following String:
eyJpdiI6InJsSzRlU3pDZTBBUVNwMzdXMjVcL0tBPT0iLCJ2YWx1ZSI6Ik5JOENsSVVWaWk2RGNhNlwvWjJNeG94UzVkclwvMGJOREQreWUyS1UzclRMND0iLCJtYWMiOiJhZTZkYjNkNGM2ZTliNmU0ZTc0MTRiNDBmMzFlZTJhNTczZWIxMjk4N2YwMjlhODA1NTIyMDEzODljNDY2OTk2In0
after base64 decoding I get:
{"iv":"rlK4eSzCe0AQSp37W25/KA==","value":"NI8ClIUVii6Dca6/Z2MxoxS5dr/0bNDD+ye2KU3rTL4=","mac":"ae6db3d4c6e9b6e4e7414b40f31ee2a573eb12987f029a80552201389c466996"}
Based on line 99
of Encrypter
class ( iv = base64_decode($payload['iv']);
), I performed another base64 decode on the iv
and the value
, and got an iv
of length 16. Those I passed as parameters to the function below:
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);
}
But I'm getting the following error:
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)
Note: The String iv
has length of 16, but iv.getBytes()
returns an array of length 26.
Could someone point me to where I went wrong, and how do I fix it. Thanks/
EDIT
After the comment, I made some changes, that resolved the above error:
Before I was base64 decoding iv
, converting the bytes to String, then passing that String to the decrypt method, which in return called the getBytes() on it. Somehow this made the byte array have a length of 26.
Sending the byte array I obtained after base64 decoding to the decrypt method fixed the problem.
Now the method is as follows:
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);
}
Now I have another weird problem:
The text I encrypted on the first place was KushtrimPacaj
, but the decrypted text is s:13:"KushtrimPacaj";
.
Where is that other part coming from ? 13 perhaps represents the length of KushtrimPacaj
?
Edit
Here's the working code, in case anyone needs it :
https://gist.github.com/KushtrimPacaj/43a383ab419fc222f80e
You can see in the padAndMcrypt()
function, that the given $value is serialized using PHP's serialize()
function. You can re-implement the unserialize()
function in Java or you can split the byte array yourself if you're always encrypting strings in PHP.
int firstQuoteIndex = 0;
while(decValue[firstQuoteIndex] != (byte)'"') firstQuoteIndex++;
return new String(Arrays.copyOfRange(decValue, firstQuoteIndex + 1, decValue.length-2));
Full code:
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));
}
Verifying the MAC is always a good idea, because it prevents some attacks such as the padding oracle attack. It is also a very good way to detect general modifications of ciphertexts.
Full code with MAC verification:
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;
}
相关文章