Java: Decrypting Caesar Cipher with Unknown Positions
描述:我想提出一个程序,该程序将破坏使用Caesar Cipher算法的加密消息。听起来很容易,问题在于您必须弄清楚用来加密邮件的位置是什么,以便对编码后的邮件进行解密。
因此,我有一个称为
当将Caesar Cipher设置为3个位置时,该代码可以很好地工作,但是其他任何情况都会给我带来很多问题。
我的
问题:
问题:如何更好地实现此方法,以使我不对"硬"值进行测试以停止
我现在将向您展示我的代码。如果要在系统上进行测试。您将需要3个文本文件。一个文件必须很长,里面有一堆单词……至少1000个。该文件将通过
这是首先使用凯撒密码的3个位置,然后使用5个位置的加密消息。
解密时应该说:
好了,这是我编写的课程(您将需要所有导入),我要感谢提前提供帮助的任何人:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | public class CodeBreaker { public final int NUMBER_OF_LETTERS = 26; private double[] knownFrequencies = new double[NUMBER_OF_LETTERS]; public double[] getKnownFrequencies() { return knownFrequencies; } public void setKnownFrequencies(double[] knownFrequencies) { this.knownFrequencies = knownFrequencies; } /** * Method reads in a file with a lot of text in it and * then use that to figure out the frequencies of each character * * @param trainingFileName */ public void train(String trainingFileName) { try { Scanner fileIO = new Scanner(new File(trainingFileName)); int total = 0; String temp =""; while (fileIO.hasNext()) { //reading into file and storing it into a string called temp temp += fileIO.next().toLowerCase().replaceAll("[ -,!?';:.]+",""); //converting temp string into a char array } char[] c = temp.toCharArray(); total += c.length; // how many characters are in text int k = (int) 'a'; // int value of lowercase letter 'a' int[] counter = new int[NUMBER_OF_LETTERS]; for (int j = 0; j < total; j++) { for (int i = k - k; i < knownFrequencies.length; i++) { char[] d = new char[knownFrequencies.length]; d[i] = (char) (k + i); if (c[j] == d[i]) {//checking to see if char in text equals char in d array counter[i]++; knownFrequencies[i] = (double) counter[i] / total; } } } fileIO.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block System.err.println(e); System.exit(0); } } /** * Main decryption method used to take coded text from a file, figure out the positions in the CaesarCipher * and then decode it onto another file. * * @param cipherTextFileName * @param outputFileName * @return */ public int decrypt(String cipherTextFileName, String outputFileName) { Scanner fileIO; int numberOfPositions = 0; double distance = 0.000000; try { fileIO = new Scanner(new File(cipherTextFileName)); PrintWriter writer = new PrintWriter(new File(outputFileName)); String temp =""; while (fileIO.hasNext()) { //reading into file and storing it into a string called temp temp += fileIO.next().toLowerCase().replaceAll("",""); } fileIO.close(); do { distance = 0.0; int total = 0; double[] observedFreq = new double[NUMBER_OF_LETTERS]; temp = decrypt(temp, numberOfPositions); char[] c = temp.toCharArray(); //store decrypted chars into an array total += c.length; // how many characters are in text int k = (int) 'a'; // int value of lowercase letter 'a' int[] counter = new int[NUMBER_OF_LETTERS]; //use to count the number of characters in text for (int j = 0; j < total; j++) { for (int i = k - k; i < observedFreq.length; i++) { char[] d = new char[observedFreq.length]; d[i] = (char) (k + i); if (c[j] == d[i]) { //checking to see if char in text equals char in d array counter[i]++; observedFreq[i] = (double) counter[i] / total; } } } //Formula for finding distance that will determine the numberOfPositions in CaesarCipher for (int j = 0; j < knownFrequencies.length; j++) { distance += Math.abs(knownFrequencies[j] - observedFreq[j]); //This is the part of the code I am having trouble with } numberOfPositions = numberOfPositions + 1; } while (distance > 0.6); //This is the part of the code I am having trouble with Scanner fileIO2 = new Scanner(new File(cipherTextFileName)); while (fileIO2.hasNextLine()) { //reading into file and storing it into a string called temp temp = fileIO2.nextLine(); writer.println(decrypt(temp, numberOfPositions)); } writer.close(); fileIO2.close(); System.out.println(distance); } catch (FileNotFoundException e) { // TODO Auto-generated catch block System.err.println(e); System.exit(0); } return numberOfPositions; } /** * CaesarCipher decrypt and encrypt methods * * @param ciphertext * @param numberOfPositions * @return */ public String decrypt(String ciphertext, int numberOfPositions) { // TODO Auto-generated method stub return encrypt(ciphertext, -numberOfPositions); } public String encrypt(String msg, int offset) { offset = offset % 26 + 26; StringBuilder encoded = new StringBuilder(); for (char i : msg.toCharArray()) { if (Character.isLowerCase(i)) { int j = (i - 'a' + offset) % 26; encoded.append((char) (j + 'a')); } else if (Character.isUpperCase(i)) { int h = (i - 'A' + offset) % 26; encoded.append((char) (h + 'A')); } else { encoded.append(i); } } return encoded.toString(); } // barebones main method to test your code public static void main(String[] args) { // args[0] contains the filename of the training file // args[1] contains the filename of the cipher text file // args[2] contains the filename of the output file CodeBreaker cb = new CodeBreaker(); cb.train(args[0]); System.out.println(cb.decrypt(args[1], args[2])); } } |
解码凯撒密码的标准方法称为"降低字母"。本质上是蛮力解决方案;您尝试所有可能性。由于只有26个可能的键,所以并不难。
以您的示例为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | WKH SURJUDP ZDV KRVWHG ... wkh surjudp zdv krvwhg ... xli tvskveq aew lswxih ... ymj uwtlwfr bfx mtxyji ... znk vxumxgs cgy nuyzkj ... aol wyvnyht dhz ovzalk ... bpm xzwoziu eia pwabml ... cqn yaxpajv fjb qxbcnm ... dro zbyqbkw gkc rycdon ... esp aczrclx hld szdepo ... ftq bdasdmy ime taefqp ... gur cebtenz jnf ubfgrq ... hvs dfcufoa kog vcghsr ... iwt egdvgpb lph wdhits ... jxu fhewhqc mqi xeijut ... kyv gifxird nrj yfjkvu ... lzw hjgyjse osk zgklwv ... max ikhzktf ptl ahlmxw ... nby jlialug qum bimnyx ... ocz kmjbmvh rvn cjnozy ... pda lnkcnwi swo dkopaz ... qeb moldoxj txp elpqba ... rfc npmepyk uyq fmqrcb ... sgd oqnfqzl vzr gnrsdc ... the program was hosted ... uif qsphsbn xbt iptufe ... vjg rtqitco ycu jquvgf ... wkh surjudp zdv krvwhg ... |
对于一个人来说,只需选择26条就能找到正确的线就足够简单了。对于计算机而言,难度更大。您计算字母频率的想法很好。您也可以将字母对标记为" qx",并将标记对标记为" th"。计算所有26个可能结果的分数,并选择得分最高的结果。只要您对计分方法进行了很好的调整,就很有可能找到正确的解决方案。
接受rossum的建议,并意识到我的第一堂课真是一团糟,没人能理解。这次我使用一堆方法重写了该类,而不是将所有内容都整合到一个或两个方法中,现在该类可以完美地工作了。我想提出任何使代码更有效的建议。在我看来,这有点多余,因此欢迎提出任何改进建议。这是针对已过期的班级分配的,因此此代码将作为参考。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | public class CodeBreaker { //Setting up instance variables and setter/getter methods public final int NUMBER_OF_LETTERS = 26; private int numberOfPositions = 0; private double[] knownFrequencies = new double[NUMBER_OF_LETTERS]; private double[] observedFreq = new double[NUMBER_OF_LETTERS]; public double[] getKnownFrequencies() { return knownFrequencies; } public void setKnownFrequencies(double[] knownFrequencies) { this.knownFrequencies = knownFrequencies; } //This method reads text from a long file, breaks it down into individual characters, and stores it in the knownFrequencies array public void train(String trainingFileName) { String tempString =""; double totalChars = 0.0; try { @SuppressWarnings("resource") Scanner FileIO = new Scanner(new File(trainingFileName)).useDelimiter("[ *-,!?.]+"); //reading text from a file using //the delimiter so we get all of the contents while(FileIO.hasNext()){ tempString += FileIO.next().toLowerCase();//storing contents into a string, all lower case } FileIO.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block System.err.println(e); System.exit(0); } //Figuring out total number of English letters(a-z) used to determine the frequencies for(int j = 0; j < tempString.length(); j++){ char ch = tempString.charAt(j); if(Character.isAlphabetic(ch)){ totalChars++; } } //Initializing the knownFrequencies array with each individual letter count a-z for (int k = 0; k <= tempString.length()-1; k++){ char ch = tempString.charAt(k); double chValue = (double) ch; if (Character.isAlphabetic(ch)) { if(chValue >= 97 && chValue <= 122){ knownFrequencies[ch - 'a']++; } } } //Divide the individual letter counts by the total to get a decimal number //for the frequency and store that into the knownFrequencies array. for (int i = 0; i < knownFrequencies.length; i++) { if(knownFrequencies[i] > 0){ knownFrequencies[i] = knownFrequencies[i]/totalChars; } } } //This method does practically the same thing in the train method except it doesn't read from a file, and it compiles all of the //cipher text characters to find the frequencies that will be used later to determine the key public void setObservedFreq(String tempString)//String parameter takes in the cipher text { //Finding total number of lower case English letters (a-z) double totalChars = 0.0; for(int j = 0; j < tempString.length(); j++){ char ch = tempString.charAt(j); if(Character.isAlphabetic(ch)){ totalChars++; } } //Initializing observedFreq with the number of letters in the string. for (int k = 0; k <= tempString.length()-1; k++){ char ch = tempString.charAt(k); double chValue = (double) ch; if (Character.isAlphabetic(ch)) { if(chValue >= 97 && chValue <= 122){ observedFreq[ch - 'a']++; } } } //Re-initializing with a decimal frequency. for (int i = 0; i < NUMBER_OF_LETTERS; i++) { if(observedFreq[i] > 0){ observedFreq[i] = observedFreq[i]/totalChars; } } } //This method subtracts the absolute value of the observedFreq from the knownFrequencies, sum all those together and store it //in a variable that will be return in the method. The smallest distance value means the cipher text has been decoded. public double findDistance(){ double distance = 0.0; for(int x = 0; x < NUMBER_OF_LETTERS; x++){ distance += Math.abs(knownFrequencies[x] - observedFreq[x]); } return(distance); } //This method finds a int value that will be used as the key to decipher the cipherText public int findNumberOfPositions(String cipherText){ int smallestIndex = 0; double [] indexArray = new double [NUMBER_OF_LETTERS]; //We are going through all possible shifts (up to 25) to see and storing those distances into the indexArray. for(int i = 0; i < NUMBER_OF_LETTERS; i ++){ setObservedFreq(decrypt(cipherText,i)); indexArray[i] = findDistance(); } //Determine which index in the array has the smallest distance double currentValue = indexArray[0]; for (int j=0; j < NUMBER_OF_LETTERS; j++) { if (indexArray[j] < currentValue) { currentValue = indexArray[j]; smallestIndex = j; } } return smallestIndex; //The index is returned and will be used for the key when the message is decrypted } //Read in a file that contains cipher text decrypt it using the key that was found in the findNumberOfPositions method //then write the plain text into a output file. public int decrypt(String cipherTextFileName, String outputFileName) { String tempString =""; try { @SuppressWarnings("resource") Scanner FileIO = new Scanner(new File(cipherTextFileName)).useDelimiter("[ *-,!?.]+"); while(FileIO.hasNext()){ tempString += FileIO.next().toLowerCase();//read into a file and store lower case text it into tempString } FileIO.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block System.err.println(e); System.exit(0); } numberOfPositions = findNumberOfPositions(tempString); //call our findNumberOfPositions method to find the key try { Scanner scan = new Scanner(new File(cipherTextFileName)); PrintWriter writer = new PrintWriter(new File(outputFileName)); while(scan.hasNextLine()){ writer.println(decrypt(scan.nextLine(), numberOfPositions)); //key is then used to decrypt the message and gets //printed into another file. } writer.close(); scan.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block System.err.println(e); System.exit(0); } return numberOfPositions; } //Caesar Cipher encrypt and decrypt methods public String decrypt(String ciphertext, int numberOfPositions) { // TODO Auto-generated method stub return encrypt(ciphertext, -numberOfPositions); } public String encrypt(String msg, int offset){ offset = offset % 26 + 26; StringBuilder encoded = new StringBuilder(); for (char i : msg.toCharArray()) { if (Character.isLowerCase(i)) { int j = (i - 'a' + offset) % 26; encoded.append((char) (j + 'a')); } else if(Character.isUpperCase(i)){ int h = (i - 'A' + offset) % 26; encoded.append((char) (h + 'A')); } else { encoded.append(i); } } return encoded.toString(); } public static void main(String[] args) { // args[0] contains the filename of the training file // args[1] contains the filename of the cipher text file // args[2] contains the filename of the output file CodeBreaker cb = new CodeBreaker(); cb.train(args[0]); cb.decrypt(args[1], args[2]); } } |