关于加密:Java:使用未知位置解密凯撒密码

Java: Decrypting Caesar Cipher with Unknown Positions

描述:我想提出一个程序,该程序将破坏使用Caesar Cipher算法的加密消息。听起来很容易,问题在于您必须弄清楚用来加密邮件的位置是什么,以便对编码后的邮件进行解密。
因此,我有一个称为public void train(String trainingFileName)的方法,该方法读取其中包含大量文本的文件,以确定存储在double array []中的每个lowercase alphabetical English character (a - z)的频率。该方法效果很好,因此除非您愿意,否则不必查看,但是我确实在public int decrypt(String cipherTextFileName, String outputFileName)方法遇到麻烦的方法中使用了大部分代码。
当将Caesar Cipher设置为3个位置时,该代码可以很好地工作,但是其他任何情况都会给我带来很多问题。
我的public int decrypt(String cipherTextFileName, String outputFileName)方法中有一个do-while循环,该循环将解密从0个位置开始的编码消息,然后使用我正在使用的"距离"公式(注意:我不能使用任何其他公式)来找到最小值我的加密邮件中knownFrequenciesobservedFreq之间的距离。现在,我将do-while循环设置为distanceless than 0.6的位置,然后停止循环。理论上,当我在凯撒密码中拥有正确的位置数时,距离应低于该值。

问题:numberOfPositions为3时,程序运行良好,但是当我使用未在凯撒密码中使用3个位置的加密消息时,distance永远不会低于1,而在调试模式下,当我将numberOfPositions设置为解密邮件应该是什么,该邮件仍处于加密状态。

问题:如何更好地实现此方法,以使我不对"硬"值进行测试以停止do-while循环?我尝试使用Math.min(),但这不起作用。为什么我不能用Caesar Cipher(凯撒密码)的位置而不是3解码消息。

我现在将向您展示我的代码。如果要在系统上进行测试。您将需要3个文本文件。一个文件必须很长,里面有一堆单词……至少1000个。该文件将通过train方法读取。您需要一个带有加密消息的文件,以及另一个文件,程序才能写入解密的消息。

这是首先使用凯撒密码的3个位置,然后使用5个位置的加密消息。

Wkh surjudp zdv krvwhg eb dfwru Slhufh Eurvqdq dqg kdg frpphqwdub iurp pdqb Kroobzrrg dfwruv dqg iloppdnhuv Prylh txrwdwlrqv wkdw ylhzhuv xvh lq wkhlu rzq olyhv dqg vlwxdwlrqv

Ymj uwtlwfr bfx mtxyji gd fhytw Unjwhj Gwtxsfs fsi mfi htrrjsyfwd kwtr rfsd Mtqqdbtti fhytwx fsi knqrrfpjwx Rtanj vztyfyntsx ymfy anjbjwx zxj ns ymjnw tbs qnajx fsi xnyzfyntsx

解密时应该说:
The program was hosted by actor Pierce Brosnan and had commentary from many Hollywood actors and filmmakers Movie quotations that viewers use in their own lives and situations

好了,这是我编写的课程(您将需要所有导入),我要感谢提前提供帮助的任何人:

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]);
}
}