When it’s ready.

出来るまで出来ない

ナショナルトレジャーに出てきたプレイフェア暗号の片道分を作ってみた。

http://www.disney.co.jp/movies/nt2/

映画の中で出てきた、2文字区切りのアルファベットを見て主人公が「プレイフェア暗号の特徴だ」と言ったのを聞いてしまい。WikiPで調べたら実際にあった。キーワードから文字変換の為にマトリクス表を作成する。マトリクスに従って文章の文字をリプレースしていく。とても簡単そうに思えた。

http://www.tamagaki.com/math/PlayfairCipher.html
に、とても分かりやすくやり方が乗っていたのでやる気になってしまい作ってみた。
やってみると、想像より全然めんどくさい。始めた当初の目的は、さっくり作ってGAE上に秘密鍵ストレージを作ろうとか、WaveのRobotにしてみようと思ったけど、コア部分作るだけで力尽きた。
以下ソース

#!/usr/bin/env python
# coding:utf-8

'''
プレイフェア暗号
1: 5文字のキーフレーズを定義する
2: xを使わないキーフレーズ
2.1: IとJを一つのますに入れる(??)
3: キーフレーズを決める
4.1: キーフレーズに使われている文字を全アルファベット内から削除
4.2: キーフレーズの後ろに4.1のアルファベットを追加する
4.3: 5文字5列のマトリックスに分解する
'''

cipher = ''

def strChecker(key):
  alphabet = 'abcdefghijklmnopqrstuvwyz'
  res = []
  for alphaStr in alphabet:
    if alphaStr in key:
      pass
    else:
      res.append(alphaStr)
  return key + ''.join(res)

def makeMatrix(arr):
  res = []
  if len(arr) == 25:
    for i in range(0,25,5):
      res.append(tuple(arr[i:i+5]))
    return res
  else:
    return res

# 2文字続いている時は間にXを入れる
# 文字のトータルが2の倍数になるようにxを最後に付加して調整する
def searchDupperStr(orgSentence):
  orgSentence = orgSentence.lower()
  orgSentence = orgSentence.replace(' ', '')
  res = ''
  for i in range(len(orgSentence)-1):
    if orgSentence[i] == orgSentence[i + 1]:
      res += orgSentence[i] + 'x'
    else:
      res += orgSentence[i]
  res += orgSentence[-1]
  if len(res) % 2 != 0:
    res += 'x'
  return res

def splitTwo(orgSentence):
  res = []
  return [orgSentence[x] + orgSentence[x+1] for x in range(0, len(orgSentence) -1, 2)]

def convUnigram(bygram, sentence, indexOne, indexTwo):
  global cipher
  one = sentence[indexOne[0]][indexTwo[1]]
  two = sentence[indexTwo[0]][indexOne[1]]
  cipher += "%s%s "%(one, two)
  return "%s > %s%s\n"%(bygram, one, two)

def convHor(bygram, sentence):
  global cipher
  one = sentence[(sentence.index(bygram[0])+1)%5]
  two = sentence[(sentence.index(bygram[1])+1)%5]
  cipher += "%s%s "%(one, two)
  return "%s > %s%s\n"%(bygram, one, two)

def convXnext(unigram, sentence):
  return sentence[(list(sentence).index(unigram)+1)%5]

def findStr(mxTable, arrStr):
  global cipher
  res = ''
  for i in arrStr:
    flg = 1
    # xが入っていた場合
    if i[0] == 'x':
      for ii in mxTable:
        if i[1] in ii:
          cipher += "x%s "%(convXnext(i[1], ii))
          res += "%s > %s%s"%(i, 'x', convXnext(i[1], ii))
    elif i[1] == 'x':
      for ii in mxTable:
        if i[0] in ii:
          cipher += "%sx "%(convXnext(i[0], ii))
          res += "%s > %s%s"%(i, convXnext(i[0], ii),'x')
    else: 
      # 2文字が横に同列の場合
      for ii in mxTable:
        if i[0] in ii and i[1] in ii:
          res += convHor(i, ii)  
          flg = 0
          

      # 2文字が縦に位置列の場合
      mtx = apply(zip, mxTable)
      for ii in mtx:
        if i[0] in ii and i[1] in ii:
          res +=  convHor(i, ii)
          flg = 0

      # 2文字がねじれの関係の時
      if flg:
        onePos, twoPos = '', ''
        for ii in range(len(mxTable)):
          if i[0] in mxTable[ii]:
            onePos = [ii, mxTable[ii].index(i[0])]
          elif i[1] in mxTable[ii]:
            twoPos = [ii, mxTable[ii].index(i[1])]
        if onePos and twoPos:
          res += convUnigram(i, mxTable, onePos, twoPos) 
  print res

def makeStr(key, Str):
  global cipher
  mtx = makeMatrix(strChecker(key))
  strTxt = splitTwo(searchDupperStr(Str))
  findStr(mtx, strTxt)
  print "key : %s"%key
  print 'original : %s'%Str
  print 'encript  : %s'%cipher
  print 'conv_text: %s'%(' '.join(strTxt))
  print 'matrix   :'
  for i in mtx:
    print i

def hogeTest():
  # step1 : アルファベットの並び替え>26文字 
  step1 = strChecker(keyphrase)

  # step2 : マトリックス作成
  step2 = makeMatrix(step1)

  step3 = searchDupperStr(originalStr)
  step4 = splitTwo(step3)
  print step1
  print step2

  for i in step2:
    print i
  print keyphrase
  print originalStr
  print step3

  findStr(step2, step4)
 
if __name__ == '__main__':
  import sys
  argvs = sys.argv
  if len(argvs) >1 :
    keyphrase = argvs[1]
    originalStr = argvs[2]
    makeStr(keyphrase, originalStr) 

リポジトリhttp://bitbucket.org/a2c/python-playfair/overview/

使い方

キーワードを指定する必要が有る。25文字以内で、アルファベット。文字は1度しか使えない。

    • Good
      • lady
      • gold
    • bad
      • baby
      • tokyo

スクリプトの第1引数にキーワード、第2引数に暗号化したい文言を指定し起動する。

python PlayfairCipher.py gold 'time is money'

スペースはつぶされて、2文字以上のつながりがある部分は間にxが入る。
オリジナルの'time is money'は 'ti me is mo ne yx'となる。最後にxが付加されているのは、2の倍数にする為

暗号化の結果は、

key : gold
original : time is money
encript : pn kf mp jd kh zx
conv_text: ti me is mo ne yx
matrix :
('g', 'o', 'l', 'd', 'a')
('b', 'c', 'e', 'f', 'h')
('i', 'j', 'k', 'm', 'n')
('p', 'q', 'r', 's', 't')
('u', 'v', 'w', 'y', 'z')

'time is money' > 'ti me is mo ne yx' > 'pn kf mp jd kh zx' と変化していってる。
キーワードの'gold'さえ覚えておけば、'pn kf mp jd kh zx' を元の文章に戻す事が可能なはず。

昔の暗号は、頭の体操にちょうど良いかも

プログラミングで頭に負荷掛けたくても、自分で考えるお題は、バカの壁の内側なのでたいしたお題を思いつかない。
何百年も前から有る暗号をLLで実装するのはとても頭の体操になる。ネタもいっぱい転がってるしちょうど良いかも知れない。

次は、復号をやってみたいけどちょっと大変そうな気がする。逆をするだけだから簡単かな?

適当すぎるコードだけど、書かないよりましだと思った。