Probelm 59 - Project Euler

30分プログラム、その337。Problem 59 - Project Euler

(訳者注: 文字コードの説明は適当です) 各文字はそれぞれ一意のコードに割り当てられている. よく使われる標準としてASCII (American Standard Code for Information Interchange) がある. ASCIIでは, 大文字A = 65, アスタリスク (*) = 42, 小文字k = 107というふうに割り当てられている.
モダンな暗号化の方法として, テキストファイルの各バイトをASCIIに変換し, 秘密鍵から計算された値とXORを取るという手法がある. XOR関数の良い点は, 暗号化に用いたのと同じ暗号化鍵でXORを取ると平文を復号できる点である. 65 XOR 42 = 107であり, 107 XOR 42 = 65である.
破られない暗号化のためには, 鍵は平文と同じ長さのランダムなバイト列でなければならない. ユーザーは暗号文と暗号化鍵を別々の場所に保存する必要がある. また, もし一方が失われると, 暗号文を復号することは不可能になる.
悲しいかな, この手法はほとんどのユーザーにとって非現実的である. そこで, 鍵の変わりにパスワードを用いる手法が用いられる. パスワードが平文より短ければ (よくあることだが), パスワードは鍵として繰り返し用いられる. この手法では, 安全性を保つために十分長いパスワードを用いる必要があるが, 記憶するためにはある程度短くないといけない.
この問題での課題は簡単になっている. 暗号化鍵は3文字の小文字である. cipher1.txtは暗号化されたASCIIのコードを含んでいる. また, 平文はよく用いられる英単語を含んでいる. この暗号文を復号し, 平文のASCIIでの値の和を求めよ.

暗号解読か。英単語だということが分っているので、暗号化された文字列の頻度表と英語の頻度表をみくられべて、適当なキーを想像する。そして、それを用いて復号してみて、意味のある言葉になっているかを調べるという、場当り的な方法で解いた。
ちなみに答えの「The Gospel of John」はヨハネ福音書のことらしいです。Johnはジョンじゃなくてヨハ(ネ)のことらしいです。

使い方

$ perl problem59.pl cipher1.txt
# 頻度表
First
------------------------------
71: 70
 2: 48
...

Second
------------------------------
79: 85
 7: 35
...

Third
------------------------------
68: 77
 1: 41
...

# 復号した文章
(The Gospel of John, chapter 1) 1 In the beginning ...

# 答え
107359

ソースコード

#! /usr/bin/perl
# -*- mode:perl; coding:utf-8 -*-
#
# problem59.pl -
#
# Copyright(C) 2008 by mzp
# Author: MIZUNO Hiroki / mzpppp at gmail dot com
# http://howdyworld.org
#
# Timestamp: 2008/07/09 22:14:48
#
# This program is free software; you can redistribute it and/or
# modify it under MIT Lincence.
#

use strict;
use warnings;
use Data::Dumper;

# 頻度表の作成
sub hist(%){
    my (%hash) = @_;
    for(sort {$hash{$b} <=> $hash{$a}} keys %hash){
	printf "%2d: %d\n",$_,$hash{$_};
    }
}
my (%first,%second,%third);
my @xs = <>;
my @code = split ",",join('',@xs);

for(my $i=0; $i < @code ; $i += 3){
    $first{$code[$i]}    += 1;
    $second{$code[$i+1]} += 1 if($i+1 < @code);
    $third{$code[$i+2]}  += 1 if($i+2 < @code);
}
print "First\n","-"x30,"\n";
print hist(%first);

print "Second\n","-"x30,"\n";
print hist(%second);

print "Third\n","-"x30,"\n";
print hist(%third);

# decode
my $sum = 0;
for(my $i=0; $i < @code ; $i += 3){
    my $x = $code[$i]   ^ ord('g');
    my $y = $i+1 > $#code ? 0 : $code[$i+1] ^ ord('o');
    my $z = $i+2 > $#code ? 0 : $code[$i+2] ^ ord('d');
    print chr($x);
    print chr($y);
    print chr($z);
    $sum += $x+$y+$z;
}
print "\n";
print "$sum\n";