在Android上解密php加密数据 [英] decrypting php encrypted data on android

查看:77
本文介绍了在Android上解密php加密数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

An Android client (4.2.1) application sends a public key via a HttpPost request to a PHP (5.6) API. This API encrypts the data with AES compliant RIJNDAEL_128, then encrypts the key for the AES encryption with the client public key with OpenSSL public encryption and RSA_PKCS1_OAEP_PADDING. It sends this data base64 encoded via XML back to the client android application which shall encrypt the data. I've setup a basic PHP test script which tests the whole process, this works as expected.

目前,我正在努力在客户端Android应用程序中实施解密,但已经无法解密AES密钥.除了当前的问题之外,我还有其他问题(请参阅最后).

Currently I'm working on implementing the decryption in the client Android application but already decrypting the AES-key fails. I have other questions besides this current problem (see at the end).

下面是发生的事情的图形图形摘要:

Here is a text graphical synopsis of what is happening:

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

我正在使用读取的与AES兼容的MCRYPT_RIJNDAEL_128加密数据(请参见>用于mycrypt的PHP doc ). 这是代码:

I'm encrypting the data with MCRYPT_RIJNDAEL_128 which I read is AES compatible (see PHP doc for mycrypt). Here is the code:

<?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密钥使用 openssl_public_encrypt 和填充设置OPENSSL_PKCS1_OAEP_PADDING.读取源代码( PHP OpenSSL实现源)等同于RSA_PKCS1_OAEP_PADDING,描述为

The AES-key coming out of this is encoded with openssl_public_encrypt and the padding setting OPENSSL_PKCS1_OAEP_PADDING. Reading the source code (source of PHP OpenSSL implementation) this is equivalent to RSA_PKCS1_OAEP_PADDING described as

在PKCS#1 v2.0中定义的EMC-OAEP,带有SHA-1,MGF1和空的编码参数.

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

在OpenSSL文档中

此处中找到.之后,我 base64_encode 能够通过XML传输数据的数据字符串到客户端.代码如下:

in the OpenSSL documentation found here. Afterwards I base64_encode the data to be able to transfer it via an XML string to the client. The code looks like this:

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应用程序通过BasicResponseHandler通过HttpPost请求获取返回数据.此返回的XML字符串有效,并通过 Simple 解析到相应的Java对象.目前,在保存已传输数据实际内容的类中,我尝试对数据进行解密.由于

The client Android application gets the return data via HttpPost request via a BasicResponseHandler. This returned XML string is valid and parsed via Simple to respective java objects. In the the class holding the actual content of the transferred data I currently try to decrypt the data. I decrypt the AES-key with the transformation RSA/ECB/OAEPWithSHA-1AndMGF1Padding which due to this site (only I could find) is a valid string and seems to be the equivalent of the padding I used in PHP. I included the way I generated the private key as it is the same way I generate the public key that was send to the PHP API. Here is that class:

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;
    }
}

执行此操作目前在行上失败

Doing this I currently fail at line

byte[] key = cipherRSA.doFinal(cryptionKeyCrypted);

Bad padding exceptions几乎适用于所有PHP openssl_public_encrypt填充参数-我尝试过的Android Cipher转换字符串组合.通过省略默认为OPENSSL_PKCS1_PADDING的openssl_public_encrypt中的padding参数和仅Cipher.getInstance("RSA")的Cipher转换字符串来使用标准的PHP填充参数,我不会遇到糟糕的填充异常.但是加密密钥似乎无效,因为AES解密失败

with Bad padding exceptions for nearly all PHP openssl_public_encrypt padding parameter - Android Cipher transformation string combinations I tried. Using the standard PHP padding parameter by omitting the padding parameter in the openssl_public_encrypt which defaults to OPENSSL_PKCS1_PADDING and a Cipher transformation string of just Cipher.getInstance("RSA") I do not get a bad padding exception. But the encrypted key seems not to be valid as AES decryption fails with

java.security.InvalidKeyException: Key length not 128/192/256 bits.

我尝试使用固定密钥对此进行验证(请参见上面的PHP代码中的代码注释),在将其解密并将其转换为字符串后,我没有得到相同的密钥.如果我正确阅读Eclipse ADT调试器,虽然它的长度为256位,但看起来只是乱码.

I tried validating this with a fixed key (see code comment in PHP code above) and I don't get the same key back after decrypting it and transforming it to a string. It seems it is just garbled data although it is 256 bits long if I read the Eclipse ADT debugger correctly.

什么可能是与PHP OPENSSL_PKCS1_OAEP_PADDING等效的正确密码转换字符串.阅读本文档我需要格式为"algorithm/mode/padding"的转换字符串,我猜想算法= RSA,但是我找不到如何将上述OpenSSL文档中关于填充的状态转换为有效的密码转换字符串的方法. IE.例如mode是什么? 不幸的是,这 Android RSA解密(失败)/服务器端加密(openssl_public_encrypt)接受的答案无法解决我的问题.

What might be the correct Cipher transformation string to use as an equivalent for PHP's OPENSSL_PKCS1_OAEP_PADDING. Reading this documentation I need the transformation string in the form "algorithm/mode/padding", I guessed that algorithm = RSA but I couldn't find out how to translate what the OpenSSL (above) documentation states about the padding into a valid cipher transformation string. I.e. what is mode for example? Unfortunately this Android RSA decryption (fails) / server-side encryption (openssl_public_encrypt) accepted answer did not solve my problem.

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

Anyway might this solve my problem or does my problem originate elsewhere?

我将如何进一步调试它?将base64解码,解密的密钥转换为人类可读形式的正确方法是什么,以便我可以将其与用于加密的密钥进行比较? 我尝试过:

How would I further debug this? What is the correct way to transform the base64 decoded, decrypted key into a human readable form so I can compare it with the key used to encrypt? I tried with:

String keyString =  new String(keyBytes, "UTF-8");

但是这不会给人类可读的文本,因此我认为密钥是错误的,或者是我转换密钥的方法.

But this doesn't give any human readable text back so I assume either the key is wrong or my method of transforming it.

解密功能 mcrypt_decrypt .如您在代码中看到的,我发送了它,但是在Android中似乎不需要它了吗?为什么这样?

Also decrypting the AES encrypted data in PHP the IV is needed in the decryption function mcrypt_decrypt. As you can see in the code I send it but it seems in Android this is not needed? Why so?

PS:希望我提供了所有必要的信息,我可以在评论中进一步添加.

PS: I hope I provided all needed information, I can add further in the comments.

PPS:为完整起见,这是发出HttpPost请求的Android客户端代码:

PPS: For completeness here is the Android client code making the HttpPost request:

@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密钥对,并告诉主机使用该密钥对的公共部分对DEK(数据加密密钥)进行加密.然后,您将在getData中生成一个完全不同的RSA密钥对,并尝试使用该密钥对的私有部分来解密加密的DEK.公钥加密的工作方式是,您使用密钥对的公共部分进行加密,并使用相同密钥对的私有部分进行解密;公共和私人两半是与数学有关的.您需要至少保存和使用要发送其公开一半的密钥对的私密部分(可选地是两半的密钥对).

You are generating one RSA keypair in doInBackground and telling the host to use the public half of that keypair to encrypt the DEK (data encryption key). You are then generating a completely different RSA keypair in getData and attempting to use the private half of that keypair to decrypt the encrypted DEK. The way public-key encryption works is you encrypt with the public half of a keypair and decrypt with the private half of the same keypair; the public and private halves are mathematically related. You need to save and use at least the private half of the keypair (optionally the keypair with both halves) whose public half you send.

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

Once you've got the DEK correctly, in order to decrypt CBC-mode data, yes you do need to use the same IV for decryption as was used for encryption. Your receiver needs to put it in an IvParameterSpec and pass that on the Cipher.init(direction,key[,params]) call. Alternatively if you can change the PHP, since you are using a new DEK for each message it is safe to use a fixed IV; easiest is to encrypt with '\0'x16 and allow the Java decrypt to default to all-zero.

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

Additionally you need to set Base64.decode with the parameter Base64.NO_WRAPas PHP will just put out the base64 delimited by \0. And to that you will also need to use the "AES/CBC/ZeroBytePadding" transformation cipher to decrypt the AES data as the PHP function mycrypt_encrypt will pad the data with zeros. Here is what the getData function will have to look like:

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;
}

这篇关于在Android上解密php加密数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆