학사 나부랭이

Cryptography - Opening, 고전 암호 본문

自習/Cryptography

Cryptography - Opening, 고전 암호

태양왕 해킹 (14세) 2021. 4. 11. 20:13

암호

  • 키가 있어야 정상적으로 해독 가능
  • 보안을 위해 변환
  • 평문-(암호화)->암호문-(복호화)->평문
  • 단, 해시값은 복호화 불가

코드

  • 변환 규칙을 공개해서 누구나 해독 가능
  • 편의를 위해 변환
  • 평문-(인코딩)->코드-(디코딩)->평문

들어가기 전에 가벼운 파이썬

리스트

  • 배열인데 자료형을 따지지 않음 (ex. li = [1, 2.4, 'a', 'asdf', ['가', 0, 3.1]])
  • 리스트 객체의 append()로 원소 추가 (ex. li.append(4) == [1, 2.4, 'a', 'asdf', ['가', 0, 3.1], 4])
  • 리스트 객체의 remove()나 del 키워드로 원소 삭제 (ex. li.remove('asdf') or del li[3] == [1, 2.4, 'a', ['가', 0, 3.1]])

딕셔너리

  • 키:값으로 쌍을 이루는 순서가 정해지지 않은 배열 (ex. dc = {'pura':'sino', 'pru':'ssia'})
  • 딕셔너리 객체[키] = 값으로 원소 추가 (ex. dc['pra'] = 'china' == {'pura':'sino', 'pru':'ssia', 'pra':'china'}
  • 딕셔너리 객체의 keys()로 키 추출 (ex. dc.keys() == dict_keys(['pura', 'pru', 'pra'])
  • 딕셔너리 객체의 values()로 값 추출 (ex.  dc.values() == dict_values(['sino', 'ssia', 'china'])
  • del 키워드로 원소 삭제 (ex. del dc['pru'] == {'pura':'sino', 'pra':'china'}

lambda 함수

  • lambda 변수, 변수 , ... : 식, 인자, 인자, ... 의 형태
  • 한 번 쓰고 버리는 일회성 함수
  • 인자들을 이용한 "식"의 결과를 리턴 (ex. lambda x, y: x * y, 2, 3 == 6)

map 함수

  • 함수와 반복 가능한 자료형(이하 배열)을 인자로 받음
  • 배열의 원소를 함수에 넣어 리턴 값으로 다시 배열을 만듦 (ex. map(sqare, [0, 1, 2] == [0, 1, 4])

간단한 암호화 코드

def makeCodebook():
    decbook = {'5':'a', '2':'b', '#':'d', '8':'e',
               '1':'f', '3':'g', '4':'h', '6':'i',
               '0':'l', '9':'m', '*':'n', '%':'o',
               '=':'p', '(':'r', ')':'s', ';':'t',
               '?':'u', '@':'v', ':':'y', '7':' '}
    encbook = {}
    for k in decbook:
        val = decbook[k]
        encbook[val] = k #decbook의 키와 값을 반전
    return encbook, decbook

def encrypt(msg, encbook):
    for c in msg:
        if c in encbook:
            msg = msg.replace(c, encbook[c]) #msg의 문자 하나를 대체
    return msg
def decrypt(msg, decbook):
    for c in msg:
        if c in decbook:
            msg = msg.replace(c, decbook[c])
    return msg
if __name__ == '__main__':
    plaintext = 'ⅰlove you with all my heart'
    encbook, decbook = makeCodebook()
    print(encbook)
    print(decbook)
    ciphertext = encrypt(plaintext, encbook)
    print(ciphertext)
    deciphertext = decrypt(ciphertext, decbook)
    print(deciphertext)

글자를 교환한 환자(換字)암호

ENC = 0
DEC = 1
def makeDisk(key):
    keytable = map(lambda x: (chr(x + 65), x), range(26)) #lambda 결과: ([chr(x + 65):x])map 결과: [A:0, B:1, ..., Z:25]
    key2index = {}
    for t in keytable:
        alphabet, index = t[0], t[1]
        key2index[alphabet] = index #keytable 키와 값을 반전
    print(key2index)
    if key in key2index:
        k = key2index[key]
    else:
        return None, None
    enc_disk = {}
    dec_disk = {}
    for i in range(26):
        enc_i = (i + k) % 26
        enc_ascii = enc_i + 65 #key 만큼 알파벳 순서의 뒤로 미룸
        enc_disk[chr(i+65)] = chr(enc_ascii) #a = a + key 의 딕셔너리 딕셔너리
        dec_disk[chr(enc_ascii)] = chr(i + 65) #a + key = a 의 딕셔너리
    return enc_disk, dec_disk

def caesar(msg, key, mode):
    ret = ''
    msg = msg.upper()
    enc_disk, dec_disk = makeDisk(key)
    if enc_disk is None:
        return ret
    if mode is ENC:
        disk = enc_disk
    if mode is DEC:
        disk = dec_disk
    for c in msg:
        if c in disk:
            ret += disk[c]
        else:
            ret += c
    return ret

def main():
    plaintext = 'BECARFULFORASSASSINATE'
    key = 'F'
    print('origin:\t\t%s', plaintext.upper())
    ciphertext = caesar(plaintext, key, ENC)
    print('cipherted:\t\t%s', ciphertext)
    deciphertext = caesar(ciphertext, key, DEC)
    print('deciphered:\t\t%s', deciphertext)
if __name__ == '__main__':
    main()

문자의 자리를 바꾸는 전치(転置) 암호 ~Ceasar cipher~

모든 전치 암호는 환자 암호이며 전치 암호가 아닌 것 같지만 전치 암호인 것도 있다. (ex. 한 비트씩 미루기)

 위의 카이사르(시저) 암호의 암호키는 많아봤자 26개이다. 그래서 아래처럼 이를 변형해 암호키의 개수를 늘린 Affine cipher도 있다.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(전략)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    for i in range(26):
        enc_i = (k1 * i + k2) % 26
        enc_ascii = enc_i + 65
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(후략)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

k1은 값의 중복을 방지하기 위해 26과 서로소(공약수가 없음)이며 k2는 25 이하의 값이다.

 

주상(柱状) 전치 암호

 평문을 배열한 후 열 방향으로 문장을 구성한다.

이 경우 암호문은 CGKOSBFJNRDHLPTAEIMQ이고 암호키는 4213이다.

ENC = 0
DEC = 1


def parseKey(key):
	tmp = []
	key = key.upper()
	for i, k in enumerate(key): #index 부여 == [(0, B), (1, R), ...]
		tmp.append((i, k)) #(0, B)부터 삽입
	tmp = sorted(tmp, key=lambda x:x[1]) #알파벳 순으로 정렬 == [(2, A), (0, B), ...]
	print('tmp: ', tmp)
	enc_table = {}
	dec_table = {}
	for i, r in enumerate(tmp): #i = enumerate로 붙은 index, r = (2, A)
		enc_table[r[0]] = i #enct[2] = 0, ...
		dec_table[i] = r[0] #dect[0] = 2, ...
	print('enc-table: ', enc_table)
	print('dec-table: ', dec_table)
	return enc_table, dec_table
def transposition(msg, key, mode):
	msgsize = len(msg)
	keysize = len(key)	
	ret = ''
	filler = ''	
	if msgsize%keysize != 0:
		filler = '0'*(keysize - msgsize%keysize) #패딩(마지막 블록을 다 채워서 하나의 블록으로 만들기 위해)		
	msg = msg.upper()
	msg += filler
	enc_table, dec_table = parseKey(key)
	if mode == ENC:
		table = enc_table
	else:
		table = dec_table
	if mode == ENC:
		buf = ['']*keysize #짤의 열 개수
		for i, c in enumerate(msg):
			col = i%keysize #이차원 배열의 행
			index = table[col] #행이 섞인대로 넣어야제!
			buf[index] += c #하모 그렇제!
		for text in buf:
			ret += text #그걸 한 줄로 보내야제!
	else:
		blocksize = int(msgsize/keysize) # m개의 문자를 k(5) 개의 버퍼로 출력할 때 필요한 크기
		buf = ['']*keysize
		pos = 0
		for i in range(keysize):
			text = msg[pos:pos+blocksize] #block 크기만큼 잘라서
			print(text)
			index = table[i]
			buf[index] += text
			pos += blocksize #네 다음 블록~
		for i in range(blocksize):
			for j in range(keysize):
				if buf[j][i] != '0': #패딩한거 빼고
					ret += buf[j][i]	
	return ret
def main():
	key = 'BRAIN'	
	msg = 'TREASUREBOXISBURRIEDATTWOHUNDREDFEETTONORTHEASTAWAYFROMYOURHOME'
	ciphertext = transposition(msg, key, ENC)
	deciphertext = transposition(ciphertext, key, DEC)
	print('Original:\t\t%s' %msg.upper())
	print('Ciphered:\t\t%s' %ciphertext)
	print('Deciphered:\t\t%s' %deciphertext)


if __name__ == '__main__':
	main()
	

 

Comments