Java, How to Implement a Shift Cipher (Caesar Cipher)

caesar shift cipher java

In the following example I encrypt just the letters (more precisely A-Z and a-z) and added the possibility to use any offset:

public static String cipher(String sentence, int offset) {
String s = "";
for(int i = 0; i < sentence.length(); i++) {
char c = (char)(sentence.charAt(i));
if (c >= 'A' && c <= 'Z') {
s += (char)((c - 'A' + offset) % 26 + 'A');
} else if (c >= 'a' && c <= 'z') {
s += (char)((c - 'a' + offset) % 26 + 'a');
} else {
s += c;
}
}
return s;
}

Here some examples:

cipher("abcABCxyzXYZ123", 1)   // output: "bcdBCDyzaYZA123"
cipher("abcABCxyzXYZ123", 2) // output: "cdeCDEzabZAB123"
cipher("abcABCxyzXYZ123", 13) // output: "nopNOPklmKLM123"

Note: Due to your code, I assumed that you just want to handle/encrypt the "ordinary" 26 letters. Which means letters like e.g. the german 'ü' (Character.isLetter('ü') will return true) remain unencrypted.

Caesar Cipher Java Program can't shift more than 23

How does the shifting in your method work? Well, it exploits the fact that a char can, in Java, also be viewed as an int, a simple number.

Because of that you can do stuff like this:

char c = 'A';                                 // Would print: A
int cAsValue = (int) c; // Would print: 65
int nextValue = cAsValue + 1; // Would print: 66
char nextValueAsCharacter = (char) nextValue; // Would print: B

or even that:

int first = (int) 'A';                // Would print: 65
int second = (int) 'D'; // Would print: 68
int third = first + second; // Would print: 133
char thirdAsCharacter = (char) third; // Would not print anything meaningful

Okay, now that we know how we can interpret char as int, let us analyze why 65 represents the character A and why 133 is nothing meaningful.

The keyword here is UTF-16. Characters in Java are encoded in UTF-16 and there are tables that list all characters of that encoding with their specific decimal number, like here.

Here is a relevant excerpt:

UTF-16 table showing characters around 'A'

This answers why 65 represents A and why 133 is nothing meaningful.


The reason why you experience strange results after some shifts is that the alphabet only has a size of 26 symbols.

I think you would expect that it starts all over again and a shifted by 26 is again a. But unfortunately your code is not smart enough, it simply takes the current character and adds the shift to it, like that:

char current = 'a';
int shift = 26;

int currentAsInt = (int) current; // Would print: 97
int shifted = currentAsInt + shift; // Would print: 123
char currentAfterShift = (char) shifted; // Would print: {

Compare that to the relevant part in the table:

UTF-16 table showing characters around '{'

So after z does not come a again but rather {.


So after the mystery was solved, let's now talk about how to fix it and make your code smarter.

You can simply check the bounds, like "if it is greater than the value for 'z' or smaller than 'a', then get it back again into the correct range". We can do so easily by using the modulo operator given by %. It divides a number by another and returns the remainder of the division.

Here is how we can use it:

char current = 'w';
int shift = 100;
int alphabetSize = 26; // Or alternatively ('z' - 'a')

int currentAsInt = (int) current; // Would print: 119
int shiftInRange = shift % alphabetSize; // Would print: 22
int shifted = currentAsInt + shiftInRange; // Would print: 141 (nothing meaningful)

// If exceeding the range then begin at 'a' again
int shiftCorrected = shifted;
if (shifted > 'z') {
shiftCorrected -= alphabetSize; // Would print: 115
}

char currentAfterShift = (char) shiftCorrected; // Would print: s

So instead of shifting by 100 we only shift be the relevant part, 22. Imagine the character going three rounds through the whole alphabet because 100 / 26 ~ 3.85. After those three rounds we go the remaining 0.85 rounds which are 22 steps, the remainder after dividing 100 by 26. That is exactly what the % operator did for us.

After going that 22 steps we could still exceed the bound, but maximally by one round. We correct that by subtracting the alphabet size. So instead of going 22 steps, we go 22 - 26 = -4 steps which emulates "going 4 steps to the end of the alphabet, then starting at 'a' again and finally going 18 steps to 's'".

Caesar Cipher With Space characters left unchanged

There is two ways of doing this: either create a positive match for the character or a negative one. In the positive match variant you first check if plainText.charAt(i) is a character that you want to keep, and in that case add it to the cipherText and continue with the loop.

In the other you can check if indexOf returns -1 indicating that the alphabet doesn't contain the character. In that case you do the same thing: add it and continue. This is the common method I've seen for the classic Ceasar "play" cipher:

// introduce a local variable, you don't want to perform charAt twice
char c = plainText.charAt(i);
int charPosition = ALPHABET.indexOf(c);
// if charPositions is -1 then it is not found
if (charPosition == -1) { // or define a constant NOT_FOUND = -1
cipherText += c;
// continue with the for loop
continue;
}

java caesar cipher code

Using indexOf is not very efficient... You can do integer arithmetic on char values to get their indices.

I included comments in the code to explain more, but this is what I came up with.

public class CaesarCipher {
// Rotate a character k-positions
public static char cipher(char c, int k) {
// declare some helping constants
final int alphaLength = 26;
final char asciiShift = Character.isUpperCase(c) ? 'A' : 'a';
final int cipherShift = k % alphaLength;

// shift down to 0..25 for a..z
char shifted = (char) (c - asciiShift);
// rotate the letter and handle "wrap-around" for negatives and value >= 26
shifted = (char) ((shifted + cipherShift + alphaLength) % alphaLength);
// shift back up to english characters
return (char) (shifted + asciiShift);
}

// Rotate a string k-positions
public static String cipher(String s, int k) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
sb.append(cipher(s.charAt(i), k));
}
return sb.toString();
}

public static void main(String[] args) {
Scanner keyboard = new Scanner(System.in);
String password;
int key;

System.out.print("Please enter a password: ");
password = keyboard.nextLine();

do {
System.out.print("Please enter a key between 1-25: ");
key = keyboard.nextInt();

if (key < 1 || key > 25) {
System.out.printf(" The key must be between 1 and 25, you entered %d.\n", key);
}
} while (key < 1 || key > 25);

System.out.println("Password:\t" + password);
String encryption = cipher(password, key);
System.out.println("Encrypted:\t" + encryption);
System.out.println("Decrypted:\t" + cipher(encryption, -key));

}
}

The output should be something like

Please enter a password: ABCDEFGHIJKLMNOPQRSTUVWXYZ
Please enter a key between 1-25: 1
Password: ABCDEFGHIJKLMNOPQRSTUVWXYZ
Encrypted: BCDEFGHIJKLMNOPQRSTUVWXYZA
Decrypted: ABCDEFGHIJKLMNOPQRSTUVWXYZ


Related Topics



Leave a reply



Submit