Java vigenere cipher performance issues - java

I have made a vigenere encryption/decryption program which seems to work as I intended, however running my encryption/decryption on a very large text file (500,000 characters aprox) takes 2-4minutes. I have looked through my code and cannot see what operations might be slowing it down. Anyone have any ideas how I could speed this up?
Code:
public static String encrypt(String text, String key)
{
String cipherText = "";
text = text.toLowerCase();
for(int i = 0; i < text.length(); i++)
{
System.out.println("Count: "+ i); //I just put this in to check the
//loop wasn't doing anything unexpected
int keyIndex = key.charAt(i%key.length()) - 'a';
int textIndex = text.charAt(i) - 'a';
if(text.charAt(i) >= 'a' && text.charAt(i) <= 'z') { //check letter is in alphabet
int vigenere = ((textIndex + keyIndex) % 26) + 'a';
cipherText = cipherText + (char)vigenere;
} else
cipherText = cipherText + text.charAt(i);
}
}
return cipherText;
}
Prior to running the encrypt I have a method which reads the text file to a String using Scanner. This String plus a predefined key are used to create the encrypted text.
Thanks.
ANSWER
Thanks to RC - it was my string concatenation taking the time. If anyone else is interested this is my updated code which works quickly now:
public static String encrypt(String text, String key)
{
StringBuilder cipher = new StringBuilder();
for(int i = 0; i < text.length(); i++)
{
int keyIndex = key.charAt(i%key.length()) - 'a';
int textIndex = text.charAt(i) - 'a';
if(text.charAt(i) >= 'a' && text.charAt(i) <= 'z') {
int vigenere = ((textIndex + keyIndex) % 26) + 'a';
cipher.append((char)vigenere);
} else {
cipher.append(text.charAt(i));
}
}
return cipher.toString();
}

Append to a StringBuilder instead of creating new String instances.
You want to do a
buffer.append((char)vigenere);
instead of a cipherText = cipherText + (char)vigenere;

At present you are doing
for(int i = 0; i < text.length(); i++){
...
int keyIndex = key.charAt(i%key.length()) - 'a';
...
}
You can try to remove the calucation of the keyIndex from the for-loop and realize it in a preprocessing step. For example, you can store the keyIndex values/ characters in a separate array and access the array contents in your original loop. This should save you some calculation steps.

Related

Brute force decryption for a Caesar Cipher

This is my code on decryption with brute force with the classic Caesar Cipher. The decryption with brute force shows weird results, as when I set a key of 3, it posts the same decrypted string multiple times. In other keys, such as 7, it does not even show the correct decrypted string.
My method on decrypting with brute force is to change every letter in the encrypted message according to the alphabet, therefore there is a for loop of 26 times, as well as another for loop of the message length. It uses the StringBuilder setCharAt method to change the characters in the string.
This is my code only on the decryption using brute force:
void decryptbruteforce(String encryptmessage) {
//Get the standard alphabet
String standalpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
//Convert this message to uppercase
String encryptmessageupper = encryptmessage.toUpperCase();
StringBuilder sbdecrypt = new StringBuilder(encryptmessageupper);
int key;
int i;
int index;
char currentchar;
char newchar;
//Loop through the 26 keys in the alphabet.
for (key = 1; key < 27; key++) {
//Loop through the encrypted message
for (i = 0; i < sbdecrypt.length(); i++) {
//Get the encrypted character
currentchar = sbdecrypt.charAt(i);
//Get the index in the alphabet
index = standalpha.indexOf(currentchar);
//If the currentchar is in the alphabet
if (index != -1) {
//Reduce the character by the key in the alphabet
index = index - key;
//If the character goes below 0, aka 'A', go back to the end of the alphabet
if (index < 0) {
index = index + 26;
//Get the new character in the alphabet
newchar = standalpha.charAt(index);
//Set the character in the stringbuilder
sbdecrypt.setCharAt(i, newchar);
}
else {
//Get the new character in the alphabet
newchar = standalpha.charAt(index);
//Set the character in the stringbuilder
sbdecrypt.setCharAt(i, newchar);
}
}
}
//Print the key and the resulting string
System.out.println("Key: " + key + " Decrypted String: " + sbdecrypt);
}
}
This is my output:
Caesar Cipher key of 3
Caesar Cipher key of 7
This is the link to my whole code if it helps: https://gist.github.com/cliven-hew/35e9458c24d5f5b1ace97b7146ec429a
Your issue is that you are always editing the same StringBuilder sbdecrypt object. So when you did your shift with a key 1, your next attempt was not based on shifting the original string, it was the previously shifted string. This is why you go from N.... to L.... while skipping M.... and this pattern continues throughout.
You can fix this by adding sbdecrypt = new StringBuilder(encryptmessageupper); after your for (key = 1; key < 27; key++) line.
public static void decryptbruteforce(String encryptmessage) {
String standalpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
String encryptmessageupper = encryptmessage.toUpperCase();
StringBuilder sbdecrypt = new StringBuilder(encryptmessageupper);
int key;
int i;
int index;
char currentchar;
char newchar;
for (key = 1; key < 27; key++) {
//Ensure you do your shift on the same string each time.
//This makes a new object each time
sbdecrypt = new StringBuilder(encryptmessageupper);
for (i = 0; i < sbdecrypt.length(); i++) {
currentchar = sbdecrypt.charAt(i);
index = standalpha.indexOf(currentchar);
if (index != -1) {
index = index - key;
if (index < 0) {
index = index + 26;
}
newchar = standalpha.charAt(index);
sbdecrypt.setCharAt(i, newchar);
}
}
System.out.println("Key: " + key + " Decrypted String: " + sbdecrypt);
}
}

Can't compare 2 strings if a letter in first string exists in second string - java

However I had an assignment of programming in java related to a text i already have under (text).
the function is supposed to as below
getEncryptedText(int shift)
return a string representation of ciphertext given that the text to be manipulated is the plaintext using Caesar Cipher.
The number of rotation is depend on the shift value;
positive shift value represent the right rotation while negative shift value represent left
rotation. However, unlike explain in Wikipedia, this method used following string as
plain:
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
Other characters than above will be treated as it is (i.e. will not been encrypted)
*Further reading: https://en.wikipedia.org/wiki/Caesar_cipher
So this is the class method I have made so far and wanted to know how can i keep the text chars which aren't included in the plaintext i have such as "!,#,#,$,%... and so on". So far i tried everything but couldn't make it but the rest seems fine!
public String getEncryptedText(int shift) {
String ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
String cipherText = "";
for (int i = 0; i < text.length(); i++){
{
int charPosition = ALPHABET.indexOf(text.charAt(i));
if(text.charAt(i) == ' ') {
cipherText += " ";
}
else
{
int keyVal = (shift + charPosition) % 62;
char replaceVal = ALPHABET.charAt(keyVal);
cipherText += replaceVal;
}
}
}
return cipherText;
}
Consider modifying your if statement and using the StringBuilder class:
class Main {
public static void main(String[] args) {
CesarCypherHelper cesarCypherHelper = new CesarCypherHelper();
System.out.println(cesarCypherHelper.getEncryptedText("Hello World!", 2));
System.out.println(cesarCypherHelper.getEncryptedText("Hello World!", 64));
}
}
class CesarCypherHelper {
public String getEncryptedText(String text, int shift) {
String ALPHABET =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
StringBuilder encryptedText = new StringBuilder();
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
int charPosition = ALPHABET.indexOf(ch);
if (charPosition == -1) {
encryptedText.append(ch);
} else {
int keyVal = (shift + charPosition) % ALPHABET.length();
encryptedText.append(ALPHABET.charAt(keyVal));
}
}
return encryptedText.toString();
}
}
Output:
Jgnnq Yqtnf!
Jgnnq Yqtnf!

What is best approach to encrypting an encryption?

For my CPSC class, I need to make encryption code using caesar cipher. That is done. The next part is taking the encrypted message and cycling the secretKey to be added to the encrypted message. For example, if I encrypt "Hello!" using a shift of 13, it will turn into "Uryyb!". Then I must shift "U" by one, "r" by three, "y" by one, etc... which will encrpt into "Vuzbc!" I am in a beginner class so I do not know all the cool tips and tricks. Only possible solution I know is to take the outcome of the caesar cipher and somehow index the secret key to be added to the outcome.
Here is my code that I have so far:
public class Cipher {
private int secretKey;
private int superSecretKey;
public static void main(String [] args)
{
Cipher cipher = new Cipher(1);
}
public Cipher(int myKey) {
secretKey = myKey;
}
public String caesarEncrpyt (String s) {
String r = "";
for(int i = 0; i < s.length(); i++) {
char c = (char) (s.charAt(i));
if(Character.isLetter(c)) {
if (Character.isUpperCase(c)) {
r += (char) ('A' + (c - 'A' + secretKey) % 26);
}
else {
r += (char) ('a' + (c - 'a' + secretKey) % 26);
}
}
else {
r += c;
}
}
return r;
}
public String caesarDecrypt (String s) {
String r = "";
for(int i = 0; i < s.length(); i++) {
char c = (char) (s.charAt(i));
if(Character.isLetter(c)) {
if (Character.isUpperCase(c)) {
r += (char) ('A' + (c - 'A' - secretKey) % 26);
}
else {
r += (char) ('a' + (c - 'a' - secretKey) % 26);
}
}
else {
r += c;
}
}
return r;
}
public String augustusEncrypt (String s) {
String r = "";
for(int i = 0; i < s.length(); i++) {
char c = (char) (s.charAt(i));
if(Character.isLetter(c)) {
if (Character.isUpperCase(c)) {
r += (char) ('A' + (c - 'A' + secretKey) % 26);
}
else {
r += (char) ('a' + (c - 'a' + secretKey) % 26);
}
}
else {
r += c;
}
}
return r;
}
augustusEncrypt is a copy and paste of caesarEncrypt. I've been moving some stuff around hoping for a solution. Thanks in advance!
Edit: I may not have explained this correctly, if you have a question, I'll be here.
Write a function, call it toDigits which will take an int (or a long) and return an array of ints corresponding to the digits of the input. to toDigits(13)=>{1,3} and toDigits(4834)=>{4,8,3,4}, etc
Then write a function encryptChar, taking a char and an int and encrypting the char by that int. (encryptChar('e', 1)=>'f', encryptChar('a',28)=>c, etc)
Then you can loop over the characters of the message and the digits in this array, passing the values to encryptChar and use the results to assemble your encrypted message. In a loose sort of pseudocode:
fn encryptMessage(message, key):
key_array = toDigits(key)
output = ""
for i in length (message):
output.append(encryptChar(message[i], key_array[i % length(key_array)]))
Best practices and conventions for encrypting aside, the solution is simple.
You have letters A-Z and a-z which already perform the correct loop when we step off of the alphabet, and you believe you have that working correctly. All you need to do is add 1 before you loop around.
It would be something like this (warning: untested):
('A' + ((c+1) - 'A' + secretKey) % 26)

Need help troubleshooting my VigenereCipher java code

Okay so I created this code last year for a class project and I remember it working correctly. I now need it to implement a text cipher but for some reason it does not work correctly. It will encrypt, but when I try to decrypt only the first two letters are correct. The rest of it is all wrong. It is very simple, it is a command-line program where the first argument is whether it is encrypting(-e) or decrypting(-d), second argument is the key and third argument is the text you will encrypt. It is similar to a caesar cipher except it takes each character as a reference when adding to each individual char in the string. Can anyone tell me what is wrong, I do not understand why it does not work anymore and I need it for a project.
import java.util.*;
public class VigenereCipher
{
public static void main(String[] args)
{
Scanner scan = new Scanner (System.in);
String key = "";
String ori = "";
String res = "";
if(!(args.length == 0))
{
if (args[0].equals("-e"))
{
key = args[1];
ori = args[2];
encrypt(ori, key);
System.out.println(encrypt(ori, key));
}
else if (args[0].equals("-d"))
{
key = args[1];
ori = args[2];
decrypt(ori, key);
System.out.println(decrypt(ori, key));
}
else
{
System.out.print("Usage: java VigenereCipher [-e,-d] key text");
}
}
}
static String encrypt(String text, final String key)
{
String res = "";
text = text.toUpperCase();
for (int i = 0, j = 0; i < text.length(); i++)
{
char c = text.charAt(i);
if (c < 'A' || c > 'Z') continue;
res += (char)((c + key.charAt(j) - 2 * 'A') % 26 + 'A');
j = ++j % key.length();
}
return res;
}
static String decrypt(String text, final String key)
{
String res = "";
text = text.toUpperCase();
for (int i = 0, j = 0; i < text.length(); i++)
{
char c = text.charAt(i);
if (c < 'A' || c > 'Z') continue;
res += (char) ((c - key.charAt(j) + 26) % 26 + 'A');
j = ++j % key.length();
}
return res;
}
}
You are supposed to be able to encrypt a string of text with a key then decrypt the output from the encryption using the same key for example: java VigenereCipher -e hello hello will give me "UOCCI" as output but when I take that output and do java VigenereCipher -d hello UOCCI it gives me "HE225" as my output and not "HELLO".
You forgot that your key also needs to be in the same alphabet. So if you supply a lowercase key your algorithm will fail.
This will become abundantly clear when you split your algorithm in parts, e.g. I just went through:
int im = c + key.charAt(j) - 2 * 'A';
res += (char)(im % 26 + 'A');
with my debugger and presto, the problem showed up.

Vigenère cipher in Java for all UTF-8 characters

I have this simple function for encrypting strings via Vigenère in Java. I omitted the decryption as this is just a "-" instead of the "+" in the line where the new value is calculated.
But this function works only for the normal alphabet A-Z. How can I change the function so that it supports lowercase letters as well as uppercase letters and all other UTF-8 chars?
public static String vigenere_encrypt(String plaintext, String key) {
String encryptedText = "";
for (int i = 0, j = 0; i < plaintext.length(); i++, j++) {
if (j == key.length()) { j = 0; } // use key again if end reached
encryptedText += (char) ((plaintext.charAt(i)+key.charAt(j)-130)%26 + 65);
}
return encryptedText;
}
Thank you very much for your help!
Well, you asked for it and I felt like puzzling, but print out the cipher text and you will know what you just asked for...
public static String vigenereUNICODE(String plaintext, String key, boolean encrypt) {
final int textSize = plaintext.length();
final int keySize = key.length();
final StringBuilder encryptedText = new StringBuilder(textSize);
for (int i = 0; i < textSize; i++) {
final int plainNR = plaintext.codePointAt(i);
final int keyNR = key.codePointAt(i % keySize);
final long cipherNR;
if (encrypt) {
cipherNR = ((long) plainNR + (long) keyNR) & 0xFFFFFFFFL;
} else {
cipherNR = ((long) plainNR - (long) keyNR) & 0xFFFFFFFFL;
}
encryptedText.appendCodePoint((int) cipherNR);
}
return encryptedText.toString();
}
EDIT: Please don't ever use in production code, as I haven't got a clue if the code points can indeed be encoded/decoded. Not all points have been defined, as far as I know, and the standard is a moving target.
If full unicode support is not possible and you have to define your list of valid characters, anyway, why not just use a function like this?
public static String vigenere_cipher(String plaintext, String key, boolean encrypt) {
String alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ,.-"; // including some special chars
final int alphabetSize = alphabet.length();
final int textSize = plaintext.length();
final int keySize = key.length();
final StringBuilder encryptedText = new StringBuilder(textSize);
for (int i = 0; i < textSize; i++) {
final char plainChar = plaintext.charAt(i); // get the current character to be shifted
final char keyChar = key.charAt(i % keySize); // use key again if the end is reached
final int plainPos = alphabet.indexOf(plainChar); // plain character's position in alphabet string
if (plainPos == -1) { // if character not in alphabet just append unshifted one to the result text
encryptedText.append(plainChar);
}
else { // if character is in alphabet shift it and append the new character to the result text
final int keyPos = alphabet.indexOf(keyChar); // key character's position in alphabet string
if (encrypt) { // encrypt the input text
encryptedText.append(alphabet.charAt((plainPos+keyPos) % alphabetSize));
}
else { // decrypt the input text
int shiftedPos = plainPos-keyPos;
if (shiftedPos < 0) { // negative numbers cannot be handled with modulo
shiftedPos += alphabetSize;
}
encryptedText.append(alphabet.charAt(shiftedPos));
}
}
}
return encryptedText.toString();
}
This should be a very short and working version. And the alphabet can easily be stored in a string that can always be extended (which results in different ciphertexts).
Another answer, that does do the Vigenere cipher on upper & lower case characters, simply inserting the other characters. Use this technique to create multiple groups of characters to encode.
public static String vigenere(String plaintext, String key, boolean encrypt) {
final int textSize = plaintext.length();
final int keySize = key.length();
final int groupSize1 = 'Z' - 'A' + 1;
final int groupSize2 = 'z' - 'a' + 1;
final int totalGroupSize = groupSize1 + groupSize2;
final StringBuilder encryptedText = new StringBuilder(textSize);
for (int i = 0; i < textSize; i++) {
final char plainChar = plaintext.charAt(i);
// this should be a method, called for both the plain text as well as the key
final int plainGroupNumber;
if (plainChar >= 'A' && plainChar <= 'Z') {
plainGroupNumber = plainChar - 'A';
} else if (plainChar >= 'a' && plainChar <= 'z') {
plainGroupNumber = groupSize1 + plainChar - 'a';
} else {
// simply leave spaces and other characters
encryptedText.append(plainChar);
continue;
}
final char keyChar = key.charAt(i % keySize);
final int keyGroupNumber;
if (keyChar >= 'A' && keyChar <= 'Z') {
keyGroupNumber = keyChar - 'A';
} else if (keyChar >= 'a' && keyChar <= 'z') {
keyGroupNumber = groupSize1 + keyChar - 'a';
} else {
throw new IllegalStateException("Invalid character in key");
}
// this should be a separate method
final int cipherGroupNumber;
if (encrypt) {
cipherGroupNumber = (plainGroupNumber + keyGroupNumber) % totalGroupSize;
} else {
// some code to go around the awkward way of handling % in Java for negative numbers
final int someCipherGroupNumber = plainGroupNumber - keyGroupNumber;
if (someCipherGroupNumber < 0) {
cipherGroupNumber = (someCipherGroupNumber + totalGroupSize);
} else {
cipherGroupNumber = someCipherGroupNumber;
}
}
// this should be a separate method
final char cipherChar;
if (cipherGroupNumber < groupSize1) {
cipherChar = (char) ('A' + cipherGroupNumber);
} else {
cipherChar = (char) ('a' + cipherGroupNumber - groupSize1);
}
encryptedText.append(cipherChar);
}
return encryptedText.toString();
}
Again, this is unsafe code as the cipher used has been broken for ages. Don't use too many 'A' characters in your keys :) But the character encoding should be sound.

Categories