0

Hoping somebody can point me in the right direction here.

I'm not particularly familiar with encryption (in Java or otherwise), but I said that I'd help somebody out with a task they have that specifies using RSA encrypt/decrypt.

To familiarise myself with what is trying to be accomplished, I've put together a little example program that:

  • Generates 6 random numbers, and puts them in a comma-separated string
  • Encrypts the sequence of numbers
  • Appends the encrypted numbers sequence to a file - the same file can be written to more than once if the program is run multiple times
  • Reads the encrypted file - should get all sequences that have been written
  • Decrypt each sequence and print out a collection of comma-separated strings

This works fine on the first attempt - i.e. when the first sequence is written into an empty file - and the correct number sequence is returned.

When the file contains more than one encrypted sequence, this causes the decrypt routine to crash with a BadPaddingException. I've done a step-through at this point, and each of the byte[] (representing an encrypted sequence) to be decrypted is 128 bytes, so it's not like an irregular number of bytes is causing the problem.

I know that RSA isn't the recommended way to go about it anymore, but this is the specification that I'm having to get my head around. I'm also aware that there's a ton of SO questions to do with RSA and BadPaddingException, but I haven't come across one that deals with this issue.

My example code is below:

public class EncryptDecrypt {
    
    private static Cipher cipher;
    private static KeyPair keyPair;

    public static void main(String[] args)
    {
        String[] numbers = getNumbers();
        String numbersStr = String.join(", ", numbers);
        System.out.println("Generated: " + numbersStr + ", NumBytes: " + numbersStr.getBytes().length);
        
        byte[] encryptedNumbers = encrypt(numbersStr);
        System.out.println("Encrypted: " + encryptedNumbers.toString() + ", NumBytes: " + encryptedNumbers.length);
        
        writeToFile(encryptedNumbers);
        System.out.println("Encrypted numbers written to data.txt");
        
        ArrayList<byte[]> encryptedData = readFromFile();
        System.out.println("Encrypted numbers read from data.txt, NumSequences: " + encryptedData.size());

        ArrayList<String> decryptedSequences = decrypt(encryptedData);
        for (int i = 0; i < decryptedSequences.size(); i++)
        {
            String sequence = decryptedSequences.get(i);
            System.out.println("Sequence " + i + ": " + sequence);
        }
    }
    
    private static String[] getNumbers()
    {
        String[] numbers = new String[6];
        
        int min = 1;
        int max = 60;
        
        for (int i = 0; i < numbers.length; i++)
        {
            double number = (Math.random() * (max - min) + min);
            numbers[i] = number >= 10 ? Integer.toString((int) number) : "0" + Integer.toString((int) number);
        }
        
        return numbers;
    }
    
    private static byte[] encrypt(String data)
    {
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            keyPair = keyPairGenerator.generateKeyPair();
            PublicKey publicKey = keyPair.getPublic();
            cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            cipher.update(data.getBytes());
            byte[] encrypted = cipher.doFinal();
            
            return encrypted;
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException ex) {
            Logger.getLogger(EncryptDecrypt.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        return null;
    }
    
    private static void writeToFile(byte[] data)
    {
        FileOutputStream fileOut = null;
        try {
            File file = new File("data.txt");
            file.createNewFile();
            fileOut = new FileOutputStream(file, true);
            
            fileOut.write(data);
            fileOut.flush();
            fileOut.close();
        } catch (FileNotFoundException ex) {
            Logger.getLogger(EncryptDecrypt.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(EncryptDecrypt.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                fileOut.close();
            } catch (IOException ex) {
                Logger.getLogger(EncryptDecrypt.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
    
    private static ArrayList<byte[]> readFromFile()
    {
        File file = new File("data.txt");
        if (file.exists())
        {
            try {
                ArrayList<byte[]> encryptedSequences = new ArrayList<>();
                FileInputStream fileIn = new FileInputStream(file);
                int blockSize = 128;
                int numBlocks = fileIn.available() / blockSize;
                
                for (int i = 0; i < numBlocks; i++)
                {
                    byte[] encryptedSequence = new byte[blockSize];
                    fileIn.read(encryptedSequence);
                    
                    encryptedSequences.add(encryptedSequence);
                }
                
                fileIn.close();
                
                return encryptedSequences;
            } catch (FileNotFoundException ex) {
                Logger.getLogger(EncryptDecrypt.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(EncryptDecrypt.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        
        return null;
    }
    
    private static ArrayList<String> decrypt(ArrayList<byte[]> data)
    {
        try {
            cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            PrivateKey privateKey = keyPair.getPrivate();
            
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            
            ArrayList<String> decryptedStrings = new ArrayList<>();
            
            for (byte[] sequence : data)
            {
                byte[] decryptedBytes = cipher.doFinal(sequence);
                String decryptedString = new String(decryptedBytes);
                decryptedStrings.add(decryptedString);
            }
            
            return decryptedStrings;
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException ex) {
            Logger.getLogger(EncryptDecrypt.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        return null;
    }
}

If anyone can spot what's wrong with this, I'd really appreciate it!

Thanks

marcuthh
  • 592
  • 3
  • 16
  • 42
  • 2
    You can't string several completed encryption's together and then expect it to decrypt as one block. When you call doFinal() you indicate that the data should be wrapped up and padding applied etc. Also - RSA ain't ment for bulk encryption, and you will not find support for encrypting more than one singel RSA block in libraries, as this is not the way you are supposed to use it. – Ebbe M. Pedersen Nov 25 '20 at 11:31
  • 1
    Each time you run the program you generate a new key, so as you build up encrypted data in the file, each one is encrypted with a different key. I'm not sure why this manifests as a bad padding exception. – tgdavies Nov 25 '20 at 11:42
  • 2
    Don't you have to read the data in 256 bytes chunks (instead of 128 bytes) with a 2048 bits (256 bytes) key? This could explain the `BadPaddingException` (in addition to the problem with the different keys). – Topaco Nov 25 '20 at 12:10
  • 1
    Yes, I needed to set the chunk size to 256 too. – tgdavies Nov 25 '20 at 12:38
  • @Topaco I read about this somewhere else too, but must admit I didn't fully understand. If my encrypted byte arrays are coming out as 128 bytes, will reading them in at 256 not mean that each chunk will be bigger than I want? Or does setting the key to 2048 bits correct this? Thanks – marcuthh Nov 25 '20 at 12:53
  • 1
    When encrypting with RSA the ciphertext has the length of the key, i.e. with a 2048 bit key 2048 bit or 256 bytes. This means that your encrypted data has a size of 256 bytes (and not 128 bytes) as can be easily verified during debugging. – Topaco Nov 25 '20 at 13:07

0 Answers0