シーザー暗号

2025年の共通テストから「情報I」が入る。以下はまだ「入るはずである(まだ決まったわけではないが)」という時期に書いたものに一部加筆したものである。そのころ、大学入試センターが作って限られたところに配付していた試作問題を,情報処理学会が大学入試センターと調整して大学入学共通テストへの「情報」の出題についての追記で公開した。この試作問題は「検討用イメージ」で,実際にこの形で出題されることが決まったわけではなかった。

この第5問がプログラミングの問題で,シーザー暗号がとりあげられている。言語は今までもセンター試験で出題されていた「情報関係基礎」という科目で使われている擬似言語「DNCL」(DNCは大学入試センターの略)をPython風にアレンジしたものである。この擬似言語は今はDNCLではなく「共通テスト用プログラム表記」と呼ばれている(ドント法のページも参照)。

この問題を実際にPythonを使いながら眺めてみよう。

この問題で解く暗号文は,問題の最後で,リンカーンの有名なゲティスバーグ演説を小文字にしたものであることがわかる:

gettysburg = """Four score and seven years ago our fathers brought
forth on this continent, a new nation, conceived in Liberty, and
dedicated to the proposition that all men are created equal.

Now we are engaged in a great civil war, testing whether that nation,
or any nation so conceived and so dedicated, can long endure. We are
met on a great battle-field of that war. We have come to dedicate a
portion of that field, as a final resting place for those who here
gave their lives that that nation might live. It is altogether fitting
and proper that we should do this.

But, in a larger sense, we can not dedicate -- we can not consecrate
-- we can not hallow -- this ground. The brave men, living and dead,
who struggled here, have consecrated it, far above our poor power to
add or detract. The world will little note, nor long remember what we
say here, but it can never forget what they did here. It is for us the
living, rather, to be dedicated here to the unfinished work which they
who fought here have thus far so nobly advanced. It is rather for us
to be here dedicated to the great task remaining before us -- that
from these honored dead we take increased devotion to that cause for
which they gave the last full measure of devotion -- that we here
highly resolve that these dead shall not have died in vain -- that
this nation, under God, shall have a new birth of freedom -- and that
government of the people, by the people, for the people, shall not
perish from the earth."""

Hirabun = gettysburg.lower()

暗号文はこれを10文字ずらしたシーザー暗号である。ただし,アルファベット以外の部分はそのままである。

alphabet = "abcdefghijklmnopqrstuvwxyz"
codebook = alphabet[10:] + alphabet[:10]

tbl = str.maketrans(alphabet, codebook)

Angoubun = Hirabun.translate(tbl)
print(Angoubun)
pyeb cmybo kxn cofox iokbc kqy yeb pkdrobc lbyeqrd pybdr yx drsc
myxdsxoxd, k xog xkdsyx, myxmosfon sx vslobdi, kxn nonsmkdon dy dro
zbyzycsdsyx drkd kvv wox kbo mbokdon oaekv.

xyg go kbo oxqkqon sx k qbokd msfsv gkb, docdsxq grodrob drkd xkdsyx,
yb kxi xkdsyx cy myxmosfon kxn cy nonsmkdon, mkx vyxq oxnebo. go kbo
wod yx k qbokd lkddvo-psovn yp drkd gkb. go rkfo mywo dy nonsmkdo k
zybdsyx yp drkd psovn, kc k psxkv bocdsxq zvkmo pyb dryco gry robo
qkfo drosb vsfoc drkd drkd xkdsyx wsqrd vsfo. sd sc kvdyqodrob psddsxq
kxn zbyzob drkd go cryevn ny drsc.

led, sx k vkbqob coxco, go mkx xyd nonsmkdo -- go mkx xyd myxcombkdo
-- go mkx xyd rkvvyg -- drsc qbyexn. dro lbkfo wox, vsfsxq kxn nokn,
gry cdbeqqvon robo, rkfo myxcombkdon sd, pkb klyfo yeb zyyb zygob dy
knn yb nodbkmd. dro gybvn gsvv vsddvo xydo, xyb vyxq bowowlob grkd go
cki robo, led sd mkx xofob pybqod grkd droi nsn robo. sd sc pyb ec dro
vsfsxq, bkdrob, dy lo nonsmkdon robo dy dro expsxscron gybu grsmr droi
gry pyeqrd robo rkfo drec pkb cy xylvi knfkxmon. sd sc bkdrob pyb ec
dy lo robo nonsmkdon dy dro qbokd dkcu bowksxsxq lopybo ec -- drkd
pbyw droco ryxybon nokn go dkuo sxmbokcon nofydsyx dy drkd mkeco pyb
grsmr droi qkfo dro vkcd pevv wokcebo yp nofydsyx -- drkd go robo
rsqrvi bocyvfo drkd droco nokn crkvv xyd rkfo nson sx fksx -- drkd
drsc xkdsyx, exnob qyn, crkvv rkfo k xog lsbdr yp pboonyw -- kxn drkd
qyfobxwoxd yp dro zoyzvo, li dro zoyzvo, pyb dro zoyzvo, crkvv xyd
zobscr pbyw dro okbdr.

問題ではまず出現するアルファベットの度数分布を調べる:

図5 出現頻度を求めるプログラム

ここで 差分() は a〜z を 0〜25 に置き換える関数とのことである(ちょっと名前がそぐわない?)。ただし,アルファベット以外は -1 に置き換える。Pythonで書けば次のようになる:

def 差分(c):
    d = ord(c) - ord("a")
    if d < 0 or d > 25:
        d = -1
    return d

この関数を使えば,上の問題のプログラムはPythonでは次のようになる:

Hindo = [0] * 26
for i in range(len(Angoubun)):
    bangou = 差分(Angoubun[i])
    if bangou != -1:
        Hindo[bangou] = Hindo[bangou] + 1
print(Hindo)

なお,Pythonとしては

for c in Angoubun:
    bangou = 差分(c)
    if bangou != -1:
        Hindo[bangou] += 1

と書くほうがきれいである。なお,Pythonのスタイルガイド PEP 8 では変数名や関数名は小文字が推奨されているが,ここでは問題に従った(大学入試センター試験「情報関係基礎」で使われていたDNCLという擬似言語や「情報I」で使われる「共通テスト用プログラム表記」では配列名は大文字で始める)。Pythonは大文字で始めるとクラス名みたいに見えるので,本当にPython風にするなら小文字で始めるほうがいいように思う。

この度数分布を棒グラフで表す:

import matplotlib.pyplot as plt

plt.bar(list(alphabet), Hindo)
図6 アルファベットと配列Hindoのグラフ表示

このパターンから推理して,10文字ずらしたことを突き止め,解読に成功する,というストーリーである。

図7 暗号文を復号するプログラム

これをPythonにするのは練習問題として残しておく。