3

I'm having issues with my code.

What I am trying to do is encrypt a file using ECB Mode (meaning, encrypt blocks of text with no chaining into the next block).

It works perfectly sometimes, but sometimes it does not. The issue comes when it encrypts 128 bytes of data and writes 129 bytes. It will work perfectly up until the first time it does that, but then the decrypting will be off by a byte, and will mess everything up. I know this because when it goes wrong, you can see that cipher (on line 88, a byte array) is of length 129, not 128, and then it is written to file.

Here's an example of what I mean: Encrypt: testtesttesttesttesttesttest testtesttesttesttesttesttest testtesttesttesttesttesttest testtesttesttesttesttesttest testtesttesttesttesttesttest

Output: testtesttesttesttesttesttest testtesttesttesttesttesttest testtesttesttesttesttesttest testtesttesttesttesttesttest testtest(500 bytes of jumbled data)

I've attached my entire code; for quick run throughs, I've enabled decryption to produce to stdout right after encryption.

So simply run: -k key (will generate key.public and key.private) -e key.public -i input -o output (will encrypt some file input and store it in output, read that file, decrypt it, send to stdout).

Any help would be appreciated!

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Scanner;
import java.io.*;
import java.util.List;
import java.util.Random;

public class RSA {


    public static void main(String[] args) throws UnsupportedEncodingException {
        List<String> list = Arrays.asList(args);
        if(list.contains("-h")) {
            System.out.println("Usage:");
            System.out.println("RSA -h - View command-line arguments.");
            System.out.println("RSA -k <key file> -b <bit size> - Generate public/private keyfiles of size <bit size>.");
            System.out.println("RSA -e <key file>.public -i <input file> -o <output file> - Encrypt <input file> with key <key file>, store in <output file>.");
            System.out.println("RSA -d <key file>.private -i <input file> -o <output file> - Decrypt <input file> with key <key file>, store in <output file>.");
        } else if (list.contains("-k")) {
            String key_file = "";
            try {
                key_file = list.get(list.indexOf("-k") + 1);
                if(key_file.equals("-b")) {
                    System.out.println("Usage:");
                    System.out.println("RSA -k <key file> -b <bit size> - Generate public/private keyfiles of size <bit size>.");
                    System.exit(1);
                }
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println("Usage:");
                System.out.println("RSA -k <key file> -b <bit size> - Generate public/private keyfiles of size <bit size>.");
                System.exit(1);
            }
            int bit_size = 0;
            if(!list.contains("-b")) {
                bit_size = 1024;
            } else {
                try {
                    bit_size = Integer.parseInt(list.get(list.indexOf("-b") + 1));
                } catch(ArrayIndexOutOfBoundsException e) {
                    System.out.println("Usage:");
                    System.out.println("RSA -k <key file> -b <bit size> - Generate public/private keyfiles of size <bit size>.");
                    System.exit(1);
                }
            }

            generate_key(bit_size, key_file);

        } else if (list.contains("-e")) {
            //get input file and output file
            String input_file = "";
            String key_file = "";
            String output_file = "";
            try {
                input_file = list.get(list.indexOf("-i") + 1);
                output_file = list.get(list.indexOf("-o") + 1);
                key_file = list.get(list.indexOf("-e") + 1);
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println("Usage:");
                System.out.println("RSA -e <key file>.public -i <input file> -o <output file> - Encrypt <input file> with key <key file>, store in <output file>.");
                System.exit(1);
            }

            String public_key = read_file(key_file);
            String private_key = read_file("key.private");

            BigInteger public_modulus = new BigInteger(public_key.substring(1, public_key.indexOf(',')));
            BigInteger public_exponent = new BigInteger(public_key.substring(public_key.indexOf(',') + 1, public_key.length() - 1));

            byte[] file = read_bytes(input_file);
            byte[] cipher = new byte[128];
            byte[] decrypted = new byte[128];

            BigInteger d = new BigInteger(private_key.substring(1, private_key.indexOf(',')));
            BigInteger modulus = new BigInteger(private_key.substring(private_key.indexOf(',') + 1, private_key.length() - 1));

            write_file(output_file, "", false);
            int index = 0;
            while (index<file.length) {
                byte[] block = Arrays.copyOfRange(file, index, index+128);
                cipher = new BigInteger(block).modPow(public_exponent, public_modulus).toByteArray();
                append_bytes(output_file, cipher);
                index+=128;
            }

            byte[] encrypted = read_bytes(output_file);


            index = 0;
            while(index < encrypted.length) {
                byte[] block = Arrays.copyOfRange(encrypted, index, index+256);
                decrypted =  new BigInteger(block).modPow(d, modulus).toByteArray();
                System.out.println(new String(decrypted));
                index+= 256;
            }


        } else if (list.contains("-d")) {
            /*String input_file, output_file, key_file;
            input_file = output_file = key_file = "";

            try {
                input_file = list.get(list.indexOf("-i") + 1);
                output_file = list.get(list.indexOf("-o") + 1);
                key_file = list.get(list.indexOf("-d") + 1);
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println("Usage:");
                System.out.println("RSA -d <key file>.private -i <input file> -o <output file> - Decrypt <input file> with key <key file>, store in <output file>.");
                System.exit(1);
            }

            String private_key = read_file(key_file).toString();
            BigInteger d = new BigInteger(private_key.substring(1, private_key.indexOf(',')));
            BigInteger modulus = new BigInteger(private_key.substring(private_key.indexOf(',') + 1, private_key.length() - 1));

            byte[] encrypted = null;
            /* todo */

        } else {
            System.out.println("Usage:");
            System.out.println("RSA -h - View command-line arguments.");
            System.out.println("RSA -k <key file> -b <bit size> - Generate public/private keyfiles of size <bit size>.");
            System.out.println("RSA -e <key file>.public -i <input file> -o <output file> - Encrypt <input file> with key <key file>, store in <output file>.");
            System.out.println("RSA -d <key file>.private -i <input file> -o <output file> - Decrypt <input file> with key <key file>, store in <output file>.");
        }
    }

    private static void generate_key(int bit_size, String key_file) {
        BigInteger p = BigInteger.probablePrime(bit_size, new Random());
        BigInteger q = BigInteger.probablePrime(bit_size, new Random());

        BigInteger one = new BigInteger("1");
        BigInteger phi = new BigInteger("1");
        BigInteger e = new BigInteger("65537");

        boolean done = false;

        while(!done) {
            BigInteger temp = p.subtract(one);
            BigInteger temp2 = q.subtract(one);
            phi = (temp.multiply(temp2));
            if(phi.gcd(e).equals(one)) {
                done = true;
            } else {
                e = BigInteger.probablePrime(bit_size, new Random());
            }
        }

        BigInteger public_modulus = p.multiply(q);
        BigInteger public_exponent = e;
        BigInteger private_key = public_exponent.modInverse(phi); //d

        try {
            write_file(key_file + ".public", "(" + public_modulus + "," + e + ")", false);
            write_file(key_file + ".private", "(" + private_key.toString() + "," + public_modulus + ")", false);
        } catch (Exception ex) {
            System.out.println("Error creating key files.");
            System.exit(1);
        }

    }


    public static void write_bytes(String file_name, byte[] bytes) {
        try {
            FileOutputStream fos = new FileOutputStream(new File(file_name));
            fos.write(bytes);
            fos.close();
        } catch (IOException e) {
            System.out.println("Error writing bytes to file.");
            System.exit(1);
        }
    }

    public static String read_file(String file_name) {
        boolean is_key = false;
        if(file_name.contains(".public") || file_name.contains(".private"))
            is_key = true;

        Scanner sc = null;
        try {
            sc = new Scanner (new File(file_name));
        } catch (FileNotFoundException e) {
            System.out.println("Input file does not exist.");
            System.exit(1);
        }

        StringBuilder buf = new StringBuilder("");
        while (sc.hasNext ()) {
           buf.append (sc.nextLine());
           if(!is_key)
               buf.append("\n");
        }
        sc.close();
        return buf.toString();
    }

    public static byte[] read_bytes(String file_name) {
        Path path = Paths.get(file_name);
        byte[] encrypted = null;
        try {
            encrypted = Files.readAllBytes(path);
        } catch (IOException e) {
            System.out.println("Error reading bytes from " + file_name);
            System.exit(1);
        }
        return encrypted;
    }
    public static void append_bytes(String file_name, byte[] bytes) {
        try {
            OutputStream fos = new FileOutputStream(file_name, true);
            fos.write(bytes);
            fos.flush();
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("Error appending bytes to file.");
            System.exit(1);
        }
    }

    public static void write_file(String file_name, String message, boolean append) {
        try {
            FileWriter fstream = new FileWriter(file_name, append);
            BufferedWriter out = new BufferedWriter(fstream);
            out.write(message);
            out.close();
        } catch (Exception e){
            System.out.println("Error writing to file.");
            System.exit(1);
        }
    }
}
Bobby Brown
  • 85
  • 2
  • 6

2 Answers2

5

ECB is a block cipher mode of operation. RSA is a public key encryption scheme, not a block cipher.

Generally, it doesn't make sense to encrypt long messages directly with RSA. Rather, you would use hybrid encryption: choose a random key for a symmetric cipher like AES, encrypt the message with the symmetric cipher, and then encrypt the symmetric key with RSA.

Also, remember to use an appropriate padding scheme such as OAEP when encrypting the key with RSA; unpadded "textbook RSA" is not secure. (Or use a scheme like RSA-KEM which doesn't need padding.) Don't use ECB mode for AES either; use a semantically secure mode like CBC or CTR, or, better yet, an authenticated encryption mode.

Community
  • 1
  • 1
Ilmari Karonen
  • 49,047
  • 9
  • 93
  • 153
  • This is for an assignment -- not something that I am planning on using. Thanks, though! – Bobby Brown Mar 02 '13 at 16:47
  • @Bobby: In that case, I would say it's either a silly assignment, or you've misunderstood what it's asking for. [It _is_ technically possible to do what you're asking for](http://crypto.stackexchange.com/a/126), but it's a really silly thing to do. – Ilmari Karonen Mar 02 '13 at 16:50
  • Interesting link about KEM, but I do think it is a bit too tricky to leave implementation to just anybody. Especially the line about generating a random M between 0 and the modulus N seems tricky, and I'm not 100% sure if creating a random one bit smaller does not introduce insecurities (the wiki page does not say). – Maarten Bodewes Mar 02 '13 at 17:02
  • It's a silly assignment, but it must be possible. There is something going wrong in my code, and I can't figure out why. – Bobby Brown Mar 02 '13 at 17:22
  • 1
    @Bobby: To allow any n-bit block as an input, your modulus must be at least 2^n. But since it cannot be _exactly_ 2^n, some outputs will be at least n+1 bits long. You'll either need to pad all outputs to at least the bitlength of your modulus, or somehow indicate where each output block ends and the next begins (e.g. by prepending each block with a single byte giving its length). – Ilmari Karonen Mar 02 '13 at 17:39
  • @owlstead: Generating a uniform random number between 0 and N-1 isn't _that_ hard, compared to, say, correctly implementing OAEP. Anyway, I don't see how cutting the range by a few bits could give an adversary more than a few bits of advantage; after all, even a proper uniformly chosen random number has a relatively high chance of being that small. (And, since this is a public-key scheme, if knowing the encryption of _several_ such small numbers provided any additional advantage, the attacker could just generate some herself.) – Ilmari Karonen Mar 02 '13 at 17:46
  • @Ilmari Yeah, I think I have to agree with that common sense part about the attacker being able to encrypt anything he wants. The thing about OAEP is of course that it can be found in most crypto libraries, I'm not so sure about KEM. But it's certainly a good addition to my library of knowledge :) – Maarten Bodewes Mar 02 '13 at 21:53
0

The problem is "BigInteger.toByteArray()". This byte array needs to be post-processed. First element of the array carries the sign-bit, other bytes are treated unsigned. If the most significant bit of a positive number is in bit 7, the method adds a leading 0 to represent positive sign. If you remove this 0 byte, your output has constant block size. Reverse, when constructing a positive BigInteger from an array it needs a leading zero bit 7.

Concerning key generation: q, p have regularly half bit size, "n = p*q" see Wikipedia RSA Key Generation

Concerning security: As mentioned above "Textbook RSA" is not secure for streaming. The minimum required modification is to add some random bytes "salt" in each block. Common RSA implementations reserve about 25% of the block size for this. Also better is to switch to operation mode CBC.

Concerning acceptance: There is nearly no portable encryption algorithm, available for all Java runtime providers and versions, each variant continuously developed and having a limited lifetime. Also implementation is hidden and might have back doors. Doing it on your own is portable, traceable and secure by the difficulty of the factorization problem, and individual modification attackers do not know. Finally the lack of performance is a security factor, e.g. when you introduce password dependent hashes a brute force password search cannot succeed in time.

Sam Ginrich
  • 661
  • 6
  • 7