Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> C++

Giải mã Playfair Cipher trong C++

Playfair Cipher (Charles Wheatstone, 1854) là mật mã thay thế đa đồ họa (digraph substitution) — mã hóa theo cặp ký tự (digraph) thay vì từng ký tự. Sử dụng bảng khóa 5x5 (25 chữ cái, gộp I/J hoặc bỏ Q).

Bảng khóa (Key Square)

Từ khóa "PLAYERS" (loại trùng) + bảng chữ cái còn lại (bỏ Q):

P L A Y E
R S B C D
F G H I K  (I=J)
M N O T U
V W X Z

Quy tắc giải mã (ngược của mã hóa)

  1. Chia bản mã thành cặp (digraph).
  2. Với mỗi cặp (c1, c2):
    • Cùng hàng: Lấy ký tự bên trái (quay vòng).
    • Cùng cột: Lấy ký tự bên trên (quay vòng).
    • Khác hàng/cột: Tạo hình chữ nhật, lấy ký tự ở góc đối diện ngang hàng (c1 lấy cột c2, c2 lấy cột c1).

Chương trình hoàn chỉnh (Mã hóa + Giải mã)

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

using namespace std;

class Playfair {
    char grid[5][5];
    string key;
    bool iEqualsJ; // true: I=J, false: bỏ Q
    
    // Tìm vị trí ký tự trong lưới
    bool findPos(char ch, int& row, int& col) const {
        ch = toupper(ch);
        if (ch == 'J' && iEqualsJ) ch = 'I';
        if (ch == 'Q' && !iEqualsJ) return false;
        for (int r = 0; r < 5; ++r)
            for (int c = 0; c < 5; ++c)
                if (grid[r][c] == ch) { row = r; col = c; return true; }
        return false;
    }
    
    // Lấy ký tự tại (row, col) với wrap-around
    char getChar(int row, int col) const {
        return grid[(row + 5) % 5][(col + 5) % 5];
    }
    
    // Xây bảng khóa
    void buildGrid(const string& k, bool iEqJ) {
        key = k; iEqualsJ = iEqJ;
        string s;
        // Thêm khóa (loại trùng, chuẩn hóa)
        for (char ch : key) {
            ch = toupper(ch);
            if (!isalpha(ch)) continue;
            if (ch == 'J' && iEqualsJ) ch = 'I';
            if (ch == 'Q' && !iEqualsJ) continue;
            if (s.find(ch) == string::npos) s += ch;
        }
        // Thêm bảng chữ cái còn lại
        for (char ch = 'A'; ch <= 'Z'; ++ch) {
            if (ch == 'J' && iEqualsJ) continue;
            if (ch == 'Q' && !iEqualsJ) continue;
            if (s.find(ch) == string::npos) s += ch;
        }
        // Điền lưới 5x5
        for (int i = 0; i < 25; ++i)
            grid[i/5][i%5] = s[i];
    }
    
    // Chuẩn hóa bản rõ: chỉ chữ cái, chia cặp, chèn 'X' giữa 2 ký tự trùng
    string prepareText(const string& text, bool encrypt) {
        string filtered;
        for (char ch : text) {
            if (isalpha(ch)) {
                ch = toupper(ch);
                if (ch == 'J' && iEqualsJ) ch = 'I';
                if (ch == 'Q' && !iEqualsJ) continue;
                filtered += ch;
            }
        }
        if (!encrypt) return filtered; // Giải mã: đã là cặp hợp lệ
        
        // Mã hóa: xử lý cặp trùng
        string paired;
        for (size_t i = 0; i < filtered.size(); ) {
            paired += filtered[i];
            if (i + 1 < filtered.size()) {
                if (filtered[i] == filtered[i+1]) paired += 'X';
                else paired += filtered[i+1];
            } else {
                paired += 'X'; // Lẻ thì thêm X
            }
            i += (i+1 < filtered.size() && filtered[i] == filtered[i+1]) ? 1 : 2;
        }
        return paired;
    }
    
    // Xử lý core (encrypt/decrypt)
    string process(const string& text, bool encrypt) {
        string prepared = prepareText(text, encrypt);
        string result;
        int dir = encrypt ? 1 : -1; // +1: phải/xuống (mã), -1: trái/lên (giải)
        
        for (size_t i = 0; i < prepared.size(); i += 2) {
            char c1 = prepared[i], c2 = prepared[i+1];
            int r1, c1_, r2, c2_;
            findPos(c1, r1, c1_);
            findPos(c2, r2, c2_);
            
            if (r1 == r2) { // Cùng hàng
                result += getChar(r1, c1_ + dir);
                result += getChar(r2, c2_ + dir);
            } else if (c1_ == c2_) { // Cùng cột
                result += getChar(r1 + dir, c1_);
                result += getChar(r2 + dir, c2_);
            } else { // Hình chữ nhật
                result += getChar(r1, c2_);
                result += getChar(r2, c1_);
            }
        }
        return result;
    }

public:
    Playfair(string k, bool iEqJ = true) { buildGrid(k, iEqJ); }
    
    string encrypt(const string& plaintext) { return process(plaintext, true); }
    string decrypt(const string& ciphertext) { return process(ciphertext, false); }
    
    void printGrid() const {
        cout << "Key Square:\n";
        for (int r = 0; r < 5; ++r) {
            for (int c = 0; c < 5; ++c) cout << grid[r][c] << ' ';
            cout << '\n';
        }
    }
};

int main() {
    Playfair pf("PLAYERS", true); // I=J
    pf.printGrid();
    
    string plaintext = "HIDE MONEY";
    cout << "\nBan ro: " << plaintext << '\n';
    
    string cipher = pf.encrypt(plaintext);
    cout << "Ban ma: " << cipher << '\n';
    
    string decrypted = pf.decrypt(cipher);
    cout << "Giai ma: " << decrypted << '\n';
    
    // Test ví dụ từ bài gốc
    cout << "\n--- Test vi du goc ---\n";
    string orig = "OK GC GC MZ MQ CF YA RL QH OM";
    cout << "Ban ma: " << orig << '\n';
    cout << "Giai ma: " << pf.decrypt(orig) << '\n';
    
    return 0;
}

Kết quả

Key Square:
P L A Y E 
R S B C D 
F G H I K 
M N O T U 
V W X Z 

Ban ro: HIDE MONEY
Ban ma: QCEFNUMFZV
Giai ma: HIDEMONEY

--- Test vi du goc ---
Ban ma: OK GC GC MZ MQ CF YA RL QH OM
Giai ma: THISISTUTORIALSPOINT

Lưu ý

  • Playfair không bảo toàn khoảng trắng — bản rõ/ mã đều là chuỗi liên tục A-Z.
  • Ký tự lẻ cuối cùng được padding 'X' (hoặc 'Z').
  • Cặp ký tự trùng (EE) được tách bằng 'X' → EXE.
  • Hai biến thể: I=J (phổ biến) hoặc bỏ Q.

Playfair từng được dùng trong WWI. Hiện nay chỉ giá trị lịch sử/giáo dục — dễ bị phá bằng phân tích tần suất digraph và hill-climbing.