解きたい問題
結城先生の暗号技術入門で、「PELCGBTENCUL
という、シーザー暗号によって暗号化された暗号文を復号化してみましょう。」というクイズが出ました。
シーザー暗号とは?
こちらは紀元前1世紀ごろに、ユリウス・カエサルさんが用いたと言われる換字式暗号です。
https://ja.wikipedia.org/wiki/暗号史
アルファベットに対して、指定の数字数だけずらしていく暗号化方式です。
ex) key: 3, abc →(暗号化) → def
手でアルファベットをずらしていくのが面倒でしかたなかったので、21世紀ではプログラムを組んで算出していきます。
一番手慣れたSwiftを使ってPlaygroundで実装してみました。
ついでに暗号化も実装しました。
シーザー暗号のプログラム
import UIKit
// シーザー暗号で暗号化/復号化できるプロトコル
protocol CaesarCrypting {
func encrypted(by key: Int) -> String
func decrypted(by key: Int) -> String
}
// アルファベットの列挙体表現
enum Alphabet: String {
case a
case b
case c
case d
case e
case f
case g
case h
case i
case j
case k
case l
case m
case n
case o
case p
case q
case r
case s
case t
case u
case v
case w
case x
case y
case z
}
extension Alphabet: CaseIterable { }
extension Alphabet: CaesarCrypting {
/// アルファベットの値を指定数ずらして暗号化したアルファベットを返す
///
/// - Parameter key: 何文字ずらすか指定する鍵
/// - Returns: 暗号化結果のアルファベットを返す
func encrypted(by key: Int) -> String {
let allCases = Alphabet.allCases
// aのindexはゼロ
assert(key >= 0 && key < allCases.count, "アルファベットは26文字しかありません。")
let currentIndex = allCases.firstIndex(of: self).map { $0 }!
let shiftedIndex = (currentIndex + key) % allCases.count
return allCases[shiftedIndex].rawValue
}
/// アルファベットの値を指定数ずらして復号化したアルファベットを返す
///
/// - Parameter key: 何文字ずらすか指定する鍵
/// - Returns: 復号化結果のアルファベットを返す
func decrypted(by key: Int) -> String {
let allCases = Alphabet.allCases
// aのindexはゼロ
assert(key >= 0 && key < allCases.count, "アルファベットは26文字しかありません。")
let currentIndex = allCases.firstIndex(of: self).map { $0 }!
let shiftedIndex = (currentIndex - key + allCases.count) % (allCases.count)
return allCases[shiftedIndex].rawValue
}
}
extension Character {
/// アルファベット変換できたら値を返す
var alphabet: Alphabet? {
return Alphabet(rawValue: String(self))
}
}
// 意図せずCompositeパターンになったっぽい。
extension String: CaesarCrypting {
func encrypted(by key: Int) -> String {
return self
.compactMap { $0.alphabet }
.map { $0.encrypted(by: key) }
.joined()
}
func decrypted(by key: Int) -> String {
return self
.compactMap { $0.alphabet }
.map { $0.decrypted(by: key) }
.joined()
}
}
// Rolling: aより前に戻ろうとするとzに飛ぶ現象を、ここではRollingと定義します。
func testDecryptIfNoRolling() {
let result = "d".decrypted(by: 1)
assert(result == "c", "result value is \(result)")
}
func testDecryptIfRolling() {
let result = "d".decrypted(by: 4)
assert(result == "z", "result value is \(result)")
}
func testEncryptIfNoRolling() {
let result = "d".encrypted(by: 1)
assert(result == "e", "result value is \(result)")
}
func testEncryptIfRolling() {
let result = "z".encrypted(by: 1)
assert(result == "a", "result value is \(result)")
}
testDecryptIfNoRolling()
testDecryptIfRolling()
testEncryptIfNoRolling()
testEncryptIfRolling()
for index in 0...25 {
print("\(index): \("pelcgbtencul".decrypted(by: index))")
/*
0: pelcgbtencul
1: odkbfasdmbtk
2: ncjaezrclasj
3: mbizdyqbkzri
4: lahycxpajyqh
5: kzgxbwozixpg
6: jyfwavnyhwof
7: ixevzumxgvne
8: hwduytlwfumd
9: gvctxskvetlc
10: fubswrjudskb
11: etarvqitcrja
12: dszquphsbqiz
13: cryptography
14: bqxosnfqzogx
15: apwnrmepynfw
16: zovmqldoxmev
17: ynulpkcnwldu
18: xmtkojbmvkct
19: wlsjnialujbs
20: vkrimhzktiar
21: ujqhlgyjshzq
22: tipgkfxirgyp
23: shofjewhqfxo
24: rgneidvgpewn
25: qfmdhcufodvm
*/
}
assert("pelcgbtencul".decrypted(by: 13) == "cryptography")
let expectation = "helloworld"
print(expectation) // helloworld
let encrypted = expectation.encrypted(by: 4)
print(encrypted) // lippsasvph
let decrypted = encrypted.decrypted(by: 4)
print(decrypted) // helloworld
assert(expectation == decrypted)
得られた文字列
13番目がcryptography
になっています!
盗聴成功!
感想
意図せずCompositeパターンになったような気がする?
pythonではテストユニットを用意するのが面倒臭い時にassertionで代用する人がいるんですが、Playgroundでも同じような考えでいけそうです。Playgroundのユニットテスト面倒で...。
暗号化実装面白いです!!!!!
今後もしばらく暗号実装投稿していくと思いますが、結城先生の暗号技術入門がマジ面白いのでオススメいたします!