暗号生成・解読プログラムをC/C++/Java/Pythonで書く(ヴィジュネル暗号編)
どうもポテト君です。
今までcore i5 4世代(モバイル版)のCPUが入ったノートパソコンでプログラミングとかしてたんですけど、デスクトップを組み立てることにしました()
これで僕のPCがCPUはi5の4世代から12世代(内蔵GPUなし版)になってGPUは独立しました。
なんてことはどうでもいいので本題に入っていきます。
ヴィジュネル暗号のアルゴリズム自体は昔書いた記事参照
ということでこれを各言語で書いていきます。
C言語
/**
* ヴィジュネル暗号を扱います。
* @param mode "m":暗号化モード,"r":復号化モード
* @param sentence 変換する文字列(かつ変換後の文字列を格納する配列)
* @param sen_size sentenceの要素数
* @param key 鍵
* @param key_size 鍵の文字数(keyの要素数ではない)
*/
void vegenere(char mode, char *sentence, int sen_size, char *key, int key_size)
{
int key_index = 0, i;
for (i = 0; i < sen_size; i++)
sentence[i] = tolower(sentence[i]);
for (i = 0; i < key_size; i++)
key[i] = tolower(key[i]);
if (mode == 'm')
{
for (i = 0; i < sen_size; i++)
{
if (sentence[i] >= 'a' && sentence[i] <= 'z')
{
sentence[i] = 'A' + (sentence[i] + key[key_index] - 2 * 'a') % 26;
key_index++;
}
if (key_index >= key_size)
key_index = 0;
}
}
if (mode == 'r')
{
for (i = 0; i < sen_size; i++)
{
if (sentence[i] >= 'a' && sentence[i] <= 'z')
{
int p = (sentence[i] - key[key_index]) % 26;
if (p < 0)
p += 26;
sentence[i] = 'a' + p;
key_index++;
}
if (key_index >= key_size)
key_index = 0;
}
}
}
シーザー暗号編でもそうでしたが、C/C++ではchar型の値を直に数値として扱えます。
ヌル文字によって文字数を調べるようにしてもいいですが、今回も文章、鍵それぞれで文字数を与えてもらいます。
鍵で使う文字は暗号化する文字毎に変わるのでインデックスを巡回させていきます。そのためkey_indexという鍵のインデックスを保存するための変数を宣言しておきます。
if文を増やさないためにsentence,keyは全てtolower関数で小文字化します。
ちなみに暗号化する場合、結果の暗号は大文字に変換することとします。
if文でmodeが'm'か'r'を判断して暗号化/復号化を分岐します。
暗号化/復号化する毎にkey_indexをインクリメントし、key_size以上になったらkey_indexに0を代入することでインデックスを巡回させます。
復号化時に、CではPythonのようにインデックスを負数で指定できないので指定したいインデックスが負数になってしまう時は正数のインデックスに変換します。
負数のインデックスとは最大のインデックスを-1として負方向に大きくなるので
負数のインデックス+=インデックスを指定される側の要素数
で正数のインデックスに変換できます。
(これは負数のインデックスの絶対値は要素数以下であるという前提の上で成り立ちます。今回はインデックスは要素数との剰余で与えられるので問題ないということです。)
C++
class Chipher{
/**
* ヴィジュネル暗号を扱います。
*
* @param mode "m":暗号化モード,"r":復号化モード
* @param sentence 変換する文字列
* @param key 鍵
* @return string 暗号文または平文
*/
public:static string vigenere(string mode,string sentence,string key){
string ret(sentence);
int i_k=0,sl=sentence.length(),kl=key.length();
for(int i=0;i<sl;i++)sentence[i]=tolower(sentence[i]);
for(int i=0;i<kl;i++)key[i]=tolower(key[i]);
if(mode[0]=='m'){
for(int i=0;i<sentence.length();i++){
if(sentence[i]>='a' && sentence[i]<='z'){
ret[i]='A'+(sentence[i]+key[i_k]-2*'a')%26;
i_k++;
}else{ret[i]=sentence[i];}
if(i_k>=key.length())i_k=0;
}
}
if(mode[0]=='r'){
for(int i=0;i<sentence.length();i++){
if(sentence[i]>='a' && sentence[i]<='z'){
int p=((int)sentence[i]-(int)key[i_k])%26;
if(p<0)p+=26;
ret[i]='a'+p;
i_k++;
}else{ret[i]=sentence[i];}
if(i_k>=key.length())i_k=0;
}
}
return ret;
};
}
もちろん大体はC言語版とおなじですが、文字列をstring型の値でやり取りをするというのが大きな違いです。
sentenceをコピーしたstring型変数retを用意し、変換して返します。
sentenceをコピーしたのは、暗号化/復号化の結果は元の文と同じ文字数になるのでインデックスで指定するだけで済み、C言語版のソースコードをそのままコピペしやすいからです()
極度のめんどくさがりっぽい感じですが空のstring型変数に文字を追加していくのでもインデックス指定して代入していくのでも大して変わらないというかほぼ同じです。
Java
public class Cipher {
/**
* ヴィジュネル暗号を扱います。
*
* @param mode "m":暗号化モード,"r":復号化モード
* @param sentence 変換する文字列
* @param key 鍵
* @return String 暗号文または平文
*/
static public String vigenere(String mode, String sentence, String key) {
int a = 'a', z = 'z';
key = key.toLowerCase();
sentence = sentence.toLowerCase();
List<Integer> list_sen = sentence.chars().boxed().collect(Collectors.toList());
List<Integer> list_key = key.chars().boxed().collect(Collectors.toList());
int b = 0;
String ret = "";
if (mode == "m") {
for (int i : list_sen) {
if (b == key.length())
b = 0;
if (i >= a && i <= z)
ret += Character.toString(a + (i + list_key.get(b) - 2 * a) % 26);
else
ret += Character.toString(i);
b++;
}
ret = ret.toUpperCase();
}
if (mode == "r") {
for (int i : list_sen) {
if (b == key.length())
b = 0;
if (i >= a && i <= z) {
if ((i - list_key.get(b)) % 26 >= 0)
ret += Character.toString(a + (i - list_key.get(b)) % 26);
else
ret += Character.toString(a + 26 + (i - list_key.get(b)) % 26);
} else
ret += Character.toString(i);
b++;
}
}
if (ret != "")
return ret;
return "error";
}
}
JavaではC/C++のように文字を直接数値として扱えないので数値に変換する過程が必要になります。
List<Integer> list_sen = sentence.chars().boxed().collect(Collectors.toList());
List<Integer> list_key = key.chars().boxed().collect(Collectors.toList());
この部分で変換しています。
for(int i : list_sen)
というのは範囲ベースのfor文(つまり変数iにlist_senの要素が順に入っていく)で、Pythonのfor文と似た方式のfor文です。
ASCIIコード化された文字列は、加工後にCharactor.toString関数でASCIIコードをString型文字列に戻します。
あとはほとんどC++版と同じです。
Python
class Chipher:
"""
暗号を扱うクラスです
"""
def vigenere(mode:str,sentence:str,key:str)->str:
"""
ヴィジュネル暗号を扱います。
mode "m":暗号化モード,"r":復号化モード
sentence 変換する文字列
key 鍵
->return 暗号文または平文
"""
a=ord('a')
z=ord('z')
key=key.lower()
sentence=sentence.lower()
b=0
ret=""
if(mode=="m"):
for i in sentence:
if b==len(key):
b=0
if(i>=a and i<=z):
ret+=chr(a+(ord(i)+ord(key[b])-2*a)%26)
else :
ret+=i
b+=1
ret=ret.upper()
if(mode=="r"):
for i in sentence:
if b==len(key):
b=0
if(i>=a and i<=z):
if((ord(i)-ord(key[b])%26>=0)):
ret+=chr(a+(ord(i)-ord(key[b])%26))
else:
ret+=chr(a+26+(ord(i)-ord(key[b])%26>=0))
else :
ret+=i
b+=1
ret=ret.lower()
return ret
おそらく冒頭で紹介した昔書いた記事と同じなのでここで解説することもあまりないかもしれないですが()
Pythonでは、関数ord,chrを使って文字と数値を変換しています。
あとは大体Java版と同じです。
ということでヴィジュネル編、完成です。
次回は単一換字式暗号編です。お楽しみに()
前回↓
この記事が気に入ったらサポートをしてみませんか?