3
\$\begingroup\$

This is a Java program I implemented to encrypt a string using the ADVGVX cipher. It takes in the message, the passphrase used to generate the Polybius square, and keyword for the transposition part of the encryption. It prints the encrypted string. What would you suggest fixing and improving?

import java.util.*;

public class Cipher {
    public static void main(String args[]){

        // get input!

        // make scanner object for input
        Scanner scan = new Scanner(System.in);

        // the message to encrypt
        System.out.print("Enter message to encrypt: ");
        String mes = scan.nextLine();

        // keyphrase, used to make polybius square
        System.out.print("Enter keyphrase for Polybius square: ");
        String keyphrase = scan.nextLine();

        while (!validKeyphrase(keyphrase) ){
            System.out.print("Enter valid keyphrase: ");
            keyphrase = scan.nextLine();
        }

        // keyword for transposition
        System.out.print("Enter keyword for transposition: ");
        String keyword = scan.nextLine();

        while (keyword.length() <= 1 && keyword.length() > mes.length()){
            System.out.println("Keyword length must match message length.");
            System.out.print("Enter keyword: ");
            keyword = scan.nextLine();
        }

        // take keyphrase and chuck into polybius square
        char [][] square = new char[6][6];

        // putting keyphrase into a character array
        char[] letters = keyphrase.toCharArray();

        // filling the polybius square
        int counter = -1;
        for (int i = 0; i< 6; i++){
            for (int j=0; j< 6; j++){
                counter++;
                square[i][j] = letters[counter];
            }
        }

        // after the substitution
        String substitution = substitute(square, mes);

        // dimensions of transposition array
        int transY = keyword.length();
        int transX = substitution.length()/keyword.length()+1;

        char [][] newSquare = new char[transX][transY];

        // fills in the transposition square
        counter = -1;
        for (int i=0; i< transX; i++){
            for (int j=0; j< transY; j++){
                counter++;
                if (counter < substitution.length())
                    newSquare[i][j] = substitution.charAt(counter);
            }
        }

        // the keyword as a character array
        char [] keyArr = keyword.toCharArray();

        // switching columns based on a bubble sort

        boolean repeat = true;

        while (repeat){
            repeat = false;

            for (int i=0; i<keyArr.length-1; i++){
                if (keyArr[i+1] < keyArr[i]){
                    repeat = true;

                    //dealing with the keyArr
                    char temp = keyArr[i+1];
                    keyArr[i+1] = keyArr[i];
                    keyArr[i] = temp;

                    // dealing with the newSquare array
                    for (int n = 0; n < transY -1 ; n++){
                        temp = newSquare[n][i+1];
                        newSquare[n][i+1] = newSquare[n][i];
                        newSquare[n][i] = newSquare[n][i];
                        newSquare[n][i] = temp;
                    }
                }
            }
        }


        String result = "";
        StringBuilder sb = new StringBuilder(result);

        for (int i=0; i< transX; i++){
            for (int j=0; j< transY; j++){
                if (newSquare[i][j] != '\0')
                    sb.append(newSquare[i][j]);

            }
        }

        for (int i=0; i< sb.toString().length(); i++){
            System.out.print(sb.toString().charAt(i));
            if (i %2 == 1){
                System.out.print(" ");
            }
        }
        System.out.println();



    }

    // must contain exactly 36 characters
    // must contain all unique characters
    // must contain a-z/A-Z and 0-9

    public static boolean validKeyphrase(String s){
        if (s.length() != 36){
            return false;
        }
        String S = s.toLowerCase();

        Set<Character> foo = new HashSet<>();
        for (int i=0; i< S.length(); i++){
            foo.add(S.charAt(i));
        }

        if (foo.size() != S.length()){
            return false;
        }

        for (int i='a'; i<='z'; i++){
            if (foo.remove((char) i)){}
            else
                return false;
        }

        for (int i='0'; i<='9'; i++){
            if (foo.remove((char) i)){}
            else
                return false;
        }

        if (!foo.isEmpty())
            return false;

        return true;
    }

    public static String substitute(char[][] arr, String s){
        String result = "";
        final char[] cipher = {'A', 'D', 'F', 'G', 'V', 'X'};

        for (int k = 0; k < s.length(); k++){
            arrLoop: {
                for (int i=0; i< 6; i++){
                    for (int j=0; j< 6; j++){
                        if (s.charAt(k) == arr[i][j] ){
                            result += cipher[i];
                            result += cipher[j];
                            break arrLoop;
                        }
                    }
                }
            }
        }

        return result;
    }
}
\$\endgroup\$
5
  • 3
    \$\begingroup\$ Welcome to Code Review, please add link, documentation to the explanation of algorithm you had implemented. \$\endgroup\$ Sep 5, 2020 at 6:14
  • 1
    \$\begingroup\$ What [needs fixing]? For starters, the acronym in the post body. (Then, as of 2020/9/5, CR is one of the sites needing a newline following a ~~~ or ``` closing a code block.) \$\endgroup\$
    – greybeard
    Sep 5, 2020 at 7:01
  • \$\begingroup\$ @greybeard I usually remove the code-fencing when it goes wrong like that and simply indent-all. The tag takes care of the code-highlighting. See edit, Ctrl+K is magic. \$\endgroup\$
    – Mast
    Sep 5, 2020 at 11:10
  • \$\begingroup\$ (@Mast one advantage of fences is copy&paste between IDE & SE.) \$\endgroup\$
    – greybeard
    Sep 5, 2020 at 11:16
  • \$\begingroup\$ @greybeard My IDE can un-indent with shift-tab. The whole thing at once. A lot of modern editors can (Notepad++ has had it standard for years now). \$\endgroup\$
    – Mast
    Sep 5, 2020 at 11:17

2 Answers 2

5
\$\begingroup\$

I have some suggestions for your code.

Always add curly braces to loop & if

In my opinion, it's a bad practice to have a block of code not surrounded by curly braces; I saw so many bugs in my career related to that, if you forget to add the braces when adding code, you break the logic / semantic of the code.

Avoid using C-style array declaration

In the main method, you declared a C-style array declaration with the args variable.

before

String args[]

after

String[] args

In my opinion, this style is less used and can cause confusion.

Extract some of the logic to methods.

When you have logic that does the same thing, you can generally move it into a method and reuse it.

In your main method, you can extract most of the logic that asks the user a question to new methods; this will shorten the method and make the code more readable.

I suggest the following refactor:

  1. Create a new method askUserAndReceiveAnswer that ask a question as a string and return a string with the answer.
private static String askUserAndReceiveAnswer(Scanner scan, String s) {
   System.out.print(s);
   return scan.nextLine();
}

This method can be reused 3x time in your code.

  1. Create a new method that asks the user, for a valid keyphrase.
private static String askUserForValidKeyPhrase(Scanner scan) {
   String keyphrase = askUserAndReceiveAnswer(scan, "Enter keyphrase for Polybius square: ");

   while (!validKeyphrase(keyphrase)) {
      System.out.print("Enter valid keyphrase: ");
      keyphrase = scan.nextLine();
   }
   return keyphrase;
}
  1. Create a new method that asks the user, for a valid keyword.
private static String askUserForValidKeyword(Scanner scan, String mes) {
   String keyword = askUserAndReceiveAnswer(scan, "Enter keyword for transposition: ");

   while (keyword.length() <= 1 && keyword.length() > mes.length()) {
      System.out.println("Keyword length must match message length.");
      System.out.print("Enter keyword: ");
      keyword = scan.nextLine();
   }
   return keyword;
}

Use java.lang.StringBuilder to concatenate String in a loop.

It's generally more efficient to use the builder in a loop, since the compiler is unable to make it efficient in a loop; since it creates a new String each iteration. There are lots of good explanations with more details on the subject.

Cipher#substitute Before

public static String substitute(char[][] arr, String s) {
   String result = "";
   final char[] cipher = {'A', 'D', 'F', 'G', 'V', 'X'};

   for (int k = 0; k < s.length(); k++) {
      arrLoop: {
         for (int i = 0; i < 6; i++) {
            for (int j = 0; j < 6; j++) {
               if (s.charAt(k) == arr[i][j]) {
                  result += cipher[i];
                  result += cipher[j];
                  break arrLoop;
               }
            }
         }
      }
   }

   return result;
}

after

public static String substitute(char[][] arr, String s) {
   StringBuilder result = new StringBuilder();
   final char[] cipher = {'A', 'D', 'F', 'G', 'V', 'X'};

   for (int k = 0; k < s.length(); k++) {
      arrLoop: {
         for (int i = 0; i < 6; i++) {
            for (int j = 0; j < 6; j++) {
               if (s.charAt(k) == arr[i][j]) {
                  result.append(cipher[i]);
                  result.append(cipher[j]);
                  break arrLoop;
               }
            }
         }
      }
   }

   return result.toString();
}

Instead of having empty body in a condition, invert the logic.

In your code, you have multiple conditions that have an empty body; I highly suggest that you invert the logic to remove the confusion those can create.

Cipher#validKeyphrase

Before

for (int i = 'a'; i <= 'z'; i++) {
   if (foo.remove((char) i)) {

   } else {
      return false;
   }
}

for (int i = '0'; i <= '9'; i++) {
   if (foo.remove((char) i)) {

   } else {
      return false;
   }
}

After

for (int i = 'a'; i <= 'z'; i++) {
   if (!foo.remove((char) i)) {
      return false;
   }
}

for (int i = '0'; i <= '9'; i++) {
   if (!foo.remove((char) i)) {
      return false;
   }
}

Simplify the boolean conditions.

This can be simplified

if (!foo.isEmpty()) {
   return false;
}

return true;

to

return foo.isEmpty();
\$\endgroup\$
2
\$\begingroup\$

Here's my nitpick on code practices. You should really be more consistent and more neat with regard to code style. Furthermore, you should definitely use more methods, better error reporting and better keywords.

As for the design, I would expect to be able to instantiate a Cipher (e.g. a constructor with the square as input) and then have non-static encrypt and decrypt methods on them that take a message and identically sized passphrase. Those methods in turn should be subdivided using private methods.


public class Cipher {

That's not specific enough for a class name.


System.out.print("Enter message to encrypt: ");
...
System.out.print("Enter valid keyphrase: ");

I'd make it a bit more clear what is expected from the user, e.g. that you have to enter a line, or what kind of keyphrase is acceptable.


char [][] square = new char[6][6];

That you perform the user interface within the main is somewhat acceptable, but the business logic should not be in the main method.

The variables 6 should be in a constant or two.


char[] letters = keyphrase.toCharArray();

Later on we'll find that the letters should also contain digits. We call those alphanumericals (alphaNumericals).


int counter = -1;
...
square[i][j] = letters[counter];

Try and avoid invalid values. For instance, in this case letters[counter++] would have let you start with a zero.


int transX = substitution.length()/keyword.length()+1;

Always use spaces around operators, e.g. substitution.length() / keyword.length() +1;.


// dimensions of transposition array
int transY = keyword.length();

I'm a bit worried about the variable naming here, transY doesn't sound like a dimention to me. And the fact that you need to prefix trans indicates that you should have created a method (see next comment).


// fills in the transposition square

If you have to make such a comment then you might as well create a method, e.g. fillTranspositionSquare() right?


for (int i=0; i< transX; i++){

For sure, if transX is the maximum for x, you're not naming your variable i, right?


String result = "";

This is definitely a code smell. Assigning null or an empty string is almost never needed.

Also, this is where you got bored explaining your code in comments. It would not be necessary if you'd had used well named methods.


StringBuilder sb = new StringBuilder(result);

Now your StringBuilder has a capacity of zero character, in all likelihood. Instead, you already know how large it will be in the end, right? So calculate the size beforehand and use the StringBuilder(int capacity) constructor.


if (s.length() != 36){

Never use literals like that. First of all, 36 is 6 x 6. Just use the dimensions to calculate that number, and if it is indeed static, put it in a constant.


String S = s.toLowerCase();

You already had an s and decided to use S for a lowercase string? Seriously? And why is s not called keyphrase? Hint: you can use simple names while typing, but modern IDE's will let you rename variables afterwards. That way you can type concisely and make it more verbose afterwards.


return false;

No, here you should have a more complex result, e.g. an enum to indicate the kind of failure. Just returning false for any kind of failure won't allow you to indicate to the user what is wrong.


public static String substitute(char[][] arr, String s){

Wait, the polybiusSquare has become arr? Why is that?


String result = "";

Mentioned already, here String resultBuilder = new StringBuilder(s.length()) would certainly be better.


arrLoop: {

If you need labels then you're doing it wrong, most of the time. Note that the label is for the for loop, so the brace is not necessary. If you ever need to use a label, make it fully upper case. However, in this case the double for loop can easily be put inside a separate method, so it is not required.

Note that the spacing for < is completely inconsistent. Not using enough spacing is bad enough, having an inconsistent style is considered worse.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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