65

I am using rsa key to encrypt a long string which I will send to my server(will encrypt it with server's public key and my private key) But it throws an exception like javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes I feel that I have not understood the working of rsa properly till now(using the inbuilt libraries are the cause for this).
Can some one please explain why this exception is being thrown. Is it not at all possible to send long string encrypted?

Ashwin
  • 12,691
  • 31
  • 118
  • 190

6 Answers6

90

The RSA algorithm can only encrypt data that has a maximum byte length of the RSA key length in bits divided with eight minus eleven padding bytes, i.e. number of maximum bytes = key length in bits / 8 - 11.

So basicly you divide the key length with 8 -11(if you have padding). For example if you have a 2048bit key you can encrypt 2048/8 = 256 bytes (- 11 bytes if you have padding). So, either use a larger key or you encrypt the data with a symmetric key, and encrypt that key with rsa (which is the recommended approach).

That will require you to:

  1. generate a symmetric key
  2. Encrypt the data with the symmetric key
  3. Encrypt the symmetric key with rsa
  4. send the encrypted key and the data
  5. Decrypt the encrypted symmetric key with rsa
  6. decrypt the data with the symmetric key
  7. done :)
Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
John Snow
  • 5,214
  • 4
  • 37
  • 44
55

Based on @John Snow answer, I did an example

  1. Generate Symmetric Key (AES with 128 bits)

    KeyGenerator generator = KeyGenerator.getInstance("AES");
    generator.init(128); // The AES key size in number of bits
    SecretKey secKey = generator.generateKey();
    
  2. Encrypt plain text using AES

    String plainText = "Please encrypt me urgently..."
    Cipher aesCipher = Cipher.getInstance("AES");
    aesCipher.init(Cipher.ENCRYPT_MODE, secKey);
    byte[] byteCipherText = aesCipher.doFinal(plainText.getBytes());
    
  3. Encrypt the key using RSA public key

    KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
    kpg.initialize(2048);
    KeyPair keyPair = kpg.generateKeyPair();
    
    PublicKey puKey = keyPair.getPublic();
    PrivateKey prKey = keyPair.getPrivate();
    
    Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    cipher.init(Cipher.PUBLIC_KEY, puKey);
    byte[] encryptedKey = cipher.doFinal(secKey.getEncoded()/*Seceret Key From Step 1*/);
    
  4. Send encrypted data (byteCipherText) + encrypted AES Key (encryptedKey)

  5. On the client side, decrypt symmetric key using RSA private key

    cipher.init(Cipher.PRIVATE_KEY, prKey);
    byte[] decryptedKey = cipher.doFinal(encryptedKey);
    
  6. Decrypt the cipher text using decrypted symmetric key

    //Convert bytes to AES SecertKey
    SecretKey originalKey = new SecretKeySpec(decryptedKey , 0, decryptedKey .length, "AES");
    Cipher aesCipher = Cipher.getInstance("AES");
    aesCipher.init(Cipher.DECRYPT_MODE, originalKey);
    byte[] bytePlainText = aesCipher.doFinal(byteCipherText);
    String plainText = new String(bytePlainText);`
    
Matt Ke
  • 3,599
  • 12
  • 30
  • 49
17

You should not use RSA on your secret data directly. You should only ever use RSA on pseudo-random or completely random data, such as session keys or message authentication codes.

You've gotten the problem at 256 bytes -- that is because you're probably working with 2048 bit keys. The keys are able to encrypt any integer in the range 0 to 2^2048 - 1 into the same range, and that means your data must be 256 bytes or smaller.

If you intend to encrypt more than this, please use one RSA encryption to encrypt a session key for a symmetric algorithm, and use that to encrypt your data.

sarnold
  • 102,305
  • 22
  • 181
  • 238
  • Why is there a restriction like that, that you can encrypt data upto a certain length only? – Ashwin Apr 07 '12 at 01:17
  • 4
    Because RSA is performed on a _finite [ring](http://en.wikipedia.org/wiki/Ring_(mathematics))_, the only numbers that exist are the integers in the range `[0, 2^2048-1]`, inclusive. Any message longer than 2048 bits represents a number outside this range, and must be encoded in either two blocks or -- if you want safety -- the entire message should be encrypted in a session key. Real-world deployed RSA must protect against [multiple attacks](http://en.wikipedia.org/wiki/RSA_(algorithm)#Attacks_against_plain_RSA), and never working on "raw" plaintext is one important part of using RSA safely. – sarnold Apr 08 '12 at 22:50
3

To follow on from John Snow's answer above I created a simple random-symmetric-crypt library that you can use to simply encrypt any length data using a private key.

You can find the library at GitHub - random-symmetric-crypto

 final RandomSymmetricCipher cipher = new RandomSymmetricCipher();

 // Encrypt the data and the random symmetric key.
 final CryptoPacket cryptoPacket = cipher.encrypt(inputData, PRIVATE_KEY_BASE64);

 // Convert the CryptoPacket into a Base64 String that can be readily reconstituted at the other end.
 final CryptoPacketConverter cryptoPacketConverter = new CryptoPacketConverter();
 final String base64EncryptedData = cryptoPacketConverter.convert(cryptoPacket);
 System.out.println("Base64EncryptedData=" + base64EncryptedData);

 // Decrypt the Base64 encoded (and encrypted) String.
 final byte[] outputData = cipher.decrypt(base64EncryptedData, PUBLIC_KEY_BASE64);
William
  • 20,150
  • 8
  • 49
  • 91
  • I can't find where you specify which padding mode to use. You should explicitly specify OAEP padding, since other common paddings like PKCS#1v1.5 or no padding at all are insecure. – CodesInChaos Aug 13 '14 at 10:27
  • Happy to accept a pull request. Have you got a URL to support that no padding is insecure? – William Aug 13 '14 at 12:19
  • Boneh has written a good overview of [attacks against RSA](http://crypto.stanford.edu/~dabo/papers/RSA-survey.pdf). Crypto.se has [lots of questions about textbook RSA](http://crypto.stackexchange.com/search?q=textbook+rsa) as well. I couldn't find a MAC in the symmetric part of your code. This means you'll be vulnerable to active attacks, such as padding oracles. The old and still popular PKCS#1v1.5 padding is vulnerable to active attacks as well (unless you work around the weakness *really* carefully), namely Bleichenbacher's attack. – CodesInChaos Aug 13 '14 at 12:34
  • The paddings are specified in the RandomSymmetricCipher class. It is using "DESede/CBC/PKCS5Padding" for the symmetric and "RSA/ECB/PKCS1Padding" for the public key cipher. It is trivial to change them. http://stackoverflow.com/a/10935308/493682 lists several options and links to more. – William Aug 15 '14 at 07:12
  • So if you have a server which receives messages and directly decrypts them, returning an error if it's invalid (a pretty common configuration), an attacker can use that server to decrypt the message. There are two relevant attacks, the standard padding oracle against unauthenticated CBC and Bleichenbacher against RSA with PKCS#1v1.5 padding. – CodesInChaos Aug 15 '14 at 07:47
  • Yes I understand the attack. I don't think either of the 2 padding configs I've used are subject to that. Though if you can show otherwise and provide a better alternative I'm all ears. – William Aug 15 '14 at 18:37
  • I downloaded and successfully employed your code from git ... VERY NICE WORK! Saved me a ton of time and several headaches. Thank You! – Michael Sims May 22 '19 at 17:12
0

I went through the same problem, this is how I solved it. AES can encrypt data as a standalone algorithm and can also do it with the help of RSA algorithm. Using AES standalone algorithm combined with RSA algorithm in the same block code(function) will cause increase in Data size affecting the AES key Size. You shouldn't do as shown with the code:

//encryption without using RSA KEY both can't run at the same time.
                    byte[] bytes = Files.readAllBytes(Paths.get(pvtKeyFile));
                    ks = new PKCS8EncodedKeySpec(bytes);
                    kf = KeyFactory.getInstance("RSA");
                    pvt = kf.generatePrivate(ks);
                    cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
                    cipher.init(Cipher.ENCRYPT_MODE, pvt);
                    processFiles(cipher, localFile, localFile + ".enc");
                    System.out.println("The encrypted files have been created successfully.");
                    //encryption using RSA.
                    byte[] bytes = Files.readAllBytes(Paths.get(pvtKeyFile));
                    ks = new PKCS8EncodedKeySpec(bytes);
                    kf = KeyFactory.getInstance("RSA");
                    pvt = kf.generatePrivate(ks);
                    kgen = KeyGenerator.getInstance("AES");
                    kgen.init(128);
                    skey = kgen.generateKey();
                    byte[] iv = new byte[128/8];
                    srandom.nextBytes(iv);
                    ivspec = new IvParameterSpec(iv);
                    try (FileOutputStream out = new FileOutputStream(localFile + ".enc")) {
                        {
                            cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
                            cipher.init(Cipher.ENCRYPT_MODE, pvt);
                            byte[] b = cipher.doFinal(skey.getEncoded());
                            out.write(b);
                            System.err.println("AES Key Length: " + b.length);
                        }
                        out.write(iv);
                        System.err.println("IV Length: " + iv.length);
                        ciphers = Cipher.getInstance("AES/CBC/PKCS5Padding");
                        ciphers.init(Cipher.ENCRYPT_MODE, skey, ivspec);
                        System.out.println("The encrypted files have been created successfully.");
                        try (FileInputStream in = new FileInputStream(localFile)) {
                            processFile(ciphers, in, out);
                            
                        }
                    }

You can't do as shown above, it will cause error during the decryption process. if you are to use AES standalone algorithm use it in one block of code without including the RSA algorithm and the vice versa is true, as show below.

//encryption without using RSA KEY both can't run at the same time.
                    byte[] bytes = Files.readAllBytes(Paths.get(pvtKeyFile));
                    ks = new PKCS8EncodedKeySpec(bytes);
                    kf = KeyFactory.getInstance("RSA");
                    pvt = kf.generatePrivate(ks);
                    cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
                    cipher.init(Cipher.ENCRYPT_MODE, pvt);
                    processFiles(cipher, localFile, localFile + ".enc");
                    System.out.println("The encrypted files have been created successfully.");

OR You can only use the RSA Algorithm as shown:

byte[] bytes = Files.readAllBytes(Paths.get(pvtKeyFile));
                    ks = new PKCS8EncodedKeySpec(bytes);
                    kf = KeyFactory.getInstance("RSA");
                    pvt = kf.generatePrivate(ks);
                    kgen = KeyGenerator.getInstance("AES");
                    kgen.init(128);
                    skey = kgen.generateKey();
                    byte[] iv = new byte[128/8];
                    srandom.nextBytes(iv);
                    ivspec = new IvParameterSpec(iv);
                    try (FileOutputStream out = new FileOutputStream(localFile + ".enc")) {
                        {
                            cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
                            cipher.init(Cipher.ENCRYPT_MODE, pvt);
                            byte[] b = cipher.doFinal(skey.getEncoded());
                            out.write(b);
                            System.err.println("AES Key Length: " + b.length);
                        }
                        out.write(iv);
                        System.err.println("IV Length: " + iv.length);
                        ciphers = Cipher.getInstance("AES/CBC/PKCS5Padding");
                        ciphers.init(Cipher.ENCRYPT_MODE, skey, ivspec);
                        System.out.println("The encrypted files have been created successfully.");
                        try (FileInputStream in = new FileInputStream(localFile)) {
                            processFile(ciphers, in, out);
                            
                        }
                    }

Thanks I hope it will help someone regards.

Evans sang
  • 31
  • 2
-3

you need split your data by the publicKey

int keyLength = publicKey.getModulus().bitLength() / 16;
String[] datas = splitString(data, keyLength - 11);
String mi = ""//the data after encrypted;
for (String s : datas) {
    mi += bcd2Str(cipher.doFinal(s.getBytes()));
}
return mi;


public static String bcd2Str(byte[] bytes) {
    char temp[] = new char[bytes.length * 2], val;

    for (int i = 0; i < bytes.length; i++) {
        val = (char) (((bytes[i] & 0xf0) >> 4) & 0x0f);
        temp[i * 2] = (char) (val > 9 ? val + 'A' - 10 : val + '0');

        val = (char) (bytes[i] & 0x0f);
        temp[i * 2 + 1] = (char) (val > 9 ? val + 'A' - 10 : val + '0');
    }
   return new String(temp);
}
Carl
  • 251
  • 1
  • 4
  • 13
  • Just because you can do something does not mean you should. What is needed is hybrid encryption where the data is encrypted with a symmetric algorithm such as AES using a random key and the key is encrypted with RSA. – zaph Jul 19 '16 at 04:42
  • 1
    Notes: 1. "data" is the plural, "datum" is the singular form. 2. The `bcd2Str` method is really performing hexadecimal encoding [0-9A-Z]. 3. There may be no need to encode the binary output by the RSA encryption and if encoding is necessary, while hexadecimal is OK, the usual case is to use Base64 encoding, it is more compact. – zaph Jul 19 '16 at 04:54