初心者用の簡単な暗号解析プログラム

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
じゃすと
記事: 5
登録日時: 7年前

初心者用の簡単な暗号解析プログラム

#1

投稿記事 by じゃすと » 7年前

先週からC言語を学び始めた じゃすと と申します。初めての投稿となるので、至らない点がありましたらご指摘ください。

まず、独習Cという参考書に乗っていた問題で、

「ユーザーから文字列を受け取って暗号化するプログラムを作成せよ。
このとき暗号化では、文字列の両端の文字を左から始めて交互に取り出し、文字列の真ん中に達した時に終わるようにする。
例えば Hi there という文字列は Heir eth となる」

という問題があって、この問題に関しては一応答えをちらちらと見ながらも理解はしました。

ここからが本題なのですが、「 入力した文字列→暗号化→元の入力した文字列 」 と変換できるプログラムも考えてみようと思いました(これはあくまで興味本位なのですが)
要は暗号化した文字列(Heir eth) を元の文字列(Hi there) に戻すプログラムのことです。
しかし、考えてもつまずいてしまったので質問させていただきます。

私が考えた暗号化文字列の復元の手順としては、
1、暗号化した文字列を配列に格納(例えばchar 型のstrという変数に格納するとする)
2、格納した配列のstr[0], str[2], str[4], str[6], str[7], str[5], str[3], str[1] という順に読み込みようにすれば、元の文字列になるのでは。

と考えたのですが、1つめの段階でつまずいてしまいました。
暗号化プログラムは以下に示します。

プログラムでは配列のある部分を参照して文字で表示、というのをつなげて文字列にしているので、うまく出力を配列に格納できればいいのになーと思っているのですが、なかなかできず。

ご教授お願いいたします。

コード:

#include<stdio.h>
#include<string.h>

int main(void)
{
	char word[80];
	int i, j;

	printf("Input word: ");
	gets(word);
	
	i=0, j=strlen(word)-1;
	while(i<=j){
		if(i<j) printf("%c%c", word[i], word[j]);
		else printf("%c", word[i]);
		i++; j--;
	}
	printf("\n");

	return 0;
}

・環境
OS: windows 7 32bit
コンパイラ: Borland C++ Compiler 5.5

non
記事: 1097
登録日時: 9年前

Re: 初心者用の簡単な暗号解析プログラム

#2

投稿記事 by non » 7年前

考え方はそれでいいでしょう。
で、
1、暗号化した文字列を配列に格納(例えばchar 型のstrという変数に格納するとする)
が、できないというのはどこがわからないのでしょうか?

やり方としては
1 配列strを用意する。
2 strは前から順番に格納するので、どこに格納するかをカウントする変数例えばkを用意して最初はk=0
3 printfで現在文字を出力している命令の代わりにstrの配列に1文字ずつ格納する。 例えば str[k]=word
4 1文字格納したらkをカウントアップする。
5 whileから抜けた後、正しく格納されたか表示する。例えば puts(str)
non

じゃすと
記事: 5
登録日時: 7年前

Re: 初心者用の簡単な暗号解析プログラム

#3

投稿記事 by じゃすと » 7年前

non さんが書きました:
やり方としては
1 配列strを用意する。
2 strは前から順番に格納するので、どこに格納するかをカウントする変数例えばkを用意して最初はk=0
3 printfで現在文字を出力している命令の代わりにstrの配列に1文字ずつ格納する。 例えば str[k]=word
4 1文字格納したらkをカウントアップする。
5 whileから抜けた後、正しく格納されたか表示する。例えば puts(str)


一応、悪戦苦闘しながらも書いてみました。

コード:

#include<stdio.h>
#include<string.h>

int main(void)
{
	char word[80], str[80];
	int i, j, k;

	printf("Input word: ");
	gets(word);
	
	i=0, k=0, j=strlen(word)-1;
	while(i<=j){
		if(i<j)	{
			str[k] = word[i];
			str[k+1]=word[j];
			printf("%c%c", word[i], word[j]);
		}
		else {
			str[k] = word[i];
			printf("%c", word[i]);
		}
		i++;
		j--;
		k=k+2;
	}
	printf("\n");
	puts(str);
	return 0;
}

一応、ちゃんと格納できているのか確認しやすいようにprintfはそのままにしてあります。しかし、この文だとおそらくkがNULL文字まで格納しているせいか、出力結果の最後にいらない文字まで付随していまいます。

k=k+2としているので、そこでkがNULL文字に達したのか判別すればいいのか?そもそもkがstrlen(word)-1 に達したのか判別すればいいのか?と分かりません。

ご指摘お願いいたします。

YuO
記事: 941
登録日時: 9年前
住所: 東京都世田谷区

Re: 初心者用の簡単な暗号解析プログラム

#4

投稿記事 by YuO » 7年前

この手の物は,最初はループを分けたり,バッファ自体も分けたりして考えた方が考えやすいと思います。

まず,暗号化について。
  1. 平文をwordに入力する
  2. 暗号文のバッファstrを用意する
  3. 配列の前半部分をコピーしていくためのバッファfront_bufferを用意する
  4. 配列の後半部分をコピーしていくためのバッファlatter_bufferを用意する
  5. 入力文字列長length = strlen(word)を求める
  6. 配列の前半部分をコピーする長さlength_front = (length + 1) / 2を求める
  7. 配列の後半部分をコピーする長さlength_latter = length - length_frontを求める
  8. 範囲[0, length_front)のiについてwordをfront_bufferにコピーする <A>
  9. front_buffer[length_front]に\0を代入しておく
  10. ここでfront_bufferを出力してみてもよい
  11. 範囲[0, length_latter)のiについてword[i + length_front]をlatter_buffer[length_latter - i - 1]にコピーする <B>
  12. latter_buffer[length_latter]に\0を代入しておく
  13. ここでlatter_bufferを出力してみてもよい
  14. 範囲[0, length_front)のiについてfront_bufferをstr[i * 2]にコピーする <C>
  15. 範囲[0, length_latter)のiについてlatter_bufferをstr[i * 2 + 1]にコピーする <D>
  16. str[length]に\0を代入する
  17. strを出力する

効率は悪いですが,これでstrがちゃんと完成します。

次に復号について。
  1. 暗号文をstrに入力する
  2. 平文のバッファwordを用意する
  3. 配列の前半部分をコピーしていくためのバッファfront_bufferを用意する
  4. 配列の後半部分をコピーしていくためのバッファlatter_bufferを用意する
  5. 入力文字列長length = strlen(str)を求める
  6. 配列の前半部分をコピーする長さlength_front = (length + 1) / 2を求める
  7. 配列の後半部分をコピーする長さlength_latter = length - length_frontを求める
  8. 範囲[0, length_front)のiについてstr[i * 2]をfront_bufferにコピーする <C>
  9. front_buffer[length_front]に\0を代入しておく
  10. ここでfront_bufferを出力してみてもよい
  11. 範囲[0, length_latter)のiについてstr[i * 2 + 1]をlatter_bufferにコピーする <D>
  12. latter_buffer[length_latter]に\0を代入しておく
  13. ここでlatter_bufferを出力してみてもよい
  14. 範囲[0, length_front)のiについてfront_bufferをwordにコピーする <A>
  15. 範囲[0, length_latter)のiについてlatter_bufferをword[length - i - 1]にコピーする <B>
  16. word[length]に\0を代入する
  17. wordを出力する

となります。暗号化と比較すれば,<A>, <B>, <C>, <D>が対応していることがわかると思います。
# front_buffer, latter_bufferが暗号化・復号で同じになります。

次に,front_bufferとlatter_bufferを削除することを考えます。
<A>と<C>,<B>と<D>をまとめればこれらの中間バッファが削除できます。
まずは暗号化について。
  • 前半部分について,
    • <A>よりwordはfront_buffer[i]
    • <C>よりfront_buffer[i]はstr[i * 2]
    よってword[i]はstr[i * 2]になる
  • 後半部分について,
    • <B>よりword[i + length_front]はlatter_buffer[length_latter - i - 1]
    • <D>よりlatter_buffer[length_latter - i - 1]はstr[(length_latter - i - 1) * 2 + 1]
    よって,word[i + length_front]はstr[(length_latter - i - 1) * 2 + 1]になる

復号については,上記の結論部分をstr→wordの向きに読み替えます。
# 後半部分については復号の<D><B>から出した方が綺麗に結論付けられますが。


実際には平文中の位置と平文の長さから暗号文中の位置が計算できます。
また,暗号文中の位置と暗号文の長さから平文中の位置も計算できます。
  • 平文および暗号文の長さをNとする
  • 0 <= i && i < Nとする
  • i < (N + 1) / 2の時,平文中の位置がiの文字は暗号文中ではi * 2の位置にくる <E>
  • (N + 1) / 2 <= iの時,平文中の位置がiの文字は暗号文中では(N - i) * 2 - 1の位置にくる <F>
  • iが偶数の時,暗号文中の位置がiの文字は平文中ではi / 2の位置にくる <E>
  • iが奇数の時,暗号文中の位置がiの文字は平文中ではN - (i + 1) / 2の位置にくる <F>

これを使えば,複数のバッファは不要です。これも,<E>と<F>がそれぞれ対応しているのがわかります。
  • 暗号化時の偶奇は必ず復号部分の条件に相当する
  • 復号後の位置の計算式のiに暗号化時の位置の計算式を代入するとiになる

► スポイラーを表示

じゃすと
記事: 5
登録日時: 7年前

Re: 初心者用の簡単な暗号解析プログラム

#5

投稿記事 by じゃすと » 7年前

YuO さんが書きました:この手の物は,最初はループを分けたり,バッファ自体も分けたりして考えた方が考えやすいと思います。
  1. 暗号文をstrに入力する
  2. 平文のバッファwordを用意する
  3. 配列の前半部分をコピーしていくためのバッファfront_bufferを用意する
  4. 配列の後半部分をコピーしていくためのバッファlatter_bufferを用意する
  5. 入力文字列長length = strlen(str)を求める
  6. 配列の前半部分をコピーする長さlength_front = (length + 1) / 2を求める
  7. 配列の後半部分をコピーする長さlength_latter = length - length_frontを求める
  8. 範囲[0, length_front)のiについてstr[i * 2]をfront_bufferにコピーする <C>
  9. front_buffer[length_front]に\0を代入しておく
  10. ここでfront_bufferを出力してみてもよい
  11. 範囲[0, length_latter)のiについてstr[i * 2 + 1]をlatter_bufferにコピーする <D>
  12. latter_buffer[length_latter]に\0を代入しておく
  13. ここでlatter_bufferを出力してみてもよい
  14. 範囲[0, length_front)のiについてfront_bufferをwordにコピーする <A>
  15. 範囲[0, length_latter)のiについてlatter_bufferをword[length - i - 1]にコピーする <B>
  16. word[length]に\0を代入する
  17. wordを出力する

となります。暗号化と比較すれば,<A>, <B>, <C>, <D>が対応していることがわかると思います。
# front_buffer, latter_bufferが暗号化・復号で同じになります。


暗号化等の方針、プログラムまで書いてくださってありがとうございます。
しかし、まだまだC言語を始めて1週間程度ですので、関数であったりポインタであったりと、まだまだ分かっていないところが多々あります。
将来的にはこのようなプログラムをさらさらーと書けるようになれればいいのですが、私にはまだ基礎的な点が足りていないため、今回は参考のみに留まらせていただきます。
それにしても、プログラムの方針がなかなかまとまらない私にとっては、その方針を文章化されるだけでもけっこうな驚きでした。

七篠真名
記事: 12
登録日時: 7年前

Re: 初心者用の簡単な暗号解析プログラム

#6

投稿記事 by 七篠真名 » 7年前

NULL文字まで格納しているというか、NULL文字がないからputs(str);がうまく機能してないのではないでしょうか?
Cだと文字列の終わりをNULL文字で判断しているので、NULL文字が出てくるまで表示してしまいます。
つまり現状だとstrの文字列の最後の位置が分からなくてうまく表示されないので、明示的にNULL文字を入力すればいいと思います。
王道なやり方がどうかはわかりませんが、自分のところでは次のようにしてうまくいったと思います。(最後の部分だけ抜粋)
まあ配列にはうまく格納されていると思うので、2番はこのままでも問題なくできると思いますよ。

コード:

	printf("\n");
	str[strlen(word)] = NULL;	/* この行を追加 */
	puts(str);
自分もまだまだ初心者なので間違えたことを指摘してしまったらすみません。
一緒に学んでいきましょう!

じゃすと
記事: 5
登録日時: 7年前

Re: 初心者用の簡単な暗号解析プログラム

#7

投稿記事 by じゃすと » 7年前

みなさんのご指摘を受け、プログラムとしては汚いながらもなんとか思い通りの動作をさせることができました。ご指摘いただいた皆さん、本当にありがとうございました。
またつまずいたときに質問させていただきます。

コード:

#include<stdio.h>
#include<string.h>

int main(void)
{
	char input[80], code[80], front_buffer[80], rear_buffer[80];
	int i, j, k, length, length_front, length_rear;

	printf("Input word: ");
	gets(input);

	length = strlen(input);
	length_front = (length+1)/2;
	length_rear = length - length_front;

	printf("length=%d length_front=%d length_rear=%d\n", length, length_front, length_rear);

	/* 暗号化 */
	i = 0, k = 0, j = strlen(input)-1;
	while(i<=j){
		if(i<j)	{
			code[k] = input[i];
			code[k+1] = input[j];
		}
		else code[k] = input[i];

		i++;
		j--;
		k=k+2;
	}

	printf("\n");
	code[strlen(input)] = NULL;
	printf("code = %s\n", code);

	/* 複合化  */

	for(i=0;i<length_front;i++){
		front_buffer[i] = code[i*2];
	}
	front_buffer[length_front] = NULL;

	printf("   front_buffer = %s\n", front_buffer);
	
	for(i=0;i<length_rear;i++){
		rear_buffer[length_rear-i-1] = code[i*2+1];
	}	

	rear_buffer[length_rear] = NULL;

	printf("   rear_buffer = %s\n", rear_buffer);

	strcat(front_buffer, rear_buffer); 

	printf("decode = %s", front_buffer);
	
	return 0;
}


YuO
記事: 941
登録日時: 9年前
住所: 東京都世田谷区

Re: 初心者用の簡単な暗号解析プログラム

#8

投稿記事 by YuO » 7年前

細かいことですが,NULLはポインタに対してのみ使うようにしましょう。
'\0'の代わりに使えなくはないですが,NULLは「空ポインタ定数」を意味するのであって「ナル文字」ではないです。

じゃすと
記事: 5
登録日時: 7年前

Re: 初心者用の簡単な暗号解析プログラム

#9

投稿記事 by じゃすと » 7年前

YuO さんが書きました:細かいことですが,NULLはポインタに対してのみ使うようにしましょう。
'\0'の代わりに使えなくはないですが,NULLは「空ポインタ定数」を意味するのであって「ナル文字」ではないです。
そうなんですね。まだまだしっかりとした知識がないためそのようなご指摘はありがたいです。
わざわざありがとうございました。

閉鎖

“C言語何でも質問掲示板” へ戻る