【问题标题】:Java: Decrypting Caesar Cipher with Unknown PositionsJava:用未知位置解密凯撒密码
【发布时间】:2014-06-24 14:36:55
【问题描述】:

描述:我想想出一个程序来破解使用凯撒密码算法的加密消息。听起来很简单,问题是您必须弄清楚用于制作加密消息的位置才能解密编码消息。 所以我有一个名为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,并且在我设置 @ 时处于调试模式987654335@应该是什么解密消息,消息仍然是加密的。

问题:如何更好地实施此方法,这样我就不会在“硬”值上测试我的distance 以停止do-while 循环?我尝试使用Math.min(),但这不起作用。为什么凯撒密码的位置不是 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

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

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

    }
}

【问题讨论】:

  • 我可以告诉你,你已经在你的帖子中付出了努力,这很好,但你的问题可能会被认为太冗长。代码和解释的每一部分都需要了解您的问题吗?尝试删除不相关的内容,这样人们就可以消化不那么不相关的信息。
  • 阅读频率分析。英文文本中最常见的字母是e,因此凯撒密码英文文本中最常见的字母很可能是'e'+n,其中n是关键。
  • 您可能会发现此代码很有用:pajhome.org.uk/crypt/vigenere.html
  • @Jeroen Vannevel 我担心这个问题有点冗长,但看​​起来很复杂,所以我想确保人们准确地理解我想要做的事情,而不是发布一些代码并说“在这里解决这个问题”没有解释。

标签: java encryption


【解决方案1】:

解码凯撒密码的标准方法称为“按字母顺序排列”。本质上是蛮力解决方案;你尝试所有的可能性。由于只有 26 个可能的键,所以并不难。

以你为例:

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 个可能结果的分数并选择得分最高的结果。只要您对评分方法进行了很好的调整,那么您就有很大的机会找到正确的解决方案。

【讨论】:

    【解决方案2】:

    接受了 rossum 的建议,并意识到我的第一堂课完全是一团糟,没人能理解。这次我使用一堆方法重写了这个类,而不是把所有东西都集中到一个或两个方法中,现在这个类可以完美地工作了。我愿意提出任何建议来提高代码效率。对我来说,这似乎有点多余,所以欢迎任何改进建议。这是一个截止日期已过的班级作业,所以这段代码供参考。

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

    【讨论】:

      猜你喜欢
      • 2016-08-30
      • 1970-01-01
      • 1970-01-01
      • 2013-02-26
      • 1970-01-01
      • 1970-01-01
      • 2014-09-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多