正在 Android 上解密 "SunJCE" AES 加密数据
Decrypting "SunJCE" AES encrypted data on Android
我们需要编写一些 Android 代码来解密从我们的服务器发送的一些数据。我们的服务器团队给了我们一些示例解密代码,它使用 "SunJCE" 提供程序,不幸的是 Android 上不存在。
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE");
有人知道在 Android 上实现这个的最干净的方法吗?如果我们在 Android
上尝试这个
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
然后看起来在解密文本的末尾出现了一些不需要的垃圾,例如:
ComparisonFailure: expected:<...lAAAAABJRU5ErkJggg==[]> but was:<...lAAAAABJRU5ErkJggg==[��������]>
我们在 Android 密码 class 中尝试了很多不同转换的组合(例如 "AES/CBC/PKCS5Padding"),但一直遇到诸如 BadPaddingExceptions 之类的问题。
我们还能够使用仅 Java 的模块解密此数据,该模块似乎没有显示相同的垃圾字符。有没有办法只使用 Android classes 来做到这一点?
如果您 运行 Android 上的此代码,您将看到支持的密码:
TreeSet<String> ciphers = new TreeSet<>();
for (Provider provider : Security.getProviders())
for (Service service : provider.getServices())
if (service.getType().equals("Cipher"))
ciphers.add(service.getAlgorithm());
for (String cipher : ciphers)
System.out.println(cipher);
在 Windows 7 上 JDK 1.8.0_51,我得到:
AES
AESWrap
AESWrap_128
AESWrap_192
AESWrap_256
AES_128/CBC/NoPadding
AES_128/CFB/NoPadding
AES_128/ECB/NoPadding
AES_128/GCM/NoPadding
AES_128/OFB/NoPadding
AES_192/CBC/NoPadding
AES_192/CFB/NoPadding
AES_192/ECB/NoPadding
AES_192/GCM/NoPadding
AES_192/OFB/NoPadding
AES_256/CBC/NoPadding
AES_256/CFB/NoPadding
AES_256/ECB/NoPadding
AES_256/GCM/NoPadding
AES_256/OFB/NoPadding
ARCFOUR
Blowfish
DES
DESede
DESedeWrap
PBEWithHmacSHA1AndAES_128
PBEWithHmacSHA1AndAES_256
PBEWithHmacSHA224AndAES_128
PBEWithHmacSHA224AndAES_256
PBEWithHmacSHA256AndAES_128
PBEWithHmacSHA256AndAES_256
PBEWithHmacSHA384AndAES_128
PBEWithHmacSHA384AndAES_256
PBEWithHmacSHA512AndAES_128
PBEWithHmacSHA512AndAES_256
PBEWithMD5AndDES
PBEWithMD5AndTripleDES
PBEWithSHA1AndDESede
PBEWithSHA1AndRC2_128
PBEWithSHA1AndRC2_40
PBEWithSHA1AndRC4_128
PBEWithSHA1AndRC4_40
RC2
RSA
RSA/ECB/PKCS1Padding
Java 代码中也存在同样的垃圾。只是您可能 运行 在 Windows 上使用默认的拉丁 (ISO_8859_1
) 字符集,而 Android 默认使用 UTF-8。它还取决于用于打印字符的控制台和字体。在这种情况下,使用的填充可能不会在 Windows 控制台上打印,但会在 Android 代码上打印。
您需要查看字节数组(例如十六进制)以找出使用了哪个填充,然后在将明文转换为字符串之前将其删除。
@Maarten Bodewes 是正确的:垃圾字符也存在于我们的 Java-only 模块中。
问题是我们的服务器端代码在加密之前用零填充输入数据。这是为了使输入与 AES 所需的块大小相匹配。
这里有很好的讨论:Android - Removing padded bits in decryption
尽管我使用这个 "zero padding" 有点不自在,但是我写了一个实用程序来删除它:
public static String getStringAfterRemovingZeroPadding(byte[] input) {
if (input == null) {
return null;
}
int index = input.length - 1;
while (index >= 0) {
if (input[index] == 0) {
// We found some zero padding, look at the next character and see if it's also zero
// padding
--index;
} else {
// This character is not a zero padding, so go back to the zero padding that we
// just inspected, or go to the end of the string
++index;
break;
}
}
if (index < 0) {
return "";
}
return new String(input, 0, index);
}
...这是我的单元测试:
@Test
public void testRemoveZeroPaddingNull() throws Exception {
String result = StringUtils.getStringAfterRemovingZeroPadding(null);
assertThat(result).isNull();
}
@Test
public void testRemoveZeroPaddingAllZeros() throws Exception {
byte[] input = {0, 0};
String result = StringUtils.getStringAfterRemovingZeroPadding(input);
assertThat(result).isEqualTo("");
}
@Test
public void testRemoveZeroPaddingNoZeros() throws Exception {
byte[] input = {80, 80, 80};
String result = StringUtils.getStringAfterRemovingZeroPadding(input);
assertThat(result).isEqualTo("PPP");
}
@Test
public void testRemoveZeroPaddingOneZero() throws Exception {
byte[] input = {80, 80, 80, 0};
String result = StringUtils.getStringAfterRemovingZeroPadding(input);
assertThat(result).isEqualTo("PPP");
}
@Test
public void testRemoveZeroPaddingSomeZeros() throws Exception {
byte[] input = {80, 80, 80, 0, 0, 0, 0, 0};
String result = StringUtils.getStringAfterRemovingZeroPadding(input);
assertThat(result).isEqualTo("PPP");
}
我们需要编写一些 Android 代码来解密从我们的服务器发送的一些数据。我们的服务器团队给了我们一些示例解密代码,它使用 "SunJCE" 提供程序,不幸的是 Android 上不存在。
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE");
有人知道在 Android 上实现这个的最干净的方法吗?如果我们在 Android
上尝试这个Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
然后看起来在解密文本的末尾出现了一些不需要的垃圾,例如:
ComparisonFailure: expected:<...lAAAAABJRU5ErkJggg==[]> but was:<...lAAAAABJRU5ErkJggg==[��������]>
我们在 Android 密码 class 中尝试了很多不同转换的组合(例如 "AES/CBC/PKCS5Padding"),但一直遇到诸如 BadPaddingExceptions 之类的问题。
我们还能够使用仅 Java 的模块解密此数据,该模块似乎没有显示相同的垃圾字符。有没有办法只使用 Android classes 来做到这一点?
如果您 运行 Android 上的此代码,您将看到支持的密码:
TreeSet<String> ciphers = new TreeSet<>();
for (Provider provider : Security.getProviders())
for (Service service : provider.getServices())
if (service.getType().equals("Cipher"))
ciphers.add(service.getAlgorithm());
for (String cipher : ciphers)
System.out.println(cipher);
在 Windows 7 上 JDK 1.8.0_51,我得到:
AES
AESWrap
AESWrap_128
AESWrap_192
AESWrap_256
AES_128/CBC/NoPadding
AES_128/CFB/NoPadding
AES_128/ECB/NoPadding
AES_128/GCM/NoPadding
AES_128/OFB/NoPadding
AES_192/CBC/NoPadding
AES_192/CFB/NoPadding
AES_192/ECB/NoPadding
AES_192/GCM/NoPadding
AES_192/OFB/NoPadding
AES_256/CBC/NoPadding
AES_256/CFB/NoPadding
AES_256/ECB/NoPadding
AES_256/GCM/NoPadding
AES_256/OFB/NoPadding
ARCFOUR
Blowfish
DES
DESede
DESedeWrap
PBEWithHmacSHA1AndAES_128
PBEWithHmacSHA1AndAES_256
PBEWithHmacSHA224AndAES_128
PBEWithHmacSHA224AndAES_256
PBEWithHmacSHA256AndAES_128
PBEWithHmacSHA256AndAES_256
PBEWithHmacSHA384AndAES_128
PBEWithHmacSHA384AndAES_256
PBEWithHmacSHA512AndAES_128
PBEWithHmacSHA512AndAES_256
PBEWithMD5AndDES
PBEWithMD5AndTripleDES
PBEWithSHA1AndDESede
PBEWithSHA1AndRC2_128
PBEWithSHA1AndRC2_40
PBEWithSHA1AndRC4_128
PBEWithSHA1AndRC4_40
RC2
RSA
RSA/ECB/PKCS1Padding
Java 代码中也存在同样的垃圾。只是您可能 运行 在 Windows 上使用默认的拉丁 (ISO_8859_1
) 字符集,而 Android 默认使用 UTF-8。它还取决于用于打印字符的控制台和字体。在这种情况下,使用的填充可能不会在 Windows 控制台上打印,但会在 Android 代码上打印。
您需要查看字节数组(例如十六进制)以找出使用了哪个填充,然后在将明文转换为字符串之前将其删除。
@Maarten Bodewes 是正确的:垃圾字符也存在于我们的 Java-only 模块中。
问题是我们的服务器端代码在加密之前用零填充输入数据。这是为了使输入与 AES 所需的块大小相匹配。
这里有很好的讨论:Android - Removing padded bits in decryption
尽管我使用这个 "zero padding" 有点不自在,但是我写了一个实用程序来删除它:
public static String getStringAfterRemovingZeroPadding(byte[] input) {
if (input == null) {
return null;
}
int index = input.length - 1;
while (index >= 0) {
if (input[index] == 0) {
// We found some zero padding, look at the next character and see if it's also zero
// padding
--index;
} else {
// This character is not a zero padding, so go back to the zero padding that we
// just inspected, or go to the end of the string
++index;
break;
}
}
if (index < 0) {
return "";
}
return new String(input, 0, index);
}
...这是我的单元测试:
@Test
public void testRemoveZeroPaddingNull() throws Exception {
String result = StringUtils.getStringAfterRemovingZeroPadding(null);
assertThat(result).isNull();
}
@Test
public void testRemoveZeroPaddingAllZeros() throws Exception {
byte[] input = {0, 0};
String result = StringUtils.getStringAfterRemovingZeroPadding(input);
assertThat(result).isEqualTo("");
}
@Test
public void testRemoveZeroPaddingNoZeros() throws Exception {
byte[] input = {80, 80, 80};
String result = StringUtils.getStringAfterRemovingZeroPadding(input);
assertThat(result).isEqualTo("PPP");
}
@Test
public void testRemoveZeroPaddingOneZero() throws Exception {
byte[] input = {80, 80, 80, 0};
String result = StringUtils.getStringAfterRemovingZeroPadding(input);
assertThat(result).isEqualTo("PPP");
}
@Test
public void testRemoveZeroPaddingSomeZeros() throws Exception {
byte[] input = {80, 80, 80, 0, 0, 0, 0, 0};
String result = StringUtils.getStringAfterRemovingZeroPadding(input);
assertThat(result).isEqualTo("PPP");
}