正在 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");
}