java - 在android上解密php加密数据

标签 java php android security encryption

Android 客户端 (4.2.1) 应用程序通过 HttpPost 请求将公钥发送到 PHP (5.6) API。此 API 使用符合 AESRIJNDAEL_128 对数据进行加密,然后使用具有 OpenSSL 公共(public)加密和 RSA_PKCS1_OAEP_PADDING 的客户端公钥对 AES 加密的 key 进行加密。它将通过 base64 编码的数据 XML 发送回客户端 android 应用程序,该应用程序将对数据进行加密。我已经设置了一个基本的 PHP 测试脚本来测试整个过程,这按预期工作。

目前我正致力于在客户端 Android 应用程序中实现解密,但已经解密 AES key 失败。除了这个当前的问题,我还有其他问题(见文末)。

这是正在发生的事情的文本图形概要:

client -> public key -> API -> data -> AESencrypt(data), RSAencrypt(AES-key) -> base64encode[AES(data)], base64encode[RSA(AES-key)] -> <xml>base64[AES(data)], base64[RSA(AES-key)]</xml> -> client -> base64[AES(data)], base64[RSA(AES-key)] -> base64decode[AES(data)], base64decode[RSA(AES-key)] -> AESdecrypt(data), RSAdecrypt(AES-key) -> data

我正在使用 MCRYPT_RIJNDAEL_128 加密数据,我读到它与 AES 兼容(请参阅 PHP doc for mycrypt )。
这是代码:

<?php
$randomBytes = openssl_random_pseudo_bytes(32, $safe);
$randomKey = bin2hex($randomBytes);
$randomKeyPacked = pack('H*', $randomKey);
// test with fixed key:
// $randomKeyPacked = "12345678901234567890123456789012";
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$dataCrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $randomKeyPacked, $data, MCRYPT_MODE_CBC, $iv);

由此产生的 AES key 使用 openssl_public_encrypt 和填充设置 OPENSSL_PKCS1_OAEP_PADDING 进行编码。阅读源代码( source of PHP OpenSSL implementation )这相当于 RSA_PKCS1_OAEP_PADDING 描述为

EME-OAEP as defined in PKCS #1 v2.0 with SHA-1, MGF1 and an empty encoding parameter.



在 OpenSSL 文档中找到 here 。之后我 base64_encode 数据能够通过 XML 字符串传输到客户端。代码如下所示:

openssl_public_encrypt($randomKeyPacked, $cryptionKeyCrypted, $clientPublicKey, OPENSSL_PKCS1_OAEP_PADDING);
$content = array(
    'cryptionKeyCryptedBase64' => base64_encode($cryptionKeyCrypted),
    'cryptionIVBase64' => base64_encode($iv),
    'dataCryptedBase64' => base64_encode($dataCrypted)
);
// $content gets parsed to a valid xml element here

客户端 Android 应用程序通过 HttpPost 请求通过 BasicResponseHandler 获取返回数据。这个返回的 XML 字符串是有效的,并通过 Simple 解析为相应的 java 对象。在保存传输数据的实际内容的类中,我目前尝试解密数据。我使用转换 RSA/ECB/OAEPWithSHA-1AndMGF1Padding 解密 AES key ,由于 this site(只有我能找到)是一个有效的字符串,并且似乎与我在 PHP 中使用的填充等效。我包括了生成私钥的方式,因为它与生成发送到 PHP API 的公钥的方式相同。这是那个类:

public class Content {

    @Element
    private String cryptionKeyCryptedBase64;

    @Element
    private String cryptionIVBase64;

    @Element
    private String dataCryptedBase64;

    @SuppressLint("TrulyRandom")
    public String getData() {
        String dataDecrypted = null;
        try {
            PRNGFixes.apply(); // fix TrulyRandom
            KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
            keygen.initialize(2048);
            KeyPair keypair = keygen.generateKeyPair();
            PrivateKey privateKey = keypair.getPrivate();

            byte[] cryptionKeyCrypted = Base64.decode(cryptionKeyCryptedBase64, Base64.DEFAULT);
            //byte[] cryptionIV = Base64.decode(cryptionIVBase64, Base64.DEFAULT);

            Cipher cipherRSA = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipherRSA.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] key = cipherRSA.doFinal(cryptionKeyCrypted);

            byte[] dataCrytped = Base64.decode(dataCryptedBase64, Base64.DEFAULT);
            SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
            Cipher cipherAES = Cipher.getInstance("AES");
            cipherAES.init(Cipher.DECRYPT_MODE, skeySpec);
            byte[] decryptedAESBytes = cipherAES.doFinal(dataCrytped);
            dataDecrypted = new String(decryptedAESBytes, "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dataDecrypted;
    }
}

这样做我目前在线失败
byte[] key = cipherRSA.doFinal(cryptionKeyCrypted);
Bad padding exceptions 用于几乎所有 PHP openssl_public_encrypt 填充参数 - 我尝试过的 Android 密码转换字符串组合。通过省略默认为 OPENSSL_PKCS1_PADDING 的 openssl_public_encrypt 中的 padding 参数和仅 Cipher.getInstance("RSA") 的密码转换字符串来使用标准 PHP 填充参数,我没有得到错误的填充异常。但是加密 key 似乎无效,因为 AES 解密失败
java.security.InvalidKeyException: Key length not 128/192/256 bits.

我尝试使用固定 key 验证它(请参阅上面 PHP 代码中的代码注释),但在解密并将其转换为字符串后,我没有取回相同的 key 。如果我正确读取 Eclipse ADT 调试器,它似乎只是乱码数据,尽管它是 256 位长。

什么可能是正确的 Cipher 转换字符串以用作 PHP 的 OPENSSL_PKCS1_OAEP_PADDING 等价物。读取 this documentation 我需要 "algorithm/mode/padding" 形式的转换字符串,我猜算法 = RSA 但我无法找到如何将 OpenSSL(以上)文档中关于填充的说明转换为有效的密码转换字符串。 IE。例如,mode 是什么?
不幸的是,这个 Android RSA decryption (fails) / server-side encryption (openssl_public_encrypt) 接受的答案并没有解决我的问题。

无论如何这可以解决我的问题还是我的问题起源于其他地方?

我将如何进一步调试?将 base64 解码、解密的 key 转换为人类可读形式以便我可以将其与用于加密的 key 进行比较的正确方法是什么?
我试过:
String keyString =  new String(keyBytes, "UTF-8");

但这不会返回任何人类可读的文本,因此我假设 key 错误或我的转换方法错误。

同样在 PHP 中解密 AES 加密数据,解密函数 mcrypt_decrypt 中需要 IV。正如您在我发送的代码中看到的那样,但在 Android 中似乎不需要这样做?为什么这样?

PS:我希望我提供了所有需要的信息,我可以在评论中进一步补充。

PPS:为了完整起见,这里是发出 HttpPost 请求的 Android 客户端代码:
@SuppressLint("TrulyRandom")
protected String doInBackground(URI... urls) {
    try {
        System.setProperty("jsse.enableSNIExtension", "false");
        HttpClient httpClient = createHttpClient();
        HttpPost httpPost = new HttpPost(urls[0]);

        PRNGFixes.apply(); // fix TrulyRandom
        KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
        keygen.initialize(2048);
        KeyPair keypair = keygen.generateKeyPair();
        PublicKey publickey = keypair.getPublic();
        byte[] publicKeyBytes = publickey.getEncoded();
        String pubkeystr = "-----BEGIN PUBLIC KEY-----\n"+Base64.encodeToString(publicKeyBytes,
                Base64.DEFAULT)+"-----END PUBLIC KEY-----";

        List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
        nameValuePairs.add(new BasicNameValuePair("publickey", pubkeystr));
        httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));

        // Execute HTTP Post Request
        HttpResponse response = httpClient.execute(httpPost);
        return new BasicResponseHandler().handleResponse(response);
    } catch (Exception e) {
        Toast toast = Toast.makeText(asyncResult.getContext(),
                "unknown exception occured: " + e.getMessage(),
                Toast.LENGTH_SHORT);
        toast.show();
        return "error";
    }
}

最佳答案

您正在 doInBackground 中生成一个 RSA key 对,并告诉主机使用该 key 对的公共(public)部分来加密 DEK(数据加密 key )。然后,您将在 getData 中生成一个完全不同的 RSA key 对,并尝试使用该 key 对的私有(private)部分来解密加密的 DEK。公钥加密的工作方式是使用 key 对的公共(public)部分进行加密,并使用 相同 key 对 的私有(private)部分进行解密;公共(public)和私有(private)的一半是 数学上相关的 。您需要至少保存和使用您发送的公共(public)部分的 key 对的私有(private)部分(可选地,具有两部分的 key 对)。

正确获得 DEK 后,为了解密 CBC 模式数据, 是的,您确实需要使用与用于加密相同的 IV 来解密 。您的接收者需要将其放入 IvParameterSpec 并在 Cipher.init(direction,key[,params]) 调用中传递它。或者,如果您可以更改 PHP,因为您为每条消息使用新的 DEK,使用固定的 IV 是安全的;最简单的方法是使用 '\0'x16 加密并允许 Java 解密默认为全零。

此外,您需要使用参数 Base64.NO_WRAP 设置 Base64.decode 因为 PHP 只会输出由 \0 分隔的 base64。为此,您还需要使用 "AES/CBC/ZeroBytePadding" 转换密码来解密 AES 数据,因为 PHP 函数 mycrypt_encrypt 将用零填充数据。
下面是 getData 函数的样子:

public String getData() {
    String dataDecrypted = null;
    try {
        byte[] cryptionKeyCrypted = Base64.decode(cryptionKeyCryptedBase64, Base64.NO_WRAP);
        byte[] cryptionIV = Base64.decode(cryptionIVBase64, Base64.NO_WRAP);

        Cipher cipherRSA = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
        // get private key from the pair used to grab the public key to send to the api
        cipherRSA.init(Cipher.DECRYPT_MODE, rsaKeyPair.getPrivateKey());
        byte[] key = cipherRSA.doFinal(cryptionKeyCrypted);

        byte[] dataCrytped = Base64.decode(dataCryptedBase64, Base64.NO_WRAP);
        IvParameterSpec ivSpec = new IvParameterSpec(cryptionIV);
        SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
        Cipher cipherAES = Cipher.getInstance("AES/CBC/ZeroBytePadding");
        cipherAES.init(Cipher.DECRYPT_MODE, skeySpec, ivSpec);
        byte[] decryptedAESBytes = cipherAES.doFinal(dataCrytped);
        dataDecrypted = new String(decryptedAESBytes, "UTF-8");
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return dataDecrypted;
}

关于java - 在android上解密php加密数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26068460/

相关文章:

android - 如何为 Android 市场应用程序强制执行加速度计?

java - 从Java,Android调用Kotlin?

php - 无法插入PHP到MYSQL数据库

java - Android 教程应用程序无法运行

javascript - 变量未发布在 keyup 上

php - 如何在新行中打印数据库中的每条记录?

无法重新分配 Android val

java - 求素数-->筛道

java - RCPTT 从脚本运行 Java 代码

Java:类型标记中的通用类型