C++
 Computer >> コンピューター >  >> プログラミング >> C++

【C++】Playfair暗号でメッセージを暗号化・復号化する方法と実装例

Playfair暗号は、単純な単文字置換暗号とは異なり、文字を2文字ずつのペア(ダイグラフ)単位で暗号化する古典暗号の一種です。ペア単位で処理することで単純な頻度分析への耐性が高まり、歴史的にも広く利用されてきました。

Playfair暗号の基本構造

鍵表(キーテーブル)の作成

Playfair暗号では、まず平文を暗号化するための「鍵表」を作成します。鍵表は5×5のアルファベット格子で、25個のマスすべてに異なる文字を配置する必要があります。アルファベットは26文字あるため、1文字(通常はJ)を除外します。平文にJが含まれる場合は、Iに置き換えて処理します。

送信者と受信者は、あらかじめ共通の鍵(ここでは例として「tutorials」)を取り決めておきます。鍵表には、まず鍵フレーズの文字を重複を除いて左から右へ順に配置し、残りのマスには未使用のアルファベットをAから順に埋めていきます。

Playfair暗号の暗号化手順

最初に、平文のメッセージを2文字ずつのペア(ダイグラフ)に分割します。文字数が奇数になる場合は、末尾にZを追加します。例として「hide money」というメッセージを暗号化する場合、次のように分割されます。

HI DE MO NE YZ

暗号化の3つのルール

  • 同じ列にある場合: 各文字のすぐ下の文字に置き換えます(最下行の場合は最上行に折り返します)。HとIが同じ列にあるため、HI → QC となります。
  • 同じ行にある場合: 各文字のすぐ右の文字に置き換えます(最右列の場合は最左列に折り返します)。DとEが同じ行にあるため、DE → EF となります。
  • どちらにも該当しない場合: 2文字を頂点とする長方形を考え、それぞれの文字と水平方向に対角となる位置の文字に置き換えます。

これらのルールを適用すると、「hide money」を鍵「tutorials」で暗号化した結果は以下のようになります。

QC EF NU MF ZV

Playfair暗号の復号化は、同じ手順を逆方向に適用するだけです。受信者は同じ鍵を持っていれば同一の鍵表を作成でき、その鍵で暗号化されたメッセージを正しく復号できます。

以下に、Playfair暗号を使ってメッセージを暗号化するC++プログラムを示します。

アルゴリズム

Begin
Function void play( int dir )
For it = msg.begin() to it != msg.end()
    If ( getPos( *it++, j, k ) )
       If ( getPos( *it, p, q) )
          If ( j == p )
             nmsg += getChar( j, k + dir )
             nmsg += getChar( p, q + dir )
          else if( k == q )
             nmsg += getChar( j + dir, k )
             nmsg += getChar( p + dir, q )
          else
             nmsg += getChar( p, k )
             nmsg += getChar( j, q )
          done
       done
    done
    msg = nmsg
done
End

C++による実装例

#include <iostream>
#include <string>
using namespace std;
class playfair {
    public:
        string msg; char n[5][5];
    void play( string k, string t, bool m, bool e ) {
        createEncoder( k, m );
        getText( t, m, e );
        if( e )
            play( 1 );
        else
            play( -1 );
        print();
    }
    private:
        void play( int dir ) {
        int j,k,p,q;
        string nmsg;
        for( string::const_iterator it = msg.begin(); it != msg.end(); it++ ) {
            if( getPos( *it++, j, k ) )
            if( getPos( *it, p, q ) ) {
                // 同じ行の場合
                if( j == p ) {
                    nmsg += getChar( j, k + dir );
                    nmsg += getChar( p, q + dir );
                }
                // 同じ列の場合
                else if( k == q ) {
                    nmsg += getChar( j + dir, k );
                    nmsg += getChar( p + dir, q );
                } else {
                    nmsg += getChar( p, k );
                    nmsg += getChar( j, q );
                }
            }
        }
        msg = nmsg;
    }
    void print() { // 結果を出力
        cout << "\n\n Solution:" << endl;
        string::iterator it = msg.begin(); int count = 0;
        while( it != msg.end() ) {
            cout << *it;
            it++;
            cout << *it << " ";
            it++;
            if( ++count >= 26 )
            cout << endl;
            count = 0;
        }
        cout << endl << endl;
    }
    char getChar( int a, int b ) { // 文字を取得
        return n[ (b + 5) % 5 ][ (a + 5) % 5 ];
    }
    bool getPos( char l, int &c, int &d ) { // 位置を取得
        for( int y = 0; y < 5; y++ )
        for( int x = 0; x < 5; x++ )
        if( n[y][x] == l ) {
            c = x;
            d = y;
            return true;
        }
        return false;
    }
    void getText( string t, bool m, bool e ) { // 元のメッセージを取得
        for( string::iterator it = t.begin(); it != t.end(); it++ ) {
            // J→I への変換、または Q の除外を選択
            *it = toupper( *it );
            if( *it < 65 || *it > 90 )
                continue;
            if( *it == 'J' && m )
                *it = 'I';
            else if( *it == 'Q' && !m )
                continue;
            msg += *it;
        }
        if( e ) {
            string nmsg = ""; size_t len = msg.length();
            for( size_t x = 0; x < len; x += 2 ) {
                nmsg += msg[x];
                if( x + 1 < len ) {
                    if( msg[x] == msg[x + 1] ) nmsg += 'X';
                    nmsg += msg[x + 1];
                }
            }
            msg = nmsg;
        }
        if( msg.length() & 1 )
        msg += 'X';
    }
    void createEncoder( string key, bool m ) { // 鍵表の作成
        if( key.length() < 1 )
        key = "KEYWORD";
        key += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        string s = "";
        for( string::iterator it = key.begin(); it != key.end(); it++ ) {
            *it = toupper( *it );
            if( *it < 65 || *it > 90 )
                continue;
            if( ( *it == 'J' && m ) || ( *it == 'Q' && !m ) )
                continue;
            if( s.find( *it ) == -1 )
                s += *it;
        }
        copy( s.begin(), s.end(), &n[0][0] );
    }
};
int main( int argc, char* argv[] ) {
    string k, i, msg;
    bool m, c;
    cout << "Encrypt or Decrypt? ";
    getline( cin, i );
    c = ( i[0] == 'e' || i[0] == 'E' );
    cout << "Enter a key: ";
    getline( cin, k);
    cout << "I <-> J (Y/N): ";
    getline( cin, i );
    m = ( i[0] == 'y' || i[0] == 'Y' );
    cout << "Enter the message: ";
    getline( cin, msg );
    playfair pf;
    pf.play( k, msg, m, c );
    return system( "pause" );
}

実行結果

Encrypt or Decrypt? e
Enter a key: players
I <-> J (Y/N): y
Enter the message: This is tutorialspoint

Solution:
OK GC GC MZ MQ CF YA RL QH OM
  1. 接続行列を使ってグラフを表現するC++プログラムの解説

    接続行列(インシデンス行列)とはグラフの接続行列(インシデンス行列)は、グラフをメモリ上に格納するためのもうひとつの表現方法です。隣接行列と異なり、接続行列は正方行列ではありません。そのサイズは V × E で表されます。ここで V はグラフの頂点数、E は辺の数です。この行列では、各行に頂点が配置され、各列に辺が配置されます。ある辺 e {u, v} に対しては、列 e のうち頂点 u と頂点 v に対応する位置に「1」がマークされます。これにより、「どの頂点がどの辺に接続しているか」という情報を直感的に把握できます。接続行列の計算量とメモリ使用量接続行列による表現では、構築時に O(V ×

  2. 隣接行列を使ってグラフを表現するC++プログラムの解説

    グラフの隣接行列(Adjacency Matrix)とは、サイズが V × V の正方行列のことです。ここでの V はグラフ G の頂点数を表します。行列の行と列にはそれぞれ頂点が対応しており、頂点 i から頂点 j への辺が存在する場合は、i 行 j 列の要素に「1」(重み付きグラフの場合は非ゼロの値)を格納します。辺が存在しない場合は、その位置には「0」が入ります。 隣接行列表現の計算量 隣接行列は計算時に O(V2) の記憶領域を必要とします。グラフが最大数の辺を持つ場合でも最小数の辺しか持たない場合でも、必要なメモリ量は同じです。つまり、辺の数に依存せず常に V × V 分の領域を確