简体   繁体   中英

javax.crypto.BadPaddingException during RSA Decryption

In my Java code, I'm trying to encrypt a String using RSA, with a public key. The String is a Base64 encoded String that represents an Image (Image was converted to String). It will be decrypted using a private key.

During the Encryption, I first got an exception "javax.crypto.IllegalBlockSizeException: Data must not be longer than 190 bytes". So, I processed the String (plaintext) in blocks of 189 which then resolved it.

During the Decryption, I got another exception "javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes". So, I processed the byte[] (ciphertext), by converting it to a String first, in blocks of 256 which then resolved it as well.

Again, during my decryption process, I end up getting a "javax.crypto.BadPaddingException: Decryption error" Exception, which I have been unable to resolve.

Upon the recommendation of experts on this site, I used "OAEPWithSHA-256AndMGF1Padding". I even tried using No Padding, after other padding methods, to see if the Exception would go away, but it did not work. What have I done wrong?

I was able to identify that the Exception was thrown at the line - decryptedImagePartial = t.rsaDecrypt(cipherTextTrimmed.getBytes(), privateKey); - which is in the decryption portion of the main method.

Please bear with me if my coding practices are poor. I'd really prefer to just find out the error behind the exception for now.

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;

public class Tester
{

    public KeyPair buildKeyPair() throws NoSuchAlgorithmException 
    {
        final int keySize = 2048;
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(keySize);
        return keyPairGenerator.genKeyPair();   
    }

    public byte[] encrypt(PublicKey publicKey, String message) throws Exception
    {
        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");  
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);  
        return cipher.doFinal(message.getBytes());  
    }

    public String decrypt(PrivateKey privateKey, byte [] encrypted) throws Exception
    { 
        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");  
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return new String(cipher.doFinal(encrypted));
    }

    public byte[] rsaEncrypt(String watermarkMsg, PublicKey publicKey) throws Exception
    {
        byte[] cipherText = encrypt(publicKey, watermarkMsg);
        return cipherText;
    }

    public String rsaDecrypt(byte[] cipherText, PrivateKey privateKey) throws Exception
    {
        String plainText = decrypt(privateKey, cipherText);
        return plainText;
    }

    public static void main(String args[]) throws NoSuchAlgorithmException
    {
        Tester t = new Tester();

        String inputImageFilePath = "<file_path_here";
        String stringOfImage = null;
        byte[] encryptedImage = null;
        byte[] encryptedImagePartial = null;
        KeyPair keyPair = t.buildKeyPair();
        PublicKey pubKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate()

        //-----------IMAGE TO STRING CONVERSION----------------
        //The imagetostring() function retrieves the image at the file path and converts it into a Base64 encoded String  
        try
        {
            stringOfImage = t.imagetostring(inputImageFilePath);
        }
        catch(Exception e)
        {
            System.out.println(e.toString());
        }

        //-----------ENCRYPTION OF STRING----------------
        //The encryption is done in blocks of 189, because earlier I got an exception - "javax.crypto.IllegalBlockSizeException: Data must not be longer than 190 bytes"
        try
        {
            String plaintext = stringOfImage;
            String plaintextTrimmed = "";
            System.out.println(stringOfImage);
            encryptedImage = new byte[15512];    //The size is given as 15512 because the length of the particular string was found to be 15512
            while(plaintext!="")
            {
                if(plaintext.length()>189)
                {
                    plaintextTrimmed = plaintext.substring(0, 189);
                    plaintext = plaintext.substring(189);
                }
                else
                {
                    plaintextTrimmed = plaintext;
                    plaintext = "";
                }
                encryptedImagePartial = t.rsaEncrypt(plaintextTrimmed, pubKey);
                encryptedImage = t.concatenate(encryptedImage, encryptedImagePartial);
                System.out.println(encryptedImage.length);
            }

        }
        catch(Exception e)
        {
            System.out.println(e.toString());
        }
        t.byteDigest(encryptedImage);

        //-----------DECRYPTION OF STRING--------------
        //The decryption is done in blocks of 189, because earlier I got an exception - "javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes"
        try
        {
            // The ciphertext is located in the variable encryptedImage which is a byte[]
            String stringRepOfCipherText = new String(encryptedImage);              String cipherTextTrimmed = "";
            String decryptedImagePartial;
            String decryptedImage = "";
            while(stringRepOfCipherText!="")
            {
                if(stringRepOfCipherText.length()>189)
                {
                    cipherTextTrimmed = stringRepOfCipherText.substring(0, 189);
                    stringRepOfCipherText = stringRepOfCipherText.substring(189);
                }
                else
                {
                    cipherTextTrimmed = stringRepOfCipherText;
                    stringRepOfCipherText = "";
                }
                decryptedImagePartial = t.rsaDecrypt(cipherTextTrimmed.getBytes(), privateKey);
                decryptedImage = decryptedImage + decryptedImagePartial;
            }
        }
        catch(BadPaddingException e)
        {
            System.out.println(e.toString());
        }
        catch(Exception e)
        {
            System.out.println(e.toString());
        }
    }
}

Also, I noticed a few other examples where KeyFactory was used to generate the keys. Could anyone also tell me the difference between using KeyFactory and what I have used?

You can not cut the ciphertext into arbitrary chunks!

Since you specifically asked for plain RSA without symmetric algorithms involved (which I strongly recommend against!), this is what you need to do:

  1. Find out the maximum payload size for your RSA configuration.
  2. Split your plaintext into chunks of this size
  3. Encrypt each chunk individually and do not simply concatenate them and discard chunk boundaries!

During decryption:

  1. Pass each ciphertext chunk to the decrypt function using the original size it has after encryption. Do not append any data and do not create "substrings" .
  2. Concatenate the resulting plaintexts.

Ideally you should use a hybrid encryption scheme:

  1. generate an encryption key ( encKey )
  2. encrypt your image using a symmetric algorithm with encKey
  3. encrypt encKey using pubKey with RSA

Symmetric ciphers can be used in different modes of operation, that avoid such length limitations.

First of all, it makes absolutely no sense to first encode the image to base 64. The input of modern ciphers consist of bytes, and images are already bytes. You may want to base 64 encode the ciphertext if you want to store that a string.

The input block size is indeed 190 bytes. You can see a table for RSA / OAEP here (don't forget to upvote!). I'm not sure why you would want to use 189 in that case; my code is however generalized. The output block size is simply the key size for RSA as it is explicitly converted to the key size in bytes (even if it could be smaller).

During decryption you convert the ciphertext to a string. However, string decoding in Java is lossy; if the decoder finds a byte that doesn't represent a character then it is dropped silently . So this won't (always work), resulting for instance in a BadPaddingException . That's OK though, we can keep to binary ciphertext.

So without further ado, some code for you to look at. Note the expansion of the ciphertext with the 66 bytes per block and the poor performance of - mainly - the decryption. Using AES with RSA in a hybrid cryptosystem is highly recommended (and not for the first time for this question).

import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;

import javax.crypto.Cipher;

public class Tester {

    private static final int KEY_SIZE = 2048;
    private static final int OAEP_MGF1_SHA256_OVERHEAD = 66;

    public static KeyPair buildKeyPair() throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(KEY_SIZE);
        return keyPairGenerator.generateKeyPair();
    }

    public static void main(String args[]) throws Exception {

        KeyPair keyPair = Tester.buildKeyPair();
        RSAPublicKey pubKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();

        // assumes the bitLength is a multiple of 8 (check first!)
        int keySizeBytes = pubKey.getModulus().bitLength() / Byte.SIZE;

        byte[] image = new byte[1000];
        Arrays.fill(image, (byte) 'm'); 

        // --- encryption

        final Cipher enc;
        try {
            enc = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("OAEP with MGF-1 using SHA-256 not available in this runtime", e);
        }
        enc.init(Cipher.ENCRYPT_MODE, pubKey);

        int fragmentsize = keySizeBytes - OAEP_MGF1_SHA256_OVERHEAD;

        ByteArrayOutputStream ctStream = new ByteArrayOutputStream();
        int off = 0;
        while (off < image.length) {
            int toCrypt = Math.min(fragmentsize, image.length - off);
            byte[] partialCT = enc.doFinal(image, off, toCrypt);
            ctStream.write(partialCT);
            off += toCrypt;
        }

        byte[] ct = ctStream.toByteArray();

        // --- decryption

        Cipher dec = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        dec.init(Cipher.DECRYPT_MODE, privateKey);

        ByteArrayOutputStream ptStream = new ByteArrayOutputStream();
        off = 0;
        while (off < ct.length) {
            int toCrypt = Math.min(keySizeBytes, ct.length - off);
            byte[] partialPT = dec.doFinal(ct, off, toCrypt);
            ptStream.write(partialPT);
            off += toCrypt;
        }

        byte[] pt = ptStream.toByteArray();

        // mmmm...
        System.out.println(new String(pt, StandardCharsets.US_ASCII));
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM