C言語とRubyでシーザー暗号をプログラミングしてみた

CaesarCipher

 

この記事のザックリした内容
シーザー暗号ってなに?
C言語で実装したシーザー暗号プログラム
Rubyで実装したシーザー暗号プログラム
Windowsを対象としてますが、MacやLinuxでも問題なく読めます

こんにちは。ナガオカ(@boot_kt)です。

最近、暗号化の本を読んでいます

僕が読んでるのはコレ↓

最新版はコチラ↓
僕が読んでる版は一つ古くて、現在では最新版が出ています

 

この本めちゃくちゃ面白いです!!

 

いや、この本だけじゃない、結城 浩さんの本は全部面白い!!
結城さんの本は分かりやすいのは当たり前すぎて、「分かりやすい」なんて表現するのは恥ずかしいぐらい(笑)

結城さんの本は読んでて楽しいワクワクする!
興味ない分野でも興味出て来る
文章も巧い!!!
 

・・・とまぁ、それは置いといて

 

この記事は、シーザー暗号を自分でプログラミングしてみませんか?という内容です

 

 

 

シーザー暗号を自分でプログラミングしてみよう!

この本の最初の方にシーザー暗号が出て来ます
聞いたことあるような無いような・・・?

いや、無いでしょう(笑)

 

で、このシーザー暗号、簡単そうなので実装してみました

しかも、C言語とRubyの2本立て!!!

興味ある方はご覧ください

 

シーザー暗号ってなんじゃらほい?

単一換字式暗号の一種らしいですけど、そんな難しい言葉は置いといて・・・
超簡単に言いますと、文字をズラして違う文字にするだけです

以下の図は3文字ズラした場合の図です
CaesarCipher

↓こんな感じになります

「A」の文字 → 「D」に置き換える
「B」の文字 → 「E」に置き換える
「C」の文字 → 「F」に置き換える
 ・
 ・
 ・
 ・
 ・
「X」の文字 → 「A」に置き換える
「Y」の文字 → 「B」に置き換える
「Z」の文字 → 「C」に置き換える

一例を出せば、

This is a Pen.

を3文字ズラして、

Wklv lv d Shq.

となります

 

超かんたんな暗号化アルゴリズムですな

 

シーザー暗号のアルゴリズムをプログラミングしてみよう

で、これをプログラミングしてみましょう
プログラミング初心者の方でもできると思います
※但し、16進数とか文字コードの知識は必要です

難易度は高く無いです

以下のポイントを意識してプログラミングやってみてください

  • まずは絵を書きましょう
    設計と言うほどじゃないですけど、どうしたいかを絵にしてみましょう
    絵にできないということは、何を作ればいいか分かっていないということ
    何を作ればいいか分からないということは、どう作れば分からないということ
  • アスキーコード表(キャラクターコード表)を見ましょう
    「アスキーコード表」とか「キャラクターコード表」とかでググってみてください
  • ズラすべき文字はどれ?
  • ズラす必要のの無い文字はどれ?
  • 文字をズラすのはいいが、ズラした結果の文字がAZaz)を超える場合はどうするのか?
  • 文字をズラすってどういうこと?

この記事では、僕が書いたソースコードを置いてます
興味ある方はご覧ください
(もうちょっと下に読み進めばあります)

 

ソースコードの解説

コメント書いてますし、処理は複雑ではないので読めば分かると思います
ですので解説は省略しています

概要だけ書いておきますと、こんな感じです
※C言語版もRuby版も同じ

  1. -kオプションで与えられた数字の分だけ英数字(小文字・大文字・数字)をズラしています
    -k 3の場合:「a」は3文字ズラして「d」に、「y」は3文字ズラして「b」になる感じです
  2. ズラすだけじゃ面白くないので、小文字の場合は大文字に、大文字の場合は小文字に変換しています
  3. 暗号化も復号化もコマンドは同じですが、-kオプションに与える数字の符号を変えます
    -k 3で暗号化した場合 → -k -3で復号化
    -k -4で暗号化した場合 → -k 4で復号化
  4. キー(-kオプションで与えた数字)が解らないと復号化できません

 

この記事のソースコードについての注意
今回実装したものはネットや本で見た物ではなく自力で考えて書いたものになります
本当はもっとエレガント & シンプル & 簡潔 & 処理の速いコードになるのかも知れません
いや、なるでしょう

テストもそんなにやってませんので、バグは残っていると思います

悪い点や間違っている点、改善ポイントとかあればぜひ教えてください

あと、細かい入力チェックとかはやってません
メンドくさかったので・・・

 

どうしても解説が欲しいとおっしゃる方へ
プログラミングのレッスンサービスをご提供しています。
お試し用として、無料体験レッスンがあります。

今回のプログラムをどうしても解説して欲しいとおっしゃる方は無料体験レッスンを利用してください。

 

C言語版とRuby版の違い

処理そのものは全く同じです

が、C言語の方がスッキリして分かりやすいですね

Rubyは文字と数値の扱いが全く別物になりますので、.ord.chrを使ってイチイチ変換するのが面倒です
あと、参照渡しだか、参照の値渡しだか、共有渡しだか分かりませんけど、OUTの引数の作り方も面倒です
まぁ、そういう書き方はRubyっぽく無いんでしょうけど

この辺のことも聞いてみたい方は無料体験レッスンを受講頂ければお話しします

言い訳
情報を出し惜しみして無料体験レッスンに誘導したいのではなく、単に書くのが面倒なだけですwww
どうせなら、Skypeで会話した方が早いし楽なんです

それにウチのプログラミングスクールの宣伝にもなるかなと(笑)

 
広告




 

C言語でシーザー暗号

じゃ、ソースコードを見てみましょう
コメント書いてあるので、何やってるか分かると思います

 

プログラミングを始めたばかりの方へアドバイス
プログラムが長いと思うかも知れませんが、コメントを頼りに一行一行読んでみてください
別に難しい内容じゃないので、(その気があればですが)絶対に理解できます!!!

「C言語なんて知らないし・・・」なんて言い訳してないで、読んでみてください
プログラム言語なんてどれもほぼ同じようなもんです
特にC言語はシンプル中のシンプルなので、C言語知らなくても他のプログラム言語を知っているなら読めます

どうしても分からない部分があれば、ググりまくりましょう
僕も分からないことがあればググりまくってます
理解できるまでググりまくったり、本屋さんで立ち読みしたり、本買ったりしてますよ

 

ソースコード

C言語版 | GitHub

#include 
#include 
#include 

/*
    usage :caci -f filename [-k keynumber]
*/

/*******************************************************************
    for DEBUG
*******************************************************************/
#define _SWITCH_DEBUG_

#ifdef _SWITCH_DEBUG_
 #define DEBUG_PRINT(...)  printf(__VA_ARGS__)
 #define DEBUG_RETURN    return
#else
 #define DEBUG_PRINT(...)
 #define DEBUG_RETURN
#endif


/*******************************************************************
    型定義
*******************************************************************/
typedef unsigned char       uchar;
typedef signed char         schar;
typedef unsigned short int  ushort;
typedef unsigned long       ulong;


/*******************************************************************
    定数
*******************************************************************/
#define     MEMCMP_MATCH            (0)

#define     KEYNUMBER_DEFAULT       (3)         /* -K オプションを入れていない場合にこの値を使う */
#define     KEYNUMBER_SIZE          (2)         /* 2桁、0~99 */
#define     FILENAME_SIZE           (256)

#define     OUTPUTFILE_EXT          ".cc"       /* 出力ファイルに付与する拡張子 */



/*******************************************************************
    プロトタイプ
*******************************************************************/
uchar getCaesarCipher(uchar src, schar key);
uchar chgCase(uchar src);

/*******************************************************************
    main
*******************************************************************/
int main(int argc, char *argv[])
{
    /* 暗号化の際のキー */
    ushort  keynumber                           = KEYNUMBER_DEFAULT;
    uchar   szKeyNumber[KEYNUMBER_SIZE + 1]     = { 0 };

    /* ファイル関係 */
    FILE    *fpIn                               = NULL;
    uchar   szInFileName[FILENAME_SIZE + 1]     = { 0 };
    FILE    *fpOut                              = NULL;
    uchar   szOutFileName[FILENAME_SIZE + 1]    = { 0 };
    ushort  intOutFileNameLen                   = 0;
    ushort  intOutFileExtLen                    = 0;

    /* READした文字 */
    int     intC                                = '\0';
    uchar   ucC                                 = '\0';

    /* その他 */
    int     i;

    /*==============================================================*/
    /* コマンドライン解析                                           */
    /*==============================================================*/
    /* 0番目は実行ファイル名なので飛ばす */
    DEBUG_PRINT("argc:%d \n", argc);
    DEBUG_PRINT("argv[0]:%s \n", argv[0]);
    
    for(i=1; i ucLastC) {
                ucC -= (ucLastC - ucFirstC + 1);
            }
            else if(ucC < ucFirstC) {
                ucC += (ucLastC - ucFirstC + 1);
            }
            else {
                /* nop */
            }
        }
        DEBUG_PRINT("ucC:%c \n", ucC);

        /*--------------------------------------------------------------*/
        /* 大文字小文字変換                                             */
        /*--------------------------------------------------------------*/
        ucC = chgCase(ucC);
        
        /*--------------------------------------------------------------*/
        /* ファイル出力                                                 */
        /*--------------------------------------------------------------*/
        DEBUG_PRINT("output:%c \n", ucC);
        fputc( ucC, fpOut );
    }

    /*--------------------------------------------------------------*/
    /* CLOSE                                                        */
    /*--------------------------------------------------------------*/
    fclose(fpIn);
    /*--------------------------------------------------------------*/
    /* CLOSE                                                        */
    /*--------------------------------------------------------------*/
    fclose(fpOut);

    return 0;
}


/********************************************************************/
/*  name      : 文字ずらし処理                                      */
/*  content   : 対象文字にキーを足したものを返す                    */
/*  parameter : (I) uchar 対象文字                                  */
/*              (I) schar キー                                      */
/*  return    : 対象文字にキーを足したもの                          */
/*  remarks   :                                                     */
/********************************************************************/
uchar getCaesarCipher(uchar src, schar key)
{
    DEBUG_PRINT("src:%d(%Xh) \n", src, src);
    DEBUG_PRINT("key:%d(%Xh) \n", key, key);
    DEBUG_PRINT("src + key:%d(%Xh) \n", src + key, src + key);
    return src + key;
}


/********************************************************************/
/*  name      : 大文字小文字処理                                    */
/*  content   : 大文字と小文字を変換する、英字以外はそのまま返す    */
/*  parameter : (I) uchar 対象文字                                  */
/*  return    : 対象文字を大文字or小文字に変換したもの              */
/*  remarks   :                                                     */
/********************************************************************/
uchar chgCase(uchar src)
{
    uchar diff = 'a' - 'A';
    
    /* 小文字の場合 */
    if( ('a' <= src) && (src <= 'z') ) {
        return src - diff;
    }
    /* 大文字の場合 */
    else if( ('A' <= src) && (src <= 'Z') ) {
        return src + diff;
    }
    /* その他 */
    else {
        return src;
    }
    
    /* ここに到達する事は無い */
    return src;
}

ビルドコマンド

gccを使っているなら、WindowsでもMacでもLinuxでも同じです
※コマンドプロンプト or ターミナルウィンドウでコマンド打ちます

> gcc -o caci CaesarCipher.c

実行方法(暗号化)

WindowsでもMacでもLinuxでも同じです
※コマンドプロンプト or ターミナルウィンドウでコマンド打ちます

-k の後の数字は何文字ズラすかを示しています
-2525 を入力してください
※ソースコードでは入力チェックしてないので、変な数字とか小数点とか文字とか、やたら大きな数字とかは入れないでね

何文字ズラしても暗号の強度は変わらないですよ

対象ファイル(以下のコマンドではtarget.txt)と同じ場所に同じファイル名で、拡張子.ccを付与した暗号化ファイルを出力します

> caci -f target.txt -k 3

実行方法(復号化)

> caci -f target.txt.cc -k -3

※もし、暗号化時に-k 5としていたのであれば、復号化時には符号(+/-)-k -5としてください

 
広告




 

Rubyでシーザー暗号

C言語版と流れは同じです
考え方も同じです

ただ、Ruby版の方がクセがあると思います
binding.ord.chrlocal_variable_setが見慣れないだろうと思います
初心者向けのRubyの本には載っていないかも知れません
ググってみてください

AZの範囲チェックは.include?を使えるのでC言語版よりも読みやすいかな

ソースコード

Ruby版 | GitHub

# 
# usage : ruby caci.rb -f filename [-k keynumber]
# 

# ********************************************************************
#   定数
# ********************************************************************
OUTPUTFILE_EXT = ".cc"  # 出力ファイルに付与する拡張子

# ********************************************************************
# *  name      : キー&ファイル名取得処理                            *
# *  content   : コマンドライン配列からキーとファイル名を取得        *
# *  parameter : (I) arrayCommand    コマンドライン配列              *
# *              (I) bind            out用引数を使うためのbinding    *
# *              (O) outIntKeyNumber キーを入れる変数                *
# *              (O) outStrFileName  ファイル名を入れる変数          *
# *  return    : 無し                                                *
# *  remarks   : 処理結果を返すべきだが、面倒なので省略              *
# ********************************************************************
def getKeyAndFilename(arrayCommand, bind, outIntKeyNumber, outStrFileName)

  next_cancel = false

  #--------------------------------------------------------------
  # 個数分回す
  #--------------------------------------------------------------
  arrayCommand.size.times { |i|
    if next_cancel == true
      next_cancel = false
      next
    end

    #--------------------------------------------------------------
    # 解析
    #--------------------------------------------------------------
    case arrayCommand[i].upcase
    # ファイル名
    when "-F"
      next_cancel = true
      puts "#{arrayCommand[i]} #{arrayCommand[i+1]}"
      bind.local_variable_set(outStrFileName, arrayCommand[i+1])
    # キー
    when "-K"
      next_cancel = true
      puts "#{arrayCommand[i]} #{arrayCommand[i+1]}"
      bind.local_variable_set(outIntKeyNumber, arrayCommand[i+1].to_i)
    # その他
    else
      next_cancel = false
      puts arrayCommand[i]
    end
  }

end


# ********************************************************************
# *  name      : 文字ずらし処理                                      *
# *  content   : 対象文字にキーを足したものを返す                    *
# *  parameter : (I) uchar 対象文字                                  *
# *              (I) schar キー                                      *
# *  return    : 対象文字にキーを足したもの                          *
# *  remarks   :                                                     *
# ********************************************************************
def getCaesarCipher(src, key)
  return src + key
end


# ********************************************************************
# *  name      : 大文字小文字処理                                    *
# *  content   : 大文字と小文字を変換する、英字以外はそのまま返す    *
# *  parameter : (I) uchar 対象文字                                  *
# *  return    : 対象文字を大文字or小文字に変換したもの              *
# *  remarks   :                                                     *
# ********************************************************************
def chgCase(src)
  diff = 'a'.ord - 'A'.ord
  
  # 小文字の場合
  if ("a".."z").include?(src.chr)
    return src - diff
  # 大文字の場合
  elsif ("A".."Z").include?(src.chr)
    return src + diff
  # その他
  else
    return src
  end
end


# ********************************************************************
# *  name      : 処理メイン                                          *
# ********************************************************************
keynumber = 0
filename  = ""

getKeyAndFilename(ARGV, binding, :keynumber, :filename)
puts "--------"
puts keynumber
puts filename

# ファイルの存在確認
if File.exist?(filename) == false
  puts("error : input file open error.(#{filename})");
  exit
end


#==============================================================
# 1文字ずつ読み込んでファイル出力
#==============================================================
#--------------------------------------------------------------
# OPEN(入力ファイル)
#--------------------------------------------------------------
inFile  = File.open(filename, "rb")
inFile.binmode
#--------------------------------------------------------------
# OPEN(出力ファイル)
#--------------------------------------------------------------
outFile = File.open(filename + OUTPUTFILE_EXT, "wb")
outFile.binmode

#--------------------------------------------------------------
# READ & 暗号化
#--------------------------------------------------------------
# キーナンバーから暗号用に使う値を算出する
while( binC = inFile.getc )
  puts("0x{binC.ord.to_s(16)} (#{binC})")
  
  ordBinC = binC.ord
  ordFirstC = "\0"
  ordLastC  = "\0"
  
  #--------------------------------------------------------------
  # 文字の種類の判定
  #--------------------------------------------------------------
  if ("a".."z").include?(binC)
    puts "小文字"
    ordFirstC = 'a'.ord
    ordLastC  = 'z'.ord
  elsif ("A".."Z").include?(binC)
    puts "大文字"
    ordFirstC = 'A'.ord
    ordLastC  = 'Z'.ord
  elsif ("0".."9").include?(binC)
    puts "数字"
    ordFirstC = '0'.ord
    ordLastC  = '9'.ord
  else
    puts "default"
  end
  
  
  #--------------------------------------------------------------
  # (必要があれば)変換範囲内の文字に収める
  #--------------------------------------------------------------
  if ordFirstC != "\0"
    #--------------------------------------------------------------
    # 暗号化実施
    #--------------------------------------------------------------
    ordBinC = getCaesarCipher(ordBinC, keynumber)
  
    if(ordBinC > ordLastC)
      ordBinC -= (ordLastC - ordFirstC + 1)
    elsif(ordBinC < ordFirstC)
      ordBinC += (ordLastC - ordFirstC + 1)
    else
      # nop
    end
  end

  #--------------------------------------------------------------
  # 大文字小文字変換
  #--------------------------------------------------------------
  ordBinC = chgCase(ordBinC)

  #--------------------------------------------------------------
  # ファイル出力
  #--------------------------------------------------------------
  outFile.putc(ordBinC.chr)

end


#--------------------------------------------------------------
# CLOSE
#--------------------------------------------------------------
inFile.close
#--------------------------------------------------------------
# CLOSE
#--------------------------------------------------------------
outFile.close

ビルドコマンド

無し

実行方法(暗号化)

WindowsでもMacでもLinuxでも同じです
※コマンドプロンプト or ターミナルウィンドウでコマンド打ちます

-k の後の数字は何文字ズラすかを示しています
-2525 を入力してください
※ソースコードでは入力チェックしてないので、変な数字とか小数点とか文字とか、やたら大きな数字とかは入れないでね

何文字ズラしても暗号の強度は変わらないですよ

対象ファイル(以下のコマンドではtarget.txt)と同じ場所に同じファイル名で、拡張子.ccを付与した暗号化ファイルを出力します

> caci -f target.txt -k 3

実行方法(復号化)

> caci -f target.txt.cc -k -3

※もし、暗号化時に-k 5としていたのであれば、復号化時には符号(+/-)-k -5としてください

 

シーザー暗号って、暗号の強度としては安全???

 

んなワケ無いでしょ!!

 

ブルートフォースアタック(考えられる全てのパターンを力技で試してみること)したらすぐに解読されます

未来少年じゃない方の名探偵さんならブルートフォースアタックするまでもなく一瞬で解読するんじゃね?(笑)

 

小中学生が授業中にコソッと回す手紙に使う程度なら安全じゃないですかね?
先生に見つかっても中身を読み取られる可能性は低いかも知れません

あ、でも、数学の先生ならシーザー暗号とバレるかも知れませんので気を付けてね

 
広告




 

さいごに、

暗号は面白い!! 楽しい!

かなり以前に購入していた『新版 暗号技術入門 秘密の国のアリス』をやっと読み始める事ができました

暗号って難しそうなイメージがありますが、かなり大昔から使われている技術です
ということは、「情報」が人間世界では如何に重要かという事を示しています

  • 「情報」を安全に伝達したい者
  • 「情報」を盗みたい者

この双方が今現在はもちろん大昔も存在していたという事は、技術は進歩して変わったけど人間自体は全く変わっていないっていうことなのかなと思います

 

暗号技術は人類の歴史とともに発達してきました

  • 情報を守りたい側はどうやって暗号技術を開発したのか?
  • 情報を盗みたい側はどうやって解読技術を開発したのか?
  • なぜ暗号文は解読されてしまったのか?

その背景や歴史的な経緯、人間の行動を含めながら暗号技術を勉強するととてつもなく面白い!!

 

暗号おもしれーーーー!!!

 

エニグマ(Enigma)、ヤバいっ!

この本にはエニグマ(Enigma)の事も少しですが書いてあります

エニグマは名前だけは知っていたけど、
暗号方法を知ると、、、

 

これ考えた人、頭おかしくね????

 

で・・・・

 

そのエニグマを解読した人はもっと頭おかしくね??????

 

エニグマそのものにはもちろん、開発した人、解読した人、運用、なぜ破られたのか?等々を考えると、ワクワクとドキドキが止められない!
(もっと勉強してエニグマについての記事を書いてみたいし、エニグマを模したプログラミングもやってみたい)

 

ってことで、この本は超絶にマジでオススメです!!!

参考

調べてみたら、暗号化の本はもちろんですけどエニグマについての本も沢山でています
DVDもBlu-rayもありました

僕は歴史的経緯とか背景とかを知ると一気に興味が湧き上がってもっと知りたいと思うタイプなんですよ(笑)

 

この本はアルゴリズムやソースコードも解説されているので、暗号プログラムを作りたいなら手に入れておきたい
でもなかなか高価なんだよなぁ・・・

 

まず最初に軽く読みたいならこっちだね

 

エニグマを解読したアラン・チューリングさんをモデルにした映画があった!
早速見ましたけど、面白かった!!!
とってもオススメです

ただ、この映画は解読作業にはさほどスポットを与えていません
僕的には解読作業やその苦労をもっともっと見たかった

 

でもすんげー面白かったですよ!!!

 

あと、エニグマの解読はアラン・チューリングさんだけの功績ではなく、アラン・チューリングさんが解読するに至るまで多くの人の功績があります
そういう面も興味が湧きっぱなしです

↓僕と同じように、あなたもこんな風になるかも知れませんよ(笑)

  1. エニグマや暗号技術をちょっと知る
  2. エニグマすげーーー!ってなる
  3. この映画見る
  4. エニグマを解読したヤツすげーーー!!ってなる
  5. エニグマや暗号技術勉強したくなる
  6. エニグマ開発したヤツ頭おかしいーーー!!!ってなる
  7. またこの映画見る
  8. エニグマ解読したヤツ頭おかしいーーー!!!ってなる
  9. エニグマや暗号技術をもっと勉強したくなる
  10. またこの映画見る
  11. 数学者・暗号技術者カッケー!!ってなる

 

 


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

ABOUTこの記事をかいた人

Windows/Mac/Linuxを使う現役システムエンジニア&プログラマ。オープン系・組み込み系・制御系・Webシステム系と幅広い案件に携わる。C言語やC#やJava等数多くのコンパイラ言語を経験したが、少し飽きてきたので、最近はRubyやPython、WordPressなどのWeb系を修得中。初心者向けのプログラミング教室も運営中。オンライン・対面・出張等でプログラミングをレッスンします。