【问题标题】:Vigenère cipher in Java for all UTF-8 characters用于所有 UTF-8 字符的 Java 中的 Vigenère 密码
【发布时间】:2012-05-04 01:40:57
【问题描述】:

我有这个简单的函数,用于通过 Java 中的 Vigenère 加密字符串。我省略了解密,因为这只是计算新值的行中的“-”而不是“+”。

但此功能仅适用于普通字母 A-Z。如何更改函数以使其支持小写字母以及大写字母和所有其他 UTF-8 字符?

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

非常感谢您的帮助!

【问题讨论】:

  • 你想支持什么样的字符? UTF-8 是一种支持所有 unicode 字符的编码,其中许多字符甚至不会以您喜欢的字体打印。此外,Vigenère 密码并不真正适合任何现代密码学……为什么要将其应用于现代编码?当然,只支持 52 个大写/小写字符很容易......
  • 如果可能,我希望支持所有 unicode 字符以提供最佳兼容性。字体中丢失的字符没有问题,因为加密的数据永远不会被打印,而只会被存储。我想使用它,因为它简单、快速并且可以很容易地用各种编程语言实现(具有可互换的结果)。
  • 使用 unicode 的可互换结果,你确定吗?您可以将每个字符解释为 32 位 unicode 代码点。然后,密钥将是 32 位数字的随机字符串。但是,最好为纯文本描述 UTF-8,为密文描述 Base64 编码,然后使用任何简单的密码(如果你真的必须 让它变得简单和破碎)。
  • Unicode 定义了代码点,这是一个不断增长的“字母”。这可以使用例如编码UTF-8。您需要定义一个组来进行模数计算。对于 Unicode,这不会太容易发生。我将向您展示另一个使用两个组(高和低)的答案,但我强烈建议您考虑加密字符编码(字节)而不是字符,即使是简单的混淆技术。
  • 简单快速,是的,但也是微不足道的。请不要将它用于任何重要的事情。

标签: java encryption cryptography vigenere


【解决方案1】:

嗯,你要求它,我感到很困惑,但打印出密文,你就会知道你刚才要求的是什么......

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

编辑:请不要永远在生产代码中使用,因为我不知道代码点是否确实可以编码/解码。据我所知,并不是所有的点都被定义了,标准是一个移动的目标。

【讨论】:

  • 谢谢!当然,我不想要一个让其创建者感到困惑的代码;)我只是不明白为什么我在问题中发布的代码不能扩展到涵盖所有 UTF-8 字符,因为 ASCII 是可能的人物。 UTF-8 必须有有限数量的字符,因为 MySQL(例如)也提供诸如 utf8_general_ci 之类的编码?!
【解决方案2】:

如果无法提供完整的 unicode 支持,并且您必须定义有效字符列表,那么为什么不直接使用这样的函数呢?

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

}

这应该是一个非常简短且有效的版本。并且字母表可以很容易地存储在一个始终可以扩展的字符串中(这会导致不同的密文)。

【讨论】:

  • 当然,这很好用,但它需要对每个字符进行稍微昂贵的查找。国税局更短,更容易理解。这两种方法同样有效。
  • "IRS" -> "是",对不起,从我的手机发送
  • 这个解决方案效果很好。对于我的任务,我给了计算机键盘上所有可能键的自定义字符,而不仅仅是 a-z 或 A-Z。使用我的字符集,这个解决方案效果很好。
【解决方案3】:

另一个答案,它确实对大小写字符执行 Vigenere 密码,只需插入其他字符。使用此技术创建多组字符进行编码。

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

同样,这是不安全的代码,因为所使用的密码已被破解多年。不要在你的键中使用太多的 'A' 字符 :) 但是字符编码应该是正确的。

【讨论】:

  • 我知道密码可以被破解——尽管它也可以用作一次性密码本。但由于它只是为了混淆,没关系。如果 A 太多,它几乎就是凯撒密码,不是吗?
  • 更糟糕的是:在纯文本中添加零并没有太多作用。
  • 哦,是的,当然- 多么尴尬,我没有直接理解:) 最后一个问题:如何在这个函数中添加另一组字符,例如德语特殊字符ÄÖÜäöüß
  • 请看看我的回答 - 这不应该在更短的同时做同样的事情吗?
最近更新 更多