Decrypt data using RSA between PHP and Java Androi

2019-03-22 17:36发布

问题:

I am using a PHP server to encrypt some data and then decrypt it to an Android device. But when I try to decrypt it on the Android device side, I get the following error :

javax.crypto.BadPaddingException: error:0407106B:rsa routines:RSA_padding_check_PKCS1_type_2:block type is not 02

When I am on

Cipher.getInstance("RSA/ECB/PKCS1Padding");

I am encrypting the value on a PHP server using PHPSeclips Library (last github version at this date) and signing as well. This part does actually works because already used to be decoded on a Javacard program so the error does not actually belongs here.

This is the way I proceed on Android side :

protected byte[] decryptData(String alias, byte[] data) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, BadPaddingException, IllegalBlockSizeException, UnsupportedOperationException {
        Log.i(TAG, "decryptData() Decrypt data " + HexStringConverter.byteArrayToHexString(data));
        byte[] decryptedData = null;
        PrivateKey privateKey = getPrivateKey(alias);
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        decryptedData = cipher.doFinal(data);
        Log.i(TAG, "decryptData() Decrypted data: " + HexStringConverter.byteArrayToHexString(decryptedData));
        return decryptedData;
    }

With the getPrivateKey() method :

protected RSAPrivateKey getPrivateKey(String alias) {
        try {
            KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
            ks.load(null);
            KeyStore.Entry entry = ks.getEntry(alias, null);
            if (!(entry instanceof KeyStore.PrivateKeyEntry)) {
                Log.w(TAG, "getPrivateKey() Not an instance of a PrivateKeyEntry");
            }
            return (RSAPrivateKey) ((KeyStore.PrivateKeyEntry) entry).getPrivateKey();
        } catch (NoSuchAlgorithmException e) {
            Log.w(TAG, "getPrivateKey() ", e);
        } catch (KeyStoreException e) {
            Log.w(TAG, "getPrivateKey() ", e);
        } catch (CertificateException e) {
            Log.w(TAG, "getPrivateKey() ", e);
        } catch (IOException e) {
            Log.w(TAG, "getPrivateKey() ", e);
        } catch (UnrecoverableEntryException e) {
            Log.w(TAG, "getPrivateKey() ", e);
        }
        return null;
    }

And how it's encrypted on PHP side :

//Function for encrypting with RSA
function rsa_encrypt($string, $key)
{
    require_once(__DIR__ . '/../phpseclib/Crypt/RSA.php');
    //Create an instance of the RSA cypher and load the key into it
    $cipher = new Crypt_RSA();
    $cipher->loadKey($key);
    //Set the encryption mode
    $cipher->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
    //Return the encrypted version
    return $cipher->encrypt($string);
}

//Function for decrypting with RSA 
function rsa_sign($string, $key)
{
    require_once(__DIR__ . '/../phpseclib/Crypt/RSA.php');
    //Create an instance of the RSA cypher and load the key into it
    $cipher = new Crypt_RSA();
    $cipher->loadKey($key);
    //Set the signature mode
    $cipher->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);

    //Return the signed message
    return $cipher->sign($string);
}

UPDATE (7/3/14) For those who get the same mistakes I recommand you look at the following page : http://hustoknow.blogspot.ca/2013/01/rsa-block-type-is-not-02-error.html

In fact this put me on the right way to discover what the issue was.

The error raised tells that the decryption key does not match the key used to encrypt it, so I've watched every Public Key used for this exchange. Because I used 1024 key size I was parsing the input message (modulus + public exponent) with relevant size of each. And I noticed that the modulus was not fully received compared to the one displayed on the Android device. So here is the mistake, Android by default use 2048 key size when you use KeyPairGenerator object for key generation. Just set manually the key size to 1024 fix this issue.

Example:

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
            Calendar notBefore = Calendar.getInstance();
            Calendar notAfter = Calendar.getInstance();
            notAfter.add(Calendar.YEAR, 1);
            KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(mContext)
                    .setAlias(alias)
                    .setKeySize(1024)
                    .setSubject(
                            new X500Principal(String.format("CN=%s, OU=%s", alias,
                                    mContext.getPackageName())))
                    .setSerialNumber(BigInteger.ONE).setStartDate(notBefore.getTime())
                    .setEndDate(notAfter.getTime()).build();

            keyPairGenerator.initialize(spec);
            KeyPair kp = keyPairGenerator.generateKeyPair();

Hope this help.

回答1:

You might need to do define('CRYPT_RSA_PKCS15_COMPAT', true).

The following comment block elaborates:

https://github.com/phpseclib/phpseclib/blob/a8c2ff0fb013169193c649adab512cafef5068cf/phpseclib/Crypt/RSA.php#L2272

Basically, OpenSSL (and quite potentially Java too) implements PKCS#1 v1.5 whereas phpseclib implements PKCS#1 v2.1. PKCS#1 v2.1 modifies PKCS1 style encryption to make use of randomized padding so no two ciphertext's will ever be the same.