我需要解密从服务器接收到的一些数据,编写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
,我对iv
和value
进行了另一次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解码后获得的字节数组发送给解密方法解决了这个问题。
现在方法如下:
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
可以在padAndMcrypt()
函数中看到,给定的$值是使用PHP的serialize()
函数序列化的。你可以在Java中重新实现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总是一个好主意,因为它可以防止一些攻击,如填充oracle攻击。这也是一种很好的方法来检测密码文本的一般修改。
完整的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;
}