java.lang.ArrayIndexOutOfBoundsException: too much

2019-02-11 07:47发布

问题:

I'm using RSA encrypt text and decrypt text. The public key and the private key are generated with openssl tool. I encountered an "java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block" exception when decrypting data.

Here is the RSA util class:

package studio.uphie.app;

import android.util.Base64;

import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.Cipher;

/**
 * Created by Uphie on 2016/4/11.
 */
public class RSA {

    private static String RSA = "RSA";

    /**
     *
     * @param text    text to be encrypted
     * @param pub_key rsa public key
     * @return encrypted data in byte-array form
     */
    public static byte[] encryptData(String text, String pub_key) {
        try {
            byte[] data = text.getBytes();
            PublicKey publicKey = getPublicKey(Base64.decode(pub_key.getBytes(), Base64.DEFAULT));

            Cipher cipher = Cipher.getInstance(RSA);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return cipher.doFinal(data);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     *
     * @param text    text to be decrypted
     * @param pri_key rsa private key
     * @return
     */
    public static byte[] decryptData(String text, String pri_key) {
        try {
            byte[] data = text.getBytes();
            PrivateKey privateKey = getPrivateKey(Base64.decode(pri_key.getBytes(),Base64.DEFAULT));

            Cipher cipher = Cipher.getInstance(RSA);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return cipher.doFinal(data);
        } catch (Exception e) {
            //"java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block" exception occurs here.
            return null;
        }
    }

    /**
     *
     * @param keyBytes
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static PublicKey getPublicKey(byte[] keyBytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        return keyFactory.generatePublic(keySpec);
    }

    /**
     *
     * @param keyBytes
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static PrivateKey getPrivateKey(byte[] keyBytes) throws NoSuchAlgorithmException,
            InvalidKeySpecException {
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        return keyFactory.generatePrivate(keySpec);
    }
}

And the snippet that encrypts and decrypts data:

 //encrypt
 byte[] e = RSA.encryptData(text, PUBLIC_KEY);
 String result = Base64.encodeToString(e, Base64.DEFAULT);
 tv_encrypted.setText(result);

 //decrypt
 byte[] d = RSA.decryptData(text, PRIVATE_KEY);
 String result = Base64.encodeToString(d, Base64.DEFAULT);
 tv_decrypted.setText("Decrypted result:\n" + result);

I know the reason may be that the text to be decrypted is too long , but I just encrypt "abc" and then decrypt the encrypted "abc". And how to handle encrypting long text if the text to be encrypted or decrypted should be 11 bytes less than the rsa private key? How can I do to solve it? I'm new to RSA.

Thanks in advance!

回答1:

You are missing some steps in your code which makes it impossible to check. However, there are a few clues to suggest a problem. Your decryptData method takes a String argument and then calls String.getBytes() to get the data which is then decrypted. However, the result of encryption is a sequence of bytes which is not the encoding of any valid String. Perhaps you meant to base64 decode the input instead of calling getBytes(). In general to perform decryption and decoding you must reverse the steps you performed during encryption and encoding. So, if the plaintext is a byte[] then the steps are:

byte [] → Encrypt → byte [] → Base64 encode → String.

then, in the decrypt direction you start with a Base64 string, you must, in order:

String → Base64 decode → byte [] → decrypt → byte []

Also, another issue which is bad practice and a source of many portability bugs is the use of defaults. You are using defaults in two places and they're both troublesome. First you are using the default no-args String.getBytes() method, and presumably matching that up with the one-arg String (byte []) constructor. This use the platform default character set, but this can differ on different platforms. Therefore always specify a character set. For most applications 'UTF-8' is an ideal choice. Secondly, you are calling Cipher.getInstance('RSA') without specifying padding. Oracle's Java and Android's Java will give you different padding and thus your code will not be portable between the platforms. Always specify the complete padding string. Here the choice is little more difficult if you need portability to older Java implementations. OAEP padding should be your first choice, so Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); is probably the right choice. See this for further discussion.

As for how to encrypt longer texts, see the answer from Henry.



回答2:

Fianlly I modified my codes like that and they work well:

    public static String encryptData(String text, String pub_key) {
        try {
            byte[] data = text.getBytes("utf-8");
            PublicKey publicKey = getPublicKey(Base64.decode(pub_key.getBytes("utf-8"), Base64.DEFAULT));
            Cipher cipher = Cipher.getInstance(RSA);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return Base64.encodeToString(cipher.doFinal(data),Base64.DEFAULT);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    public static String decryptData(String text, String pri_key) {
        try {
            byte[] data =Base64.decode(text,Base64.DEFAULT);
            PrivateKey privateKey = getPrivateKey(Base64.decode(pri_key.getBytes("utf-8"),Base64.DEFAULT));

            Cipher cipher = Cipher.getInstance(RSA);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return new String(cipher.doFinal(data),"utf-8");
        } catch (Exception e) {
            return null;
        }
    }

If something seems wrong still you can remind me. Thanks for James and Henry's answer.



回答3:

Usually, you generate a random secret key for a symmetric cipher (like AES) and use this to encrypt your pay load.

RSA is then only used to encrypt this random key. This does not only solve the length problem but has some other advantages as well:

  • Symmetric cyphers are usually much faster
  • If the message is sent to several recievers, only the encrypted key has to be added specifically for each receiver, the main content can be the same.


标签: android rsa