【问题标题】:Trouble understanding Caesar decryption steps无法理解凯撒解密步骤
【发布时间】:2022-01-21 02:21:20
【问题描述】:

以下代码将在给定密文和密钥的情况下解密凯撒加密字符串:

#include <iostream>

std::string decrypt(std::string cipher, int key) {
    std::string d = "";
    for(int i=0; i<cipher.length();i++) {
        d += ((cipher[i]-65-key+26) %26)+65;
    }
    return d;
}

int main()
{
    std::cout << decrypt("WKLVLVJRRG", 3) << std::endl; // THISISGOOD
    std::cout << decrypt("NBCMCMAIIX", 20) << std::endl; // THISISGOOD
}

我无法理解在这一行计算新字符 ASCII 代码所执行的操作:

d += ((cipher[i]-65-key+26) %26)+65;
  1. 第一次减法应该移动数字范围
  2. 然后我们将减去密钥作为凯撒解密的定义方式
  3. 我们加 26 来处理负数 (?)
  4. 模块将限制输出,因为 ASCII 数字的范围是 26 长度
  5. 我们通过在末尾添加 65 回到旧范围

我错过了什么?

【问题讨论】:

  • 很难猜出您可能缺少什么知识,但也许值得注意 - 当用于负值时,% 运算符的行为与“模”的数学概念不同。您询问的代码行增加了复杂性,以降低 %26 对负数进行运算的可能性。
  • 代码运行良好。我缺少的是对数字的直觉
  • 再次猜测您不知道的内容,因为“我错过了什么?” 不是一个明确的问题。也许你不知道65 是大写字母A 的ASCII 值。 26 是 ASCII 字母表中的字母数。这些是您询问的代码中使用的两个数字。
  • 或者如果您不确定这里的算法,请查看Clean, efficient algorithm for wrapping integers in C++

标签: c++ algorithm encryption caesar-cipher


【解决方案1】:

如果我们稍微重新排序表达式,像这样:

d += (((cipher[i] - 65) + (26 - key)) % 26) + 65;

我们得到一个公式 rotating cipher[i] left by key:

  • cipher[i] - 65 将 ASCII 范围 A..Z 带入整数范围 0..25
  • (cipher[i] - 65 + 26 - key) % 26 将该值旋转key(减去key 模26)
  • + 65 将范围 0..25 移回 ASCII 范围 A..Z

例如给定 key 为 2,A 变为 YB 变为 ZC 变为 A,等等。

【讨论】:

  • (26 - key) 表示什么?
  • (26 - key) is -key mod 26
  • 26 对应于运算的模数。想象一个大小为 26 的圆圈,您可以向左走 N 步或向右走 26-N 步,然后到达同一个位置。因此,26-key 等价于模 26 空间中的 -key,并具有非负的额外好处。我们需要保持非负数,因为% 不会产生带有负输入的模数。
  • 是的,没错。但它也不是必需的,因为任何值 >26 都会重复旋转。
  • 此加密的前提是密钥在 1..25 范围内。大于 25 的键没有意义,不应该使用。
【解决方案2】:

让我给你一个关于凯撒密码的详细解释,以便理解这个公式。我还将展示超简单的代码示例,以及更高级的单行代码。

最大的问题是潜在的溢出。所以,我们需要解决这个问题。

那么我们需要了解加密和解密是什么意思。如果加密将所有内容向右移动,解密将再次将其向左移动。

  • 因此,如果使用“def”和 key=1,加密后的字符串将为“efg”。
  • 如果使用 key=1 进行解密,则会再次将其移至左侧。结果:“定义”

我们可以观察到,我们只需要移位 -1,因此键的负数。

所以,基本上加密和解密都可以用相同的程序来完成。我们只需要反转键。

现在让我们看看溢出问题。目前我们只从大写字符开始。字符具有关联的代码。例如,在 ASCII 中,字母“A”用 65 编码,“B”用 66 编码,以此类推。因为我们不想用这样的数字进行计算,所以我们将它们归一化。我们只是从每个字符中减去“A”。那么

  • 'A' - 'A' = 0
  • 'B' - 'A' = 1
  • 'C' - 'A' = 2
  • 'D' - 'A' = 3

你看到了模式。如果我们现在想用密钥 3 加密字母“C”,我们可以执行以下操作。

'C' - 'A' + 3 = 5 然后我们再次添加 'A' 以取回字母,我们将得到 5 + 'A' = 'F'

这就是全部的魔法。

但是如何处理“Z”之外的溢出。这可以通过简单的模除法来处理。

让我们看看 'Z' + 1。我们做 'Z' - 'A' = 25,然后 +1 = 26 现在模 26 = 0 然后加上 'A' 将是 'A'

等等等等。结果公式为:​​(c-'A'+key)%26+'A'

接下来,否定键呢?这也很简单。假设一个 'A' 和 key=-1

结果将是“Z”。但这与向右移动 25 相同。因此,我们可以简单地将负键转换为正转移。简单的陈述将是:

if (key < 0)  key = (26 + (key % 26)) % 26;

然后我们可以用一个简单的 Lambda 调用我们的转换函数。一种用于加密和解密的功能。只需使用倒置键即可。

使用上面的公式,甚至不需要检查负值。它适用于正值和负值。

所以,key = (26 + (key % 26)) % 26; 将始终有效。


一些扩展信息,如果您使用 ASCII 字符表示。请查看任何 ASCII 表。您会看到任何大写和小写字符相差 32。或者,如果您查看二进制:

char  dez  bin           char  dez  bin
'A'   65   0100 0001     'a'   97   0110 0001 
'B'   66   0100 0010     'b'   98   0110 0010 
'C'   67   0100 0011     'b'   99   0110 0011 
 . . .

所以,如果你已经知道一个字符是字母,那么大小写的唯一区别就是第 5 位。如果我们想知道,如果char 是小写,我们可以通过屏蔽这个位来得到. c &amp; 0b0010 0000 等于 c &amp; 32c &amp; 0x20

如果我们想对大写或小写字符进行操作,我们可以将“大小写”屏蔽掉。使用c &amp; 0b00011111c &amp; 31c &amp; 0x1F,我们将始终获得大写字符的等价物,已经规范化为从一个开始。

char  dez  bin        Masking         char  dez  bin         Masking
'A'   65   0100 0001  & 0x1b = 1      'a'   97   0110 0001   & 0x1b = 1
'B'   66   0100 0010  & 0x1b = 2      'b'   98   0110 0010   & 0x1b = 2
'C'   67   0100 0011  & 0x1b = 3      'b'   99   0110 0011   & 0x1b = 3
 . . .

因此,如果我们使用一个字母字符,将其屏蔽,然后减去 1,那么对于任何大写或小写字符,我们都会得到 0..25。


另外,我想重复密钥处理。正密钥将加密一个字符串,负密钥将解密一个字符串。但是,如上所述,否定键可以转换为肯定键。示例:

Shifting by  -1  is same as shifting by  +25
Shifting by  -2  is same as shifting by  +24
Shifting by  -3  is same as shifting by  +23
Shifting by  -4  is same as shifting by  +22

因此,很明显我们可以通过:26 + key 来计算始终为正的密钥。对于否定键,这将为我们提供上述偏移量。

对于正键,我们会在 26 上产生溢出,我们可以通过模 26 除法来消除:

'A'-->  0 + 26 = 26    26 % 26 = 0 
'B'-->  1 + 26 = 27    27 % 26 = 1 
'C'-->  2 + 26 = 28    28 % 26 = 2 
'D'-->  3 + 26 = 29    29 % 26 = 3

--> (c + key) % 26 将消除溢出并产生正确的新加密/解密字符。

而且,如果我们将它与上述关于否定键的智慧结合起来,我们可以写成:((26+(key%26))%26),它将适用于所有正负键。

将其与掩蔽相结合,可以得到以下程序:

const char potentialLowerCaseIndicator = c & 0x20;
const char upperOrLower = c & 0x1F;
const char normalized = upperOrLower - 1;
const int withOffset =  normalized + ((26+(key%26))%26);
const int withOverflowCompensation = withOffset % 26;
const char newUpperCaseCharacter = (char)withOverflowCompensation + 'A';
const char result = newUpperCaseCharacter | (potentialLowerCaseIndicator );

当然,以上众多语句都可以转换成一个Lambda:

#include <string>
#include <algorithm>
#include <cctype>
#include <iostream>

// Simple function for Caesar encyption/decyption

std::string caesar(const std::string& in, int key) {
    std::string res(in.size(), ' ');

    std::transform(in.begin(), in.end(), res.begin(), [&](char c) {return std::isalpha(c) ? (char)((((c & 31) - 1 + ((26 + (key % 26)) % 26)) % 26 + 65) | (c & 32)) : c; });

    return res;
}

int main() {
    std::string test{ "aBcDeF xYzZ" };
    std::cout << caesar(test, 5);
}

为了便于理解,最后一个函数也可以写得更详细:

std::string caesar1(const std::string& in, int key) {
    std::string res(in.size(), ' ');

    auto convert = [&](const char c) -> char {
        char result = c;
        if (std::isalpha(c)) {

            // Handling of a negative key (Shift to left). Key will be converted to positive value
            if (key < 0) {
                // limit the key to 0,-1,...,-25
                key = key % 26;
                // Key was negative: Now we have someting between 0 and 26
                key = 26 + key;
            };

            // Check and remember if the original character was lower case
            const bool originalIsLower = std::islower(c);

            // We want towork with uppercase only
            const char upperCaseChar = (char)std::toupper(c);

            // But, we want to start with 0 and not with 'A' (65)
            const int normalized = upperCaseChar - 'A';

            // Now add the key
            const int shifted = normalized + key;

            // Addition result maybe bigger then 25, so overflow. Cap it
            const int capped = shifted % 26;

            // Get back a character
            const char convertedUppcase = (char)capped + 'A';

            // And set back the original case
            result = originalIsLower ? (char)std::tolower(convertedUppcase) : convertedUppcase;
        }
        return result;
    };
    std::transform(in.begin(), in.end(), res.begin(), convert);
    return res;
}

如果您想查看仅包含最简单语句的解决方案,请参阅以下内容。

#include <iostream>
#include <string>

using namespace std;

string caesar(string in, int key) {

    // Here we will store the resulting encrypted/decrypted string
    string result{};


    // Handling of a negative key (Shift to left). Key will be converted to positive value
    if (key < 0) {
        // limit the key to 0,-1,...,-25
        key = key % 26;
        // Key was negative: Now we have someting between 0 and 26
        key = 26 + key;
    };
   
    // Read character by character from the string
    for (unsigned int i = 0; i < in.length(); ++i) {

        char c = in[i];

        // CHeck for alpha character
        if ((c >= 'A' and c <= 'Z') or (c >= 'a' and c <= 'z')) {

            // Check and remember if the original character was lower case
            bool originalIsLower = (c >= 'a' and c <= 'z');

            // We want to work with uppercase only
            char upperCaseChar = originalIsLower ? c - ('a' - 'A') : c;

            // But, we want to start with 0 and not with 'A' (65)
            int normalized = upperCaseChar - 'A';

            // Now add the key
            int shifted = normalized + key;

            // Addition result maybe bigger then 25, so overflow. Cap it
            int capped = shifted % 26;

            // Get back a character
            char convertedUppcase = (char)capped + 'A';

            // And set back the original case
            result += originalIsLower ? convertedUppcase + ('a' - 'A') : convertedUppcase;

        }
        else
            result += c;
    }
    return result;
}

int main() {
    string test{ "aBcDeF xYzZ" };
    string encrypted = caesar(test, 5);
    string decrypted = caesar(encrypted, -5);

    cout << "Original:  " << test << '\n';
    cout << "Encrpyted: " << encrypted << '\n';
    cout << "Decrpyted: " << decrypted << '\n';
}

【讨论】:

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