java.lang.ArrayIndexOutOfBoundsException:RSA 块的数据过多

java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block

我正在使用 RSA 加密文本和解密文本。 public 密钥和私钥是使用 openssl 工具生成的。 解密数据时遇到"java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block"异常

这是 RSA 工具 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);
    }
}

以及加密和解密数据的片段:

 //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);

我知道原因可能是要解密的文本太长,但我只是加密"abc"然后解密加密的"abc"。如果要加密或解密的文本应比 rsa 私钥少 11 个字节,如何处理加密长文本?我该怎么做才能解决它?我是 RSA 新手。

提前致谢!

通常,您会为对称密码(如 AES)生成一个随机密钥,并使用它来加密您的有效负载。

RSA 然后仅用于加密此随机密钥。这不仅解决了长度问题,还有其他一些优点:

  • 对称密码通常要快得多
  • 如果消息发送给多个接收者,只需要为每个接收者分别添加加密密钥,主要内容可以相同。

您的代码中缺少一些步骤,因此无法检查。但是,有一些线索表明存在问题。您的 decryptData 方法接受一个 String 参数,然后调用 String.getBytes() 来获取数据,然后将其解密。然而,加密的结果是一个字节序列,它不是任何有效字符串的编码。也许您打算对输入进行 base64 解码而不是调用 getBytes()。一般来说,要执行解密和解码,您必须反转在加密和编码期间执行的步骤。所以,如果明文是 byte[] 那么步骤是:

字节[]→加密→字节[]→Base64编码→字符串。

然后,在以 Base64 字符串开头的解密方向上,您必须按顺序:

字符串→Base64解码→字节[]→解密→字节[]

此外,另一个不良做法和许多可移植性错误的根源是默认值的使用。您在两个地方使用默认值,它们都很麻烦。首先,您使用默认的无参数 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 进行进一步讨论。

关于如何加密较长的文本,请参阅

最后我像那样修改了我的代码并且它们运行良好:

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

如果还有什么不对的地方可以提醒我。感谢 James 和 Henry 的回答。