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)
- Chia bản mã thành cặp (digraph).
- 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.