非对称加密差异 - Android与Java

Coo*_*ter 6 java encryption android rsa

我最近开始编写一个用Java编写的在线游戏的Android版本.但是,我遇到了与加密不一致的问题.Java应用程序工作正常 - 它从文件读取公钥,加密一些文本并将其传递到服务器,在那里使用私钥正确解密.在android上,一切似乎都有效(并且正在运行相同的代码),但是服务器有一个BadPaddingException试图解密消息.我已经在下面列出了所有相关代码和一步一步的事件序列:

连接到服务器时发生的第一件事是对称密钥的协议.这是在客户端上生成的,因此:

SecretKey symmetricKey = null;
try
{
    KeyGenerator keyGen = KeyGenerator.getInstance("AES");
    symmetricKey = keyGen.generateKey();
}
catch (Throwable t)
{
    Debug.stackTrace(t, "Failed to generate symmetric key.");
}

return symmetricKey;
Run Code Online (Sandbox Code Playgroud)

然后将其转换为Base64字符串:

byte[] keyBytes = secretKey.getEncoded();
return base64Interface.encode(keyBytes);
Run Code Online (Sandbox Code Playgroud)

并使用公钥加密:

public static String encrypt(String messageString, Key key)
{
    String encryptedString = null;
    try
    {
        byte[] messageBytes = messageString.getBytes();
        String algorithm = key.getAlgorithm()
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] cipherData = cipher.doFinal(messageBytes);
        encryptedString = base64Interface.encode(cipherData);

        //Strip out any newline characters
        encryptedString = encryptedString.replaceAll("\n", "");
        encryptedString = encryptedString.replaceAll("\r", "");
    }
    catch (Throwable t)
    {
        Debug.append("Caught " + t + " trying to encrypt message: " + messageString);
    }

    return encryptedString;
}
Run Code Online (Sandbox Code Playgroud)

在这种形式中,它被传递到服务器,该服务器使用私钥来解密消息并恢复SecretKey对象:

public static String decrypt(String encryptedMessage, Key key)
{
    String messageString = null;
    try
    {
        byte[] cipherData = base64Interface.decode(encryptedMessage);
        String algorithm = key.getAlgorithm();
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.DECRYPT_MODE, key);
        byte[] messageBytes = cipher.doFinal(cipherData);
        messageString = new String(messageBytes);
    }
    catch (Throwable t)
    {
        Debug.append("Caught " + t + " trying to decrypt message: " + encryptedMessage, failedDecryptionLogging);
    }

    return messageString;
}
Run Code Online (Sandbox Code Playgroud)

但是,每当我对从Android应用程序传递的消息执行此操作时,doFinal行会产生以下异常:

11/07 12:55:55.975   javax.crypto.BadPaddingException: Decryption error
    at sun.security.rsa.RSAPadding.unpadV15(Unknown Source)
    at sun.security.rsa.RSAPadding.unpad(Unknown Source)
    at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:354)
    at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:380)
    at javax.crypto.Cipher.doFinal(Cipher.java:2121)
    at util.EncryptionUtil.decrypt(EncryptionUtil.java:85)
    at server.MessageHandlerRunnable.handleUnencryptedMessage(MessageHandlerRunnable.java:226)
    at server.MessageHandlerRunnable.getResponse(MessageHandlerRunnable.java:188)
    at server.MessageHandlerRunnable.run(MessageHandlerRunnable.java:85)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
Run Code Online (Sandbox Code Playgroud)

我的第一个想法是问题必须在Base64编码/解码中,因为这是Android和桌面版本之间不同的代码.但是,我已经做了一些测试并验证了这些是一致的,并且我的服务器代码可以使用任一编码方法恢复原始文本.

我的下一个想法是,Android版本必须以某种方式使用错误的公钥.这是在使用以下代码从两个平台通用的文件启动时生成的:

public static void generatePublicKey()
{
    InputStream in = null;
    ObjectInputStream oin = null;

    try
    {
        in = KeyGeneratorUtil.class.getResourceAsStream("/assets/public.key");
        oin = new ObjectInputStream(new BufferedInputStream(in));

        BigInteger m = (BigInteger) oin.readObject();
        BigInteger e = (BigInteger) oin.readObject();
        RSAPublicKeySpec keySpec = new RSAPublicKeySpec(m, e);
        KeyFactory fact = KeyFactory.getInstance("RSA");
        MessageUtil.publicKey = fact.generatePublic(keySpec);
    } 
    catch (Throwable e) 
    {
        Debug.stackTrace(e, "Unable to read public key - won't be able to communicate with Server.");
    } 
    finally
    {
        if (in != null)
        {
            try {in.close();} catch (Throwable t) {}
        }

        if (oin != null)
        {
            try {oin.close();} catch (Throwable t) {}
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

当我查看两个平台上的键(使用toString())时,我看到以下内容(我已经截断了模数):

桌面:

Sun RSA公钥,1024位模数:11920225567195913955197820411061866681846853580 ...公共指数:65537

安卓:

OpenSSLRSAPublicKey {modulus = a9bfe8d8a199fc6a ...,publicExponent = 10001}

乍一看,这些看起来完全不同,但我现在确信它们与十进制(桌面)和另一个十六进制(Android)相同.十六进制中的10001相当于十进制中的65537,并且将十六进制模数放入在线转换器中会产生一个至少以十进制模数的正确数字开头的数字.那么,为什么我看到BadPaddingException呢?

最后值得注意的是,这似乎与一年前在这个问题中提出的问题相同:

Android上的RSA与PC不同

但是,没有提出任何解决方案,我认为值得提出一个新问题,我可以提供我可以获得的所有信息.

i_t*_*uro 3

BadPaddingException通常由以下原因之一引起:

  1. 传递给解密函数的密文不等于从加密函数接收到的密文(只要有一点差异就会完全破坏该过程)。

  2. 使用不同的密钥(或不匹配的私钥)来解密数据。

  3. A该程序尝试使用scheme取消填充用scheme 填充的消息B。

由于您已经验证 1. 和 2. 是正确的,因此 3. 很可能是问题的根源。

您在两端使用相同的编程语言的相同代码,那么为什么会出现任何不兼容性呢?罪魁祸首是以下两行代码:

String algorithm = key.getAlgorithm();
Cipher cipher = Cipher.getInstance(algorithm);
Run Code Online (Sandbox Code Playgroud)

key.getAlgorithm()由于密钥可以与任何填充方案结合使用,因此它仅保存其所属的算法,这具有返回 的效果"RSA"。调用Cipher.getInstance("RSA")通常不是一个好主意,因为 Java 会自动为cipher-mode和padding选择依赖于平台的默认值。

这可以通过传递完整的密码字符串 () 来避免,"<algorithm>/<mode>/<padding>"例如"RSA/ECB/PKCS1Padding". Cipher使用 JCE 库时,对所有实例执行此操作始终是一个好主意。