首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Vigenere密码

Vigenere密码
EN

Code Review用户
提问于 2015-05-05 13:23:37
回答 1查看 11K关注 0票数 6

此代码要求用户输入包含小写字符a的消息,然后将其加密为Vigenere密码,并解密密码以证明反向查找有效。

代码语言:javascript
复制
package com.testing;

import java.util.Scanner;

/**
 * A Vigenere Square or Vigenere table consists of the alphabet written out 26
 * times in different rows, each alphabet shifted cyclically to the left
 * compared to the previous alphabet, corresponding to the 26 possible Caesar
 * ciphers, At different points in the encryption process, the cipher uses a
 * different alphabet from one of the rows. The alphabet used at each point
 * depends on a repeating keyword.
 */
final class VigenereSquare {

    /**
     * A 2D char array representing the shifted alphabets.
     */
    public static final char[][] SQUARE = fillSquare();

    private static final int LETTERS_IN_ALPHABET = 26;
    private static final int ASCII_RANGE = 256;

    private VigenereSquare() {}

    /**
     * Fill square with shifted alphabets in ASCII positions:
     *  'a' = 97 .. 'z' = 122
     * @return initialised char[][]
     */
    private static char[][] fillSquare() {
        char[][] square = new char[ASCII_RANGE][ASCII_RANGE];
        int start = 'a';
        int end = start + (LETTERS_IN_ALPHABET - 1);
        int index = start;
        for (int i = start; i <= end; i++) {
            for (int j = start; j <= end; j++) {
                //Check index position if beyond the range of the alphabet
                //reset index position to start.
                if (index > end) {
                    index = start;
                }
                square[i][j] = (char) index;
                index++;
            }
            index = i + 1;
        }
        return square;
    }
}

/**
 * The person sending the message to be encrypted (eg. attackatdawn) chooses a
 * keyword and repeats it until it matches the length of the plaintext, for
 * example, the keyword lemon, the cipher key will be lemonlemonle.
 */
class CipherKey {

    /**
     * CipherKey String value.
     */
    public final String KEY;

    public CipherKey(String text, String keyword) {
        KEY = createKey(text, keyword);
    }

    /**
     * Creates a key string of the same length of the text based on
     * the keyword.
     * @param text to be encrypted
     * @param keyword the chosen keyword
     * @return the key string
     */
    private String createKey(final String text, final String keyword) {
        StringBuilder key = new StringBuilder();
        for (int i = 0, keywordIndex = 0; i < text.length(); i++,
                keywordIndex++) {
            if (keywordIndex >= keyword.length()) {
                keywordIndex = 0;
            }
            key.append(keyword.charAt(keywordIndex));
        }
        return key.toString();
    }
}

/**
 * Using a VigenereSquare and a CipherKey each row starts with a key letter. The
 * remainder of the row holds the letters A to Z (in shifted order). Although
 * there are 26 key rows shown, you will only use as many keys (different
 * alphabets) as there are unique letters in the key string, here just 5 keys,
 * {L, E, M, O, N}. For successive letters of the message, we are going to take
 * successive letters of the key string, and encipher each message letter
 * using its corresponding key row. Choose the next letter of the key, go along
 * that row to find the column heading that matches the message character; the
 * letter at the intersection of [key-row, msg-col] is the enciphered letter.
 * 
 * For example, the first letter of the plaintext, A, is paired with L, the
 * first letter of the key. So use row L and column A of the Vigenere square,
 * namely L. Similarly, for the second letter of the plaintext, the second
 * letter of the key is used; the letter at row E and column T is X. The rest
 * of the plaintext is enciphered in a similar fashion.
 * 
 * Plaintext: ATTACKATDAWN
 * Key: LEMONLEMONLE
 * Ciphertext: LXFOPVEFRNHR
 */
final class VigenereCipherEncrypter {

    private VigenereCipherEncrypter() {}

    /**
     * Encrypt the message using the provided CipherKey and VigenereSquare.
     * @param message to be encrypted
     * @param key used to encrypt message
     * @return encrypted message string
     */
    public static String encrypt(final String message, final CipherKey key) {
        StringBuilder cipher = new StringBuilder();
        String k = key.KEY;
        char[][] square = VigenereSquare.SQUARE;
        for (int i = 0; i < k.length(); i++) {
            //Use the integer values of the key and message char at postion i
            //to determine which character to use from the VigenereSquare and
            //append it to the cipher text.
            cipher.append(square[k.charAt(i)][message.charAt(i)]);
        }
        return cipher.toString();
    }
}

/**
 * Using ciphered text, a CipherKey and a VigenereSquare the
 * VigenereCipherDecrypter achieves decryption by going to the row in the table
 * corresponding to the key, finding the position of the ciphertext letter in
 * this row, and then using the column's label as the plaintext. For example,
 * in row L (from LEMON), the ciphertext L appears in column A, which is the
 * first plaintext letter. Next we go to row E (from LEMON), locate the
 * ciphertext X which is found in column T, this T is the second plaintext
 * letter.
 */
final class VigenereCipherDecrypter {

    private VigenereCipherDecrypter() {}

    /**
     * Decrypt the cipher text using the provided CipherKey and
     * VigenereSquare.
     * @param cipher text.
     * @param key used to decrypt the cipher text.
     * @return decrypted message.
     */
    public static String decrypt(final String cipher, final CipherKey key) {
        StringBuilder message = new StringBuilder();
        String k = key.KEY;
        char[][] square = VigenereSquare.SQUARE;
        for (int i = 0; i < k.length(); i++) {
            int rowIndex = k.charAt(i);
            char[] row = square[rowIndex];
            int colIndex = new String(row).indexOf(cipher.charAt(i));
            message.append((char) colIndex);
        }
        return message.toString();
    }
}

/**
 * This program asks the user to enter a message to encrypt and a keyword. Based
 * on these it will then use a CipherKey and a VigenereSquare. These are then
 * used to encrypt the message using a VigenereCipherEncrypter.
 *
 * Decryption is also performed using a VigenereCipherDecrypter.
 */
public class Vigenere {

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

        System.out.println("Enter message to encrypt (a-z characters only): ");
        String message = in.nextLine();

        System.out.println("Enter the keyword: ");
        String keyword = in.nextLine();

        CipherKey cipherKey = new CipherKey(message, keyword);

        String cipherText = VigenereCipherEncrypter.encrypt(message, cipherKey);
        System.out.println("Encrypted message: " + cipherText);

        String decryptedMessage = VigenereCipherDecrypter.decrypt(cipherText,
                cipherKey);
        System.out.println("Decrypted message: " + decryptedMessage);
    }
}
EN

回答 1

Code Review用户

回答已采纳

发布于 2015-05-05 18:23:39

说你的意思

int start = 'a'; int end = start + (LETTERS\_IN\_ALPHABET - 1); int index = start; for (int i = start; i <= end; i++) { for (int j = start; j <= end; j++) { //Check index position if beyond the range of the alphabet //reset index position to start. if (index > end) { index = start; } square[i][j] = (char) index; index++; } index = i + 1; }

此代码的目的似乎是填写与字母表中的字母对应的正方形部分。字母表的start总是定义为'a',但是您可以从startLETTERS_IN_ALPHABET中计算end。然后使用startend作为常量。为什么不让它们成为常量,然后去掉LETTERS_IN_ALPHABET呢?

代码语言:javascript
复制
    private static final char ALPHABET_START = 'a';
    private static final char ALPHABET_END = 'z';

然后我们就可以用这些:

代码语言:javascript
复制
        for (int i = ALPHABET_START; i <= ALPHABET_END; i++) {
            char c = (char) i;
            for (int j = ALPHABET_START; j <= ALPHABET_END; j++) {
                if (c > ALPHABET_END) {
                    c = ALPHABET_START;
                }

                square[i][j] = c;
                c++;
            }
        }

这比原来的更灵活,因为我们可以通过常量来改变开始和结束。

还请注意,index实际上不是一个索引。这是一封信,所以要么叫letter,要么叫c

由于我们的新c变量从未在i循环之外使用,并且每次迭代都会被重置,所以只需在循环中定义它。将它从末尾移到开头意味着我们不再需要将其设置为i + 1,因为开始是在i增量之后。在循环的第一次迭代中,c被设置为ALPHABET_START,就像在原始代码中一样。

我们还将c更改为char而不是int,因为这允许在分配给square[i][j]时直接使用它,而只需要在j循环之外进行强制转换。

这个注释现在没有必要了,因为代码读起来就像注释一样。如果c超过了字母表的末尾,请将c重置为字母表的开头。

考虑举一个例子。

代码语言:javascript
复制
 * For an ALPHABET_START of 'a' and an ALPHABET_END of 'c', generate 
 * abc
 * bca
 * cab

这样就更容易看出进展是有意的,而不是偶然的。

说你的意思,再一次

StringBuilder key = new StringBuilder(); for (int i = 0, keywordIndex = 0; i < text.length(); i++, keywordIndex++) { if (keywordIndex >= keyword.length()) { keywordIndex = 0; } key.append(keyword.charAt(keywordIndex)); }

这里要做的第一件事是给StringBuilder一个初始容量。我们知道长度所以告诉密码。

代码语言:javascript
复制
        StringBuilder key = new StringBuilder(text.length());

这允许编译器在开始时分配正确的StringBuilder长度,而不是选择任意长度并根据需要展开。

这段代码的编写方式类似于前面的代码编写方式,但是它所做的事情有所不同。它所做的就是将keyword附加到key,直到它与text的长度相同为止。所以就这么做吧。

代码语言:javascript
复制
        final int fullCount = text.length() / keyword.length();
        for (int i = 0; i < fullCount; i++) {
            key.append(keyword);
        }

        final int remainingLength = text.length() % keyword.length();
        key.append(keyword.substring(0, remainingLength));

我们不逐个添加字符,而是追加字符串的整个副本。这就省去了维护keywordIndex的问题。

多文件

在Java中,标准的做法是将每个类放在自己的文件中。这使得重用类变得更容易,因为您可以只复制所需的文件。

测试

我用测试用例和其他几个测试用例测试了这段代码:

abc de abcd ef abc gfed

它返回与第一个用例和测试用例的代码相同的结果。我没有根据您的代码检查其他代码,因为我在修改之后想到了它们。它们都产生合理的输出,并与原来的字符串相呼应。

这是一个支持发布的单元测试的论点。如果你已经测试了很多这样的情况,我可以用你的测试。那么,我可以相当肯定的是,这两个版本都做了相同的事情。这使得修改变得更容易,并且有信心它们不会导致回归。

注意:我没有评论这种加密方法。好的?坏的?我不是该说的人。我的评论主要是为了可读性,对性能略加赞赏。

票数 4
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/88864

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档