2
\$\begingroup\$

Overview

This is a program that encrypts and decrypts messages and texts using two simple algorithms. Please note, the algorithms used in this project are not suitable for production use. I used them to help deepen my understanding [beginner] of the general ideas behind Java, encryption, working with files, and interacting with the command line.

Code

App.java

package encrypt.decrypt;

import java.io.File;
import java.io.IOException;

public class App {
    public static void main(String[] args) throws IOException {

        /*
         * This program has six arguments:
         * -mode: determines the program’s mode (enc for encryption, dec for decryption).
         * -key: an integer key to modify the message.
         * -data: is a text or ciphertext to encrypt or decrypt.
         * -alg: two different algorithms. The first one would be shifting algorithm (it shifts each letter by the
         *  specified number according to its order in the alphabet in circle). The second one would be based on
         *  Unicode table, like in the previous stage.
         * -in: read data in from a txt file
         * -out: write data to a txt file
         */

        String mode = "enc";
        int key = 0;
        String data = "";
        String alg = "shift";
        boolean isDataProvided = false;
        boolean isInProvided = false;
        File inputFile = null;
        File outputFilePath = null;
        String workingDirectory = System.getProperty("user.dir");

        for (int i = 0; i < args.length; i++) {
            switch (args[i]) {
                case "-alg" -> alg = args[i + 1];
                case "-mode" -> mode = args[i + 1];
                case "-key" -> key = Integer.parseInt(args[i + 1]);
                case "-data" -> {
                    isDataProvided = true;
                    data = args[i + 1];
                }
                case "-in" -> {
                    isInProvided = true;
                    inputFile = new File(workingDirectory + "\\" + args[i + 1]);
                }
                case "-out" -> outputFilePath = new File(workingDirectory + "\\" + args[i + 1]);
            }
        }
        switch (mode) {
            case "enc":
                if (alg.equals("unicode")) {
                    if (inputFile == null && outputFilePath == null) {
                        System.out.println(EncryptDecrypt.encrypt(data, key));
                    } else if (isDataProvided && isInProvided) {
                        System.out.println(EncryptDecrypt.encrypt(data, key));
                    } else {
                        EncryptDecrypt.writeCipherTextFile(inputFile, outputFilePath, key, alg, mode);
                    }
                    break;
                } else if (alg.equals("shift")) {
                    if (inputFile == null && outputFilePath == null) {
                        System.out.println(EncryptDecrypt.alphabetIndexPositions(data, key, mode, alg));
                    } else if (isDataProvided && isInProvided) {
                        System.out.println(EncryptDecrypt.alphabetIndexPositions(data, key, mode, alg));
                    } else {
                        EncryptDecrypt.writeCipherTextFile(inputFile, outputFilePath, key, alg, mode);
                    }
                    break;
                }
            case "dec":
                if (inputFile == null && outputFilePath == null) {
                    System.out.println(EncryptDecrypt.alphabetIndexPositions(data, key, mode, alg));
                } else if (isDataProvided && isInProvided) {
                    System.out.println(EncryptDecrypt.alphabetIndexPositions(data, key, mode, alg));
                } else {
                    EncryptDecrypt.readCipherTextFile(inputFile, outputFilePath, key, alg, mode);
                }
                break;
        }
    }
}

EncryptDecrypt.java

package encrypt.decrypt;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

public class EncryptDecrypt {

    public static final String[] ALPHABET = {
            "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
            "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
            "u", "v", "w", "x", "y", "z"
    };

    public static String alphabetIndexPositions(String inputMessage, int inputKey, String mode, String alg) {
        String[] inputMessageArray = inputMessage.split("");
        int alphabetCount = 0;
        StringBuilder alphabetIndex = new StringBuilder();
        StringBuilder isUpperCase = new StringBuilder();

        for (String inputMessageLetter : inputMessageArray) {
            for (String alphabetLetter : ALPHABET) {
                if (inputMessageLetter.equals(alphabetLetter)) {
                    isUpperCase.append(false).append(" ");
                    break;
                } else if (inputMessageLetter.equals(alphabetLetter.toUpperCase())) {
                    isUpperCase.append(true).append(" ");
                    break;
                } else if (inputMessageLetter.matches("[a-zA-Z]")) {
                    alphabetCount++;
                }
            }
            if (alphabetCount == 0 && inputMessageLetter.matches("a")) {
                alphabetIndex.append(0).append(" ");
            } else if (alphabetCount == 0 && !inputMessageLetter.matches("[a-zA-Z]") && !inputMessageLetter.equals(" ")) {
                alphabetIndex.append(inputMessageLetter).append(" ");
                isUpperCase.append("false").append(" ");
            } else {
                if (inputMessageLetter.equals(" ")) {
                    alphabetIndex.append(inputMessageLetter);
                    isUpperCase.append("false").append(" ");
                } else {
                    alphabetIndex.append(alphabetCount).append(" ");
                }
            }
            alphabetCount = 0;
        }
        if (mode.equals("enc")) {
            return indexWithKey(alphabetIndex, inputKey, isUpperCase);
        } else if (mode.equals("dec")) {
            if (alg.equals("shift")) {
                return decryptShiftIndexes(alphabetIndex, inputKey, isUpperCase);
            } else if (alg.equals("unicode")) {
                return decrypt(inputMessage, inputKey);
            } else {
                return "Algorithm must be shift or unicode!";
            }
        } else {
            return "Mode must be enc or dec!";
        }
    }

    public static String indexWithKey(StringBuilder alphabetIndex, int inputKey, StringBuilder isUpperCase) {
        String[] indexArray = alphabetIndex.toString().split(" ");
        StringBuilder indexArrayKey = new StringBuilder();
        int lengthOfAlphabet = 25;
        for (String index :
                indexArray) {
            int indexWithKeyCount;
            if (index.matches("\\d+")) {
                indexWithKeyCount = Integer.parseInt(index) + inputKey;
                if (indexWithKeyCount > lengthOfAlphabet) {
                    indexWithKeyCount = Math.abs(lengthOfAlphabet - indexWithKeyCount);
                    indexArrayKey.append(indexWithKeyCount - 1).append(" ");
                } else {
                    indexArrayKey.append(indexWithKeyCount).append(" ");
                }
            } else {
                indexArrayKey.append(index).append(" ");
            }
        }
        return replaceIndexWithLetters(indexArrayKey, isUpperCase);
    }

    public static String replaceIndexWithLetters(StringBuilder indexArrayKey, StringBuilder isUpperCase) {
        String[] isUpperCaseArray = isUpperCase.toString().split(" ");
        String[] alphabetIndexArray = indexArrayKey.toString().split(" ");
        StringBuilder outputCipher = new StringBuilder();
        int indexCount = 0;
        int count = 0;

        for (String letterIndexPosition : alphabetIndexArray) {
            for (String s : ALPHABET) {
                if (!letterIndexPosition.matches("\\d+")) {
                    if (alphabetIndexArray[indexCount].equals("")) {
                        outputCipher.append(letterIndexPosition).append(" ");
                    } else {
                        outputCipher.append(letterIndexPosition);
                    }
                    break;
                } else if (count == Integer.parseInt(letterIndexPosition)) {
                    if (isUpperCaseArray[indexCount].equals("true")) {
                        outputCipher.append(s.toUpperCase());
                    } else {
                        outputCipher.append(s);
                    }
                    break;
                }
                count++;
            }
            indexCount++;
            count = 0;
        }
        return outputCipher.toString();
    }

    public static String decryptShiftIndexes(StringBuilder alphabetIndex, int inputKey, StringBuilder isUpperCase) {
        String[] alphabetIndexArray = alphabetIndex.toString().split(" ");
        StringBuilder output = new StringBuilder();
        for (String index :
                alphabetIndexArray) {
            if (index.matches("\\d+")) {
                if (Integer.parseInt(index) - inputKey < 0) {
                    output.append(Integer.valueOf(26 - Math.abs(Integer.parseInt(index) - inputKey))).append(" ");
                } else {
                    output.append(Integer.parseInt(index) - inputKey).append(" ");
                }
            } else {
                output.append(index).append(" ");
            }
        }
        return decryptShift(output, isUpperCase);
    }

    public static String decryptShift(StringBuilder output, StringBuilder isUpperCase) {
        String[] isUpperCaseArray = isUpperCase.toString().split(" ");
        String[] outputFinal = output.toString().split(" ");
        StringBuilder outputFinal2 = new StringBuilder();
        for (int indexCount = 0; indexCount < outputFinal.length; indexCount++) {
            for (String index :
                    outputFinal) {
                if (index.matches("\\d+")) {
                    if (isUpperCaseArray[indexCount].equals("true")) {
                        outputFinal2.append(ALPHABET[Integer.parseInt(index)].toUpperCase());
                    } else {
                        outputFinal2.append(ALPHABET[Integer.parseInt(index)]);
                    }
                } else {
                    if (index.equals("")) {
                        outputFinal2.append(index).append(" ");
                    } else {
                        outputFinal2.append(index);
                    }
                }
            }
        }
        return String.valueOf(outputFinal2);
    }

    public static String encrypt(String inputMessage, int inputKey) {
        char[] encryptCharArray = inputMessage.toCharArray();
        char[] charArray2 = new char[encryptCharArray.length];
        for (int i = 0; i < charArray2.length; i++) {
            charArray2[i] = encryptCharArray[i] += inputKey;
        }
        return new String(charArray2);
    }

    public static String decrypt(String inputMessage, int inputKey) {
        char[] decryptCharArray = inputMessage.toCharArray();
        char[] charArray2 = new char[decryptCharArray.length];
        for (int i = 0; i < charArray2.length; i++) {
            charArray2[i] = decryptCharArray[i] -= inputKey;
        }
        return new String(charArray2);
    }

    public static void writeCipherTextFile(File inputFile, File outputFilePath, int key, String alg, String mode) throws IOException {
        Path reader = Path.of(String.valueOf(inputFile));
        List<String> allLines = Files.readAllLines(reader);
        String allLinesString = allLines.toString();
        // Remove the List<String> brackets
        String allLinesStringWithoutBrackets = allLinesString.substring(1, allLinesString.length() - 1);
        if (alg.equals("unicode")) {
            Files.writeString(Paths.get(outputFilePath.toString()), encrypt(allLinesStringWithoutBrackets, key));
        } else if (alg.equals("shift")) {
            Files.writeString(Paths.get(outputFilePath.toString()), alphabetIndexPositions(allLinesStringWithoutBrackets, key, mode, alg));
        }
    }

    public static void readCipherTextFile(File inputFile, File outputFilePath, int key, String alg, String mode) throws IOException {
        Path reader = Path.of(String.valueOf(inputFile));
        List<String> allLines = Files.readAllLines(reader);
        String allLinesString = allLines.toString();
        // Remove the List<String> brackets
        String allLinesStringWithoutBrackets = allLinesString.substring(1, allLinesString.length() - 1);
        if (alg.equals("unicode")) {
            Files.writeString(Paths.get(outputFilePath.toString()), decrypt(allLinesStringWithoutBrackets, key));
        } else if (alg.equals("shift")) {
            Files.writeString(Paths.get(outputFilePath.toString()), alphabetIndexPositions(allLinesStringWithoutBrackets, key, mode, alg));
        }
    }
}

Example Usage

Example 1

java Main -mode enc -in road_to_treasure.txt -out protected.txt -key 5 -alg unicode
This command must get data from the file road_to_treasure.txt, encrypt the data with the key 5, create a file called protected.txt and write ciphertext to it.

Example 2

Input:

java Main -mode enc -key 5 -data "Welcome to hyperskill!" -alg unicode
Output:

\jqhtrj%yt%m~ujwxpnqq&

Example 3

Input:

java Main -key 5 -alg unicode -data "\jqhtrj%yt%m~ujwxpnqq&" -mode dec
Output:

Welcome to hyperskill!

Example 4:

Input:

java Main -key 5 -alg shift -data "Welcome to hyperskill!" -mode enc
Output:

Bjqhtrj yt mdujwxpnqq!

Example 5:

Input:

java Main -key 5 -alg shift -data "Bjqhtrj yt mdujwxpnqq!" -mode dec
Output:

Welcome to hyperskill!

GitHub

https://github.com/iamericfletcher/Encrypt-Decrypt-GH

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

Conditions

Sometimes you use "switch", sometimes you use "if..else if..." . This inconsistency doesn't help readability.

Data types

  • You use String for char values
  • You use a space-delimited String for what should be boolean arrays, and also for integer arrays

Maths

  • You use "+=" and "-=" in contexts where changing the value you're adding to doesn't seem to make sense.

Algorithm Implementation

It would help us critique your code better if we had a clear expression of the encryption and decryption algorithms in use. I'm convinced they could be more elegantly implemented, but I haven't got the patience to reverse engineer them from your code and then do the rewrite.

Update: This seems to be a simple Caesar (ROT-n) cipher, in which case you have made it incredibly complicated, with an unreasonable number of passes through your input data.

Decisions

You seem to need to make decisions too many times. Once you know you are encoding, that should be it; similarly, once you know you are using the "shift" algorithm, that should be it. You shouldn't need to pass the mode and algorithm around.

This is because you haven't done anything sensible about object-orientation and encapsulation.

The encrypter/decrypter class shouldn't care where its input comes from and you shouldn't have a single encrypter/decrypter class. That's not the Java way...

If I were doing this, I'd start with an interface - let's call it "Algorithm" - which provides an encode and decode method, each taking a Reader and a Writer as arguments.

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

public interface Algorithm {
  public void encode(Reader in, Writer out) throws IOException;
  public void decode(Reader in, Writer out) throws IOException;
}

Then you would have classes to implement that interface - say ShiftAlgorithm and UnicodeAlgorithm, for lack of better names. The method implementations would simply read characters from their input, apply the appropriate transformation and write the transformed data to their output.

You can produce appropriate Readers and Writer for files, System.in/System.out and Strings easily enough, and none of that should be the business of the Algorithm implementation.

Here's a example doing just "encoding" with a dummy (no-op) Algorithm implementation and a String as input. You should be able to see how this could be extended for your variations.

import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

public class EncDec {

  public static void main(String[] args) throws IOException {
    Algorithm algImp = new DummyAlgorithm();

    String testInput = "My Dog Has Fleas";
    Reader encodeInput = new CharArrayReader(testInput.toCharArray());
    Writer encodeOutput = new CharArrayWriter();

    algImp.encode(encodeInput, encodeOutput);
    System.out.format("Encoded data = {%s}%n", encodeOutput.toString());

  }

  static final class DummyAlgorithm implements Algorithm {

    @Override
    public void encode(Reader in, Writer out) throws IOException {
      cat(in, out);
    }

    @Override
    public void decode(Reader in, Writer out) throws IOException {
      cat(in, out);
    }

    private void cat(Reader in, Writer out) throws IOException {
      int inChar;
      while ((inChar = in.read()) != -1) {
        out.write(inChar);
      }
    }
  }
}

Here's a simple implementation of the ROT cipher using this Interface approach:

  static final class RotAlgorithm implements Algorithm {

    int rotationDegree;

    RotAlgorithm(int degree) {
      rotationDegree = degree;
    }

    @Override
    public void encode(Reader in, Writer out) throws IOException {
      int inChar;
      while ((inChar = in.read()) != -1) {
        out.write(rotateChar(inChar, rotationDegree));
      }
    }

    @Override
    public void decode(Reader in, Writer out) throws IOException {
      int inChar;
      while ((inChar = in.read()) != -1) {
        out.write(rotateChar(inChar, -rotationDegree));
      }
    }

    private int rotateChar(int inChar, int degree) {
      if ((inChar >= 'a') && (inChar <= 'z')) {
        return rotateChar(inChar, 'a', 'z', degree);
      }
      if ((inChar >= 'A') && (inChar <= 'Z')) {
        return rotateChar(inChar, 'A', 'Z', degree);
      }
      return inChar;
    }

    private int rotateChar(int inChar, char start, char end, int degree) {
      int newChar = inChar + degree;
      if (newChar > end) {
        newChar = start + ((newChar - end) - 1);
      }
      else if (newChar < start) {
        newChar = end - ((start - newChar) - 1);
      }
      return newChar;
    }
  }
\$\endgroup\$
0
1
\$\begingroup\$

Here's what I found in no particular order, I hope it helps:

Command line parsing

The parser will throw an OutOfBoundsException or will use garbage if the user doesn't provide a value after an argument flag. If you pass only an input file but no output file, the parser also fails. It works well with good input though. Consider trying out a library like JSAP or Commons-CLI, these should catch all the edge cases for you. Also, a help page using the -h/--help arg would be very helpful.

Java 14 switch cases

Maybe I'm just not used to the syntax yet, but I wouldn't use arrows in a switch where some of the cases need multiple lines. Using standard breaks looks a bit more uniform here, but that's mostly my opinion.

Exception handling

Having main throw an IOException is fine for quick scripts, but for anything bigger please use try/catch to properly handle exceptions where they occur. A stack trace isn't very helpful to the user, instead catch the exception(s) and print a "human-readable" error message.

BUG: Decrypting doesn't preserve newlines

Encrypting and then decrypting replaces newlines with , , ruining the input file. While doing a toString() on the List<String> seems convenient, I'd loop over the entries and handle the line breaks manually. Another way is to read the entire file as one string and encrypt that. You might get some unprintable characters when encrypting the linebreaks though.

Comments

Please add some comments! I'm having a hard time understanding which function does what, why and how. Add a couple lines of comments before a method to briefly describe what it does and add a line whereever you do more complicated things. It helps us understand your code and it will help you if you decide to work on it after half a year.

Variable names

Too long

allLinesStringWithoutBracket is too long of a name. Long names will make all lines longer and introduce line breaks in the middle of statements, making the code harder to read. This is mostly caused by...

...Bad var names

Good names tell the reader not the content/type of the variable, but what the variable is supposed to represent. A made-up example would be this:
Say you split an input string inputString by spaces. You could name it inputStringArray, but that doesn't help the reader and is already rather long. Ask yourself: What is this array? Depending on the context, it could be called words (inputString is a line of text), args (command line), tokens (some sort of script), etc. Note: Shorter name, but helps the reader to understand the program.

You also have some vars ending in ...2. In these cases, they are used to store some sort of result, so they should be named accordingly.

Inconsistent naming

Both inputFile and outputFilePath are Files. Going by the name, I'd expect the latter to be either a Path or a String though.

Java naming conventions

Anything starting with is is usually a boolean. Here you have a StringBuilder isUpperCase, which is confusing.

=====

I get identical output for both of your encryption schemes (unicode and shift). Is that a bug, intended or am I using the program wrong?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.