LoginSignup
16
8

More than 3 years have passed since last update.

紀元前1世紀のシーザー暗号を現代でも使ってみよう

Last updated at Posted at 2019-04-28

解きたい問題

結城先生の暗号技術入門で、「PELCGBTENCUL という、シーザー暗号によって暗号化された暗号文を復号化してみましょう。」というクイズが出ました。

シーザー暗号とは?

こちらは紀元前1世紀ごろに、ユリウス・カエサルさんが用いたと言われる換字式暗号です。
https://ja.wikipedia.org/wiki/暗号史

アルファベットに対して、指定の数字数だけずらしていく暗号化方式です。
ex) key: 3, abc →(暗号化) → def

手でアルファベットをずらしていくのが面倒でしかたなかったので、21世紀ではプログラムを組んで算出していきます。
一番手慣れたSwiftを使ってPlaygroundで実装してみました。

ついでに暗号化も実装しました。

シーザー暗号のプログラム

crypt.swift
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のユニットテスト面倒で...。

暗号化実装面白いです!!!!!

今後もしばらく暗号実装投稿していくと思いますが、結城先生の暗号技術入門がマジ面白いのでオススメいたします!

16
8
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
8