C++でアフィン暗号を実装する方法|暗号化・復号化のコード解説
アフィン暗号とは?
アフィン暗号(Affine Cipher)は、単一文字置換暗号(モノアルファベティック置換暗号)の一種です。アルファベットの各文字を数値に対応付け、シンプルな数学関数によって暗号化を行い、その結果を再び文字へと変換します。
サイズ m のアルファベットでは、まず各文字を 0 ~ m−1 の範囲の整数にマッピングします。
アフィン暗号の「鍵」は a と b の 2 つの数値から構成されます。このうち a は、m と互いに素(最大公約数が 1)である数を選ぶ必要があります。
暗号化の仕組み
暗号化では、平文の各文字に対応する整数を、合同算術(モジュロ演算)を用いて別の整数へ変換します。この変換後の整数が暗号文の文字に対応します。1 文字に対する暗号化関数は次のとおりです。
E(x) = (ax + b) mod m modulus m : アルファベットのサイズ a, b : 暗号の鍵
復号化の仕組み
復号化では、暗号文の各文字を対応する整数値に変換し直します。復号化関数は次のとおりです。
D(x) = a⁻¹(x − b) mod m
a⁻¹ : a の法 m におけるモジュラ乗法逆元。
つまり a × a⁻¹ ≡ 1 (mod m) を満たす整数です。
以下に、この一連の処理を実装した C++ プログラムを示します。
アルゴリズム
開始
関数 encryption(string m)
for i = 0 to m.length()-1
if (m[i] != ' ')
c = c + (char)((((a * (m[i]-'A')) + b) % 26) + 'A')
else
c += m[i]
return c
終了
開始
関数 decryption(string c)
a_inverse = 0 で初期化
flag = 0 で初期化
for i = 0 to 25
flag = (a * i) % 26
if (flag == 1)
a_inverse = i
for i = 0 to c.length()-1
if (c[i] != ' ')
m = m + (char)(((a_inverse * ((c[i]+'A' - b)) % 26)) + 'A')
else
m = m + c[i]
終了
処理のポイント
- 鍵の設定: サンプルでは a = 7、b = 6 を使用しています。7 と 26 は互いに素であるため、乗法逆元が存在します。
- 乗法逆元の探索: 復号化の際は、(a × i) % 26 == 1 となる i を 0~25 の範囲で全探索します。a = 7 の場合は i = 15 が該当します(7 × 15 = 105 = 4 × 26 + 1)。
- 負数対策: 復号化コードで c[i] + 'A' としているのは、(c[i] − 'A' − b) が負になるのを避けるためです。2 × 65 = 130 は 26 の倍数なので、mod 26 の結果には影響しません。
- スペースの扱い: 空白文字は暗号化・復号化の対象外とし、そのまま出力にコピーします。
C++ 実装例
#include <bits/stdc++.h>
using namespace std;
static int a = 7;
static int b = 6;
string encryption(string m) {
// 暗号文を格納する文字列(最初は空)
string c = "";
for (int i = 0; i < m.length(); i++) {
// スペースは暗号化しない
if (m[i] != ' ')
// 'A' を加えて ASCII の英字範囲 [65-90 | A-Z] に収める
c = c + (char)((((a * (m[i]-'A')) + b) % 26) + 'A');
else
// スペースはそのまま追加
c += m[i];
}
return c;
}
string decryption(string c) {
string m = "";
int a_inverse = 0;
int flag = 0;
// a^-1(法 26 における a の乗法逆元)を求める
for (int i = 0; i < 26; i++) {
flag = (a * i) % 26;
// (a * i) % 26 == 1 なら、i が a の乗法逆元
if (flag == 1) {
a_inverse = i;
}
}
for (int i = 0; i < c.length(); i++) {
if (c[i] != ' ')
// 'A' を加えて ASCII の英字範囲 [65-90 | A-Z] に収める
m = m + (char)(((a_inverse * ((c[i]+'A' - b)) % 26)) + 'A');
else
// スペースはそのまま追加
m += c[i];
}
return m;
}
int main(void) {
string msg = "TUTORIALSPOINT";
string c = encryption(msg);
cout << "Encrypted Message is : " << c << endl;
cout << "Decrypted Message is: " << decryption(c);
return 0;
}
実行結果
Encrypted Message is : JQJAVKGFCHAKTJ Decrypted Message is: TUTORIALSPOINT
平文「TUTORIALSPOINT」が暗号文「JQJAVKGFCHAKTJ」に変換され、さらに復号化することで元の平文が正しく復元されていることが確認できます。
-
C++でグラフの隣接行列を実装する方法【サンプルコード付き解説】
隣接行列とは グラフの隣接行列(Adjacency Matrix)とは、V×Vのサイズを持つ正方行列のことです。ここでVはグラフGの頂点数を表します。行列の行と列にはそれぞれ頂点が対応付けられ、頂点iから頂点jへの辺が存在する場合は、i行目・j列目の要素に1が格納されます(重み付きグラフの場合は、辺の重みなどの非ゼロの値が入ります)。辺が存在しない場合は0が格納されます。 なお、無向グラフの場合、辺は双方向につながりを持つため、隣接行列は必ず対称行列になります。つまり、adj[i][j]とadj[j][i]は常に同じ値となります。 隣接行列表現の計算量 空間計算量: 隣接行列にはO(V²)
-
C++でグラフの隣接リストを実装する方法:サンプルコード付きで解説
グラフの隣接リストは、連結リスト(リンクリスト)を用いたグラフの表現方法の一つです。この表現では、リストを要素とする配列を使用し、その配列のサイズは V(頂点の総数)となります。言い換えれば、V個の異なるリストを格納するための配列を用意することになります。各リストの先頭が頂点 u に対応しており、そのリストには「頂点 u に隣接するすべての頂点」が格納されます。 隣接リスト表現の計算量 無向グラフの場合、必要な記憶領域は O(V + 2E)、有向グラフの場合は O(V + E) となります。 辺の数が増加すると、それに伴って必要なメモリ量も増えていきます。そのため、辺の密度が低い(スパースな