728x90
코틀린을 익힐 겸 토이프로젝트로 플레이페어 암호 알고리즘을 구현하기로 했습니다.
개요
플레이페어 알고리즘이란 1800년대 휘트스톤이 만든 암호기법으로 대체법에 해당됩니다.
요약해서 설명드리자면
1. 알파벳 26자를 5*5 테이블에 넣는다. (I / J 는 같은 칸에 I로 넣는다.)
2. 키문자열을 맨 위 배열부터 채워 넣는다.
3. 암호화: 입력한 문자열을 키 테이블에 입력하여 특정 규칙에 따라 치환한다.
4. 복호화: 암호화 규칙의 반대로 치환한다.
등.. 다른 디테일한 규칙은 다른 사이트에서 확인해보면 될 듯 합니다.
코드 설명
main.kt
package com.main
import KeyTable
import PlayfairAlgorithm
import java.util.*
fun main() {
//클래스선언
var keyClass:KeyTable = KeyTable()
var cipherClass:PlayfairAlgorithm = PlayfairAlgorithm()
//변수선언
var plainText:String="instruments" // 평문
var cipherText:String="Monarchy" // 암호문자
var cipherKey = cipherClass.makeCipherKey(cipherText.uppercase(Locale.getDefault())) //암호문 변형 APPLE -> APLE
var keyTable = keyClass.makeKey(cipherKey) // 암호표
var encString = cipherClass.encrypt(plainText,keyTable)
println("암호화 값: $encString")
var decString = cipherClass.decrypt(encString,keyTable)
println("디코딩 값: $decString")
}
키를 입력하고 출력하는 main.kt 입니다.
특별한 거 없이 평문문자와 암호화 할 키를 입력하고, 알고리즘 메소드를 호출해 출력시킵니다.
KeyTable.kt
import java.lang.Exception
class KeyTable() {
var key = Array<Array<Char?>>(5) { Array<Char?>(5) { null } }
fun makeKey(cipherKey: String): Array<Array<Char?>> {
try {
var rows = 0
var columns = 0
for (char in cipherKey) {
key[rows][columns] = char
columns++
if (columns > 4) {
columns = 0
rows++
}
}
} catch (e: ArrayIndexOutOfBoundsException) {
println("배열 대입할 수 있는 값 초과: $e")
return key
} catch (e: Exception) {
println("미지정 오류: $e")
}
combineAlphabetArrayAndKeyTable(key, makeAlphabetArray(cipherKey))
return key
}
private fun makeAlphabetArray(cipherKey: String): CharArray {
var alphabetArray = ArrayList<Char>()
for (i in 'A'..'Z') {
if(i!='J') { // I랑 J는 같은 문자로 취급한다.
alphabetArray.add(i)
}
}
return alphabetArray.subtract(cipherKey.toCharArray().asIterable().toSet()).toCharArray()
}
private fun combineAlphabetArrayAndKeyTable(keyTable: Array<Array<Char?>>, alphabetArray: CharArray):Array<Array<Char?>> {
var columns = 0
var cnt = 0
for (i in keyTable.indices) {
for (j in keyTable[i]) {
if (j == null) {
keyTable[i][columns] = alphabetArray[cnt]
cnt++
}
columns++
if(columns>=5) columns=0
}
}
return keyTable
}
}
암호화 표를 생성하는 KeyTable입니다.
PlayfairAlgorithm.kt
import java.lang.Exception
class PlayfairAlgorithm {
fun encrypt(plainText:String, keyTable:Array<Array<Char?>>):String{
// 암호화 결과 담을 변수
var encryptText = StringBuilder()
try {
// 텍스트 알고리즘 처리를 위한 배열화
var textArray = makeTextArray(plainText)
println("key: ${keyTable.contentDeepToString()}")
println("array: $textArray")
// target의 쌍이 keyTable에서의 좌표값 구하기
for (textCouple in textArray) {
var compareArray = ArrayList<Pair<Int, Int>>()
for (i in textCouple) {
for (keyArray in keyTable) {
for (j in keyArray) {
if (i == j) {
var row = keyTable.indexOf(keyArray)
var col = keyArray.indexOf(i)
compareArray.add(Pair(row, col))
}
}
}
}
var firstElement: Char
var secondElement: Char
// 규칙 1. 암호화 하려는 두 문자가 서로다른 행과 다른 열에 존재할 경우
// - 사각형을 그려 서로 수평하는 위치의 반대쪽 문자로 치환
if (compareArray[0].first != compareArray[1].first && compareArray[0].second != compareArray[1].second) {
firstElement = keyTable[compareArray[0].first][compareArray[1].second]!!
secondElement = keyTable[compareArray[1].first][compareArray[0].second]!!
// 규칙 2. 두 문자가 같은 열에 있는 경우
// - 한 칸 씩 밑으로 내려간다. 내려갈 자리가 없다면 맨 위로
} else if (compareArray[0].first != compareArray[1].first && compareArray[0].second == compareArray[1].second) {
if (keyTable.size - 1 > compareArray[0].first) {
firstElement = keyTable[compareArray[0].first + 1][compareArray[0].second]!!
} else {
firstElement = keyTable[0][compareArray[0].second]!!
}
if (keyTable.size - 1 > compareArray[1].first) {
secondElement = keyTable[compareArray[1].first + 1][compareArray[1].second]!!
} else {
secondElement = keyTable[0][compareArray[1].second]!!
}
// 규칙 3. 두 문자가 같은 행에 있는 경우
// - 한 칸씩 으론쪽으로 이동한다. 이동할 자리가 없다면 맨 왼쪽으로
} else if (compareArray[0].first == compareArray[1].first && compareArray[0].second != compareArray[1].second) {
if (keyTable.size - 1 > compareArray[0].second) {
firstElement = keyTable[compareArray[0].first][compareArray[0].second-1]!!
} else {
firstElement = keyTable[compareArray[0].first][0]!!
}
if (keyTable.size - 1 > compareArray[1].second) {
secondElement = keyTable[compareArray[1].first][compareArray[1].second-1]!!
} else {
secondElement = keyTable[compareArray[1].first][0]!!
}
} else {
throw Exception("규칙에 없는 인자값")
}
encryptText.append(firstElement)
encryptText.append(secondElement)
}
} catch (e:Exception){
println("예외발생 ${e.printStackTrace()}")
}
// 복호화 알고리즘
// - 암호화 때와 같으나 규칙 2,3은 움직인 방향과 반대로 이동한다.
return encryptText.toString()
}
fun decrypt(plainText:String, keyTable:Array<Array<Char?>>):String{
// 암호화 결과 담을 변수
var encryptText = StringBuilder()
try {
// 텍스트 알고리즘 처리를 위한 배열화
var textArray = makeTextArray(plainText)
println("key: ${keyTable.contentDeepToString()}")
println("array: $textArray")
// target의 쌍이 keyTable에서의 좌표값 구하기
for (textCouple in textArray) {
var compareArray = ArrayList<Pair<Int, Int>>()
for (i in textCouple) {
for (keyArray in keyTable) {
for (j in keyArray) {
if (i == j) {
var row = keyTable.indexOf(keyArray)
var col = keyArray.indexOf(i)
compareArray.add(Pair(row, col))
}
}
}
}
var firstElement: Char
var secondElement: Char
// 규칙 1. 암호화 하려는 두 문자가 서로다른 행과 다른 열에 존재할 경우
// - 사각형을 그려 서로 수평하는 위치의 반대쪽 문자로 치환
if (compareArray[0].first != compareArray[1].first && compareArray[0].second != compareArray[1].second) {
firstElement = keyTable[compareArray[0].first][compareArray[1].second]!!
secondElement = keyTable[compareArray[1].first][compareArray[0].second]!!
// 규칙 2. 두 문자가 같은 열에 있는 경우
// - 한 칸 씩 위로 올라간다. 올라갈 자리가 없다면 맨 밑으로
} else if (compareArray[0].first != compareArray[1].first && compareArray[0].second == compareArray[1].second) {
if (0 < compareArray[0].first) {
firstElement = keyTable[compareArray[0].first-1][compareArray[0].second]!!
} else {
firstElement = keyTable[4][compareArray[0].second]!!
}
if (0 < compareArray[1].first) {
secondElement = keyTable[compareArray[1].first-1][compareArray[1].second]!!
} else {
secondElement = keyTable[4][compareArray[1].second]!!
}
// 규칙 3. 두 문자가 같은 행에 있는 경우
// - 한 칸씩 으론쪽으로 이동한다. 이동할 자리가 없다면 맨 왼쪽으로
} else if (compareArray[0].first == compareArray[1].first && compareArray[0].second != compareArray[1].second) {
if (0 < compareArray[0].second) {
firstElement = keyTable[compareArray[0].first][compareArray[0].second+1]!!
} else {
firstElement = keyTable[compareArray[0].first][4]!!
}
if (0 < compareArray[1].second) {
secondElement = keyTable[compareArray[1].first][compareArray[1].second+1]!!
} else {
secondElement = keyTable[compareArray[1].first][4]!!
}
} else {
throw Exception("규칙에 없는 인자값")
}
encryptText.append(firstElement)
encryptText.append(secondElement)
}
println("plainText = ${plainText}")
println("plainText.length = ${plainText.length}")
if(plainText.length%2==1){
encryptText.deleteCharAt(plainText.lastIndex)
}
} catch (e:Exception){
println("예외발생 ${e.printStackTrace()}")
}
// 복호화 알고리즘
// - 암호화 때와 같으나 규칙 2,3은 움직인 방향과 반대로 이동한다.
return encryptText.toString()
}
// HELLO -> [[H,E],[L,X],[O,X]] 암,복호화 알고리즘에 사용되는 텍스트 배열 생성
private fun makeTextArray(plainText: String): ArrayList<ArrayList<Char>> {
var plainTextList: ArrayList<ArrayList<Char>> = ArrayList<ArrayList<Char>>()
plainTextList.add(ArrayList())
var flag = 0 // for문 2개씩 끊기 위한 flag 변수
var cnt = 0 //for 카운터 변수
for (i in plainText.uppercase()) {
if (flag >= 2) {
flag = 0
cnt++
plainTextList.add(ArrayList())
}
if (plainTextList[cnt].isNotEmpty() && plainTextList[cnt][0] == i) { // 같은 문자일 시 X 삽입
plainTextList[cnt].add('X')
plainTextList.add(ArrayList())
cnt++
plainTextList[cnt].add(i)
} else {
plainTextList[cnt].add(i)
flag++
}
}
// 마지막 자리 부족할 시 X 추가
if (plainTextList[plainTextList.size - 1].size <= 1) {
plainTextList[plainTextList.size - 1].add('X')
}
return plainTextList
}
//암호키 만드는 함수 ex) APPLE -> APLE
fun makeCipherKey(plainText:String):String{
var cipherKey:String=""
for(i in plainText.indices){
if(plainText.indexOf(plainText.elementAt(i))==i){
cipherKey+=plainText.elementAt(i)
}
}
return cipherKey
}
}
문자와 키테이블을 변수로 받아 암호화, 복호화를 수행하는 알고리즘(코어)클래스입니다.
중간에 찾았는데 코틀린에는 좌표 값을 저장하기 적당한 Pair라는 자료형이 있더라고요.
뒤늦게 발견해서 다소 삽질을 했습니다. (Triple이라는 3차원 자료형도 있음)
동작화면
X 값 처리는 아무리 자료를 찾아봐도 따로 처리하는 방법이 없는 것 같네요
그냥 X를 넣거나 제거하는 수밖에 없을 것 같습니다.
객체지향으로 구현하려고 해보았는데 아무래도 단일적인 기능을 하는 프로젝트다 보니 장점을 살리기가 쉽지 않았습니다.
요새 번아웃이 온 것 같아요.
조금 쉬엄쉬엄 하려고 합니다..
'Kotlin' 카테고리의 다른 글
[Kotlin] 코틀린 기초문법 요약 (Kotlin Summary) (0) | 2022.05.07 |
---|