kerberos 버전별 구현[소스]
오늘부터는 글의 자유도를 높이기 위해 의식의 흐름기법에 따라 끄적끄적 하도록 하겠습니다ㅏㅏ~
kerberos? 커베로스 커버로스 .. 여러가지 이름으로 불리네요.
저는 처음 배울 때 커버로스로 배웠기 때문에 커버로스라고 말을 하겠습니다.
커버로스는 version1부터 version5까지 나왔는데
여기서는 버전 1과 버전 2를 직접 구현하러 떠나봅시다~
1. 이 커버로스에 대해 알아봅시다!!
1-1) 커버로스란 무엇일까요?
▶ 검색을 해보니 그리스신화에서 저승문을 지키는 머리가 3개인 개님의 이름? 이라고 나오네요.
▶ 조금더 자세히 설명하면 MIT에서 개발된 대칭키 방식에 의한 인증시스템 정도로 볼 수 있겠네요.
2. 커버로스 버전1
2-1) 커버로스 버전1의 원리를 먼저 알아볼까요?
# 그림이 조금 그렇지만... 에헴.. 쉽게 이해해보도록 합니다!!
[1] 놀이동산 손님이 나야 나 알지? 놀이동산에 들어갈 수 있게 티켓 내놔아!! 라고 징징댑니다.
[2] 놀이동산 알바생이 티켓 줄게~ 대신 딴사람 주기만해봐!! 라고 무언의 압박을 합니다.
[3] 손님은 받은 티켓을 놀이동산에 입장할 때 티켓있는 정당한 사용자라고 당당하게^^ 보여줍니다.
[4] 티켓확인하는 알바생은 이 티켓이 암표가 아닌지 확인합니다!!
이제 아래 번호별로 매칭해서 보도록 하지요~
[1] 클라이언트 -> 인증서버(Authentication Server)
▶ 클라이언트 => 자신의 ID, 자신의 패스워드, 접속하려는 서버의 ID를 인증서버로 전송을 합니다.
[2] 인증서버 -> 클라이언트
▶ 인증서버 => master_key로 Encryption[클라이언트의 ID, 클라이언트의 IP, 접속하려는 서버의 ID] = 티켓
[3] 클라이언트 -> 인증서버
▶ 클라이언트 => 클라이언트ID와 티켓을 접속할 서버에게 전송합니다.
[4] 접속할 서버 -> 클라이언트
▶ 티켓을 복호화해서 베리파이(verify) 한 이후
클라이언트의 ID를 비교하고,
현재 접속한 IP와 티켓에 적힌 IP주소를 비교하고,
내 서버번호를 확인하고 서비스를 제공합니다.
나름 까다로운 과정이 들어가 있네요~
역시 보안은 안 할 수도 없고 하자니 번거로운...
불편한 진실이라는 점을 알 수 있네요^^
조금 불편해도 안전해야
마음도 편하고 든든하죠 하하!!
이 부분을 돌려봅시다!! python3으로
2-2) 커버로스 버전1[소스]
# mylib.py는 triple des로 암호화하기위해 라이브러리를 추가하였습니다.
# 이 부분은 아래 링크에서 나름 열심히? 설명이 되어 있으므로 넘어갈게요.
# 참고자료 : https://ccurity.tistory.com/249
[1] mylib.py
from Crypto.Cipher import DES3
from Crypto.Hash import SHA512
class MyDES3():
def __init__(self,key,iv='iviv'):
key = key.encode()
iv = iv.encode()
hash = SHA512.new(key)
hv = hash.digest()
self.key = hv[:24]
hash.update(iv)
hv = hash.digest()
self.iv = hv[:8]
def enc(self,msg1): # msg1 is unicode
msg,addlen=self.make8stringEx(msg1)
msg=self.addh(msg,addlen)
msg = msg.encode()
des3 = DES3.new(self.key,DES3.MODE_CBC,self.iv)
return des3.encrypt(msg)
def dec(self,msg1): # msg1 is byte code
des3 = DES3.new(self.key,DES3.MODE_CBC,self.iv)
msg = des3.decrypt(msg1)
msg = self.cutmsg(msg)
return msg
def cutmsg(self,msg):
msg = msg.decode('utf-8')
index= msg.find('#')
sub = msg[:index]
nsize = int(sub)
nmsg=msg[8:len(msg) - nsize]
return nmsg
def addh(self,msg,size):
numstr = str(size)
fsize = 8 - len(numstr)
fill = '#'*fsize
numstr.encode()
nmsg = '123'
nmsg = numstr + fill + msg
return nmsg
def make8stringEx(self,msg):
filler = b''
mlen = len(msg)
if mlen % 8 != 0:
addlen = 8 - mlen % 8
filler = '0'*addlen
msg += filler
return msg,addlen
def test():
pass
msg = '1234 password'
des3 = MyDES3('key')
cmsg= des3.enc(msg)
print(msg)
print(cmsg)
pmsg = des3.dec(cmsg)
print(pmsg)
#test()
# 참고_★빵빵!! = client.py의 ip적는 부분은 통신할 상대의 ip주소로 수정하여야 통신이 됩니다~
[2] client.py
import socket
import pickle
import mylib
import base64
import sys
def getTicket(IDc, pw, IDv): # (ID, PW, 접속할 서버번호)
host = '192.168.234.102' # 접속할 티켓서버 IP
port = 10100 # 접속할 티켓서버의 port
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 클라이언트 소켓 = 전송할 주소체계 타입(IPv4, TCP)
client.connect((host, port))
# 소켓으로 연결시도((ip, port))
send_data = [IDc, pw, IDv]
send_pickle = pickle.dumps(send_data)
# 리스트를 전송하기위해 시리얼라이즈
client.send(send_pickle)
# 티켓서버로 리스트덤프를 전송
ticket = client.recv(1024)
# 접속할 서버에 대한 티켓을 받음
if not ticket: # 티켓을 정상적으로 받지 못한 경우
print('Not receive a ticket!!')
client.close()
sys.exit(1)
client.close() # 티켓서버와의 세션끊기
return ticket
def server_connect(IDc, ticket):
host = '192.168.234.102' # 접속할 서비스서버의 IP
port = 1111 # 접속할 서비스서버의 port
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((host, port))
send_data = [IDc, ticket]
send_pickle = pickle.dumps(send_data)
# 클라이언트의 ID와 티켓이 담긴 리스트를 전송하기위해 시리얼라이즈
client.send(send_pickle)
# 접속할 서버로 리스트덤프를 전송
recv_pickle = client.recv(1024)
# 서버로로부터 결과 받기
if recv_pickle == b'':
print('Input Error!!')
client.close()
sys.exit(1)
recv_data = pickle.loads(recv_pickle)
# 덤프를 디시리얼라이즈
client.close() # 접속서버와의 세션끊기
return recv_data
def main():
IDc = input('user_ID : ') # user100
pw = input('user_PW : ') # d4tai1
IDv = input('server : ') # 1
# 클라이언트의 ID, PW, 접속할서버번호 입력
ticket = getTicket(IDc, pw, IDv)
# 클라이언트의 ID, PW, 접속할서버번호로 티켓을 얻는 과정
recv = server_connect(IDc, ticket)
# 클라이언트의 ID와 티켓을 들고 서버에 접속하는 과정
print('recv : {}'.format(recv))
if __name__ == '__main__':
main()
[3] AS.py
import socket
import pickle
import mylib
import base64
import sys
def authentication(IDc, pw):
if IDc == 'user100' and pw == 'd4tai1':
return True
else:
return False
def server():
host = ''
port = 10100 # Listening port
key = 'kerveros_1' # des3 암호화 키
iv = 'ticket' # des3 초기벡터
des3 = mylib.MyDES3(key, iv) # 암호화 하기 위해 객체 생성
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 서버 소켓 = 전송할 주소체계 타입(IPv4, TCP)
server.bind((host, port))
# 소켓을 포트에 바인딩(매핑)
try:
while True:
print('----------------------------------------')
print(' [*] Ticket Server')
print(' [*] Listening...')
server.listen(1) # 접속이 있을때까지 포트열고 대기
client, addr = server.accept()
# 클라이언트와 연결되어 통신가능한 (소켓, 주소)로 반환
print(' [+] Connect Client')
print(' [+] addr = {}, port {}'.format(addr[0], addr[1]))
recv_pickle = client.recv(1024)
# 클라이언트가 티켓을 얻기 위한 정보(리스트덤프)
recv_data = pickle.loads(recv_pickle)
# 리스트덤프를 디시리얼라이즈
if not authentication(recv_data[0], recv_data[1]):
client.close() # 정보가 일치하지 않으면 연결종료
print('Input information is different!!')
else:
recv_data[1] = addr[0]
# pw는 확인이 끝났으므로 클라이언트 주소를 저장
print(' [+] client info = {}'.format(recv_data))
ticket = [x for x in recv_data]
# 티켓 = [클라이언트ID, 클라이언트address, 접속서버번호]
ticket_pickle = pickle.dumps(ticket)
# 티켓리스트를 시리얼라이즈
ticket_pickle = ticket_pickle.decode('utf-8', 'ignore')
# des3.enc()에서 str타입으로 받으므로 타입변환
ticket_pickle_des3 = des3.enc(ticket_pickle)
# 트리플DES로 티켓덤프를 암호화
ticket_pickle_des3_base64enc = base64.b64encode(ticket_pickle_des3)
# 암호화 티켓을 전송하기위해 base64로 인코딩
client.send(ticket_pickle_des3_base64enc)
# 인코딩된 티켓을 클라이언트에게 전송
client.close() # 클라이언트와 세션종료
except KeyboardInterrupt:
print('\b\b [-] '+str(sys.exc_info()[0]).split()[1][1:-2])
# [Ctrl + c] 등으로 인한 키보드 인터럽트 발생 시 출력
finally:
print('server end')
# 서버종료 시 출력
if __name__ == '__main__':
server()
[4] v.py
import socket
import pickle
import mylib
import base64
import sys
from datetime import datetime
def authentication(IDc, addr, ticket):
if IDc == ticket[0] and addr == ticket[1] and '1' == ticket[2]:
# 현재 서버에 접속한 ID와 티켓에 적힌 ID가 동일한지
# 현재 서버에 접속한 IP주소와 티켓에 적힌 IP주소가 동일한지
# 현재 서버의 번호와 티켓에 적혀있는 서버의 번호가 동일한지
return True
else:
return False
def server():
host = ''
port = 1111
key = 'kerveros_1'
iv = 'ticket'
des3 = mylib.MyDES3(key, iv)
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((host, port))
try:
while True:
print('----------------------------------------')
print(' [*] Server')
print(' [*] Listening...')
server.listen(1) # 접속이 있을때까지 포트열고 대기
client, addr = server.accept()
# 클라이언트와 연결되어 통신가능한 (소켓, 주소)로 반환
print(' [+] New Connect Client')
print(' [+] addr = {}, port {}'.format(addr[0], addr[1]))
recv_pickle = client.recv(1024)
# [클라이언트ID, 티켓]의 리스트덤프 받기
recv_data = pickle.loads(recv_pickle)
# 받은 리스트덤프를 디시리얼라이즈
IDc, ticket_pickle_des3_base64enc = recv_data[0], recv_data[1]
# 클라이언트ID와 티켓덤프를 암호화후 인코딩한 정보를 저장
ticket_pickle_des3 = base64.b64decode(ticket_pickle_des3_base64enc)
# base64 디코딩 후 저장
ticket_pickle = des3.dec(ticket_pickle_des3)
# 트리플 DES3로 복호화 후 저장
ticket_pickle = ticket_pickle.encode()
# byte타입으로 변환
ticket = pickle.loads(b'\x80'+ticket_pickle)
# 티켓덤프를 메모리에 로드
if not authentication(IDc, addr[0], ticket):
client.close() # 정보가 일치하지 않으면 연결종료
print('user_ID or address or server_number is different!!')
else:
send_data = [datetime.now().year, datetime.now().month, datetime.now().day]
# 시간정보를 리스트로 저장
send_data_pickle = pickle.dumps(send_data)
client.send(send_data_pickle)
print(send_data)
client.close()
except KeyboardInterrupt:
print('\b\b [-] '+str(sys.exc_info()[0]).split()[1][1:-2])
# [Ctrl + c] 등으로 인한 키보드 인터럽트 발생 시 출력
finally:
print('server end')
# 서버종료 시 출력
if __name__ == '__main__':
server()
2-3) 커버로스 버전1[사용방법]
[1] [python3 AS.py] 명령어로 AS서버 동작
[2] [python3 v.py] 명령어로 서비스서버 동작
[3] [python3 client.py] 명령어로 클라이언트 동작
[4] 클라이언트에서 총 3개를 입력을 받음
▶ user_ID를 (user100)으로 입력
▶ user_PW를 (d4tai1)로 입력
▶ server를 (1)로 입력
- user_ID와 user_PW가 일치하지 않을 경우 티켓발급 불가
- server번호가 다를 경우 티켓을 발급되지만 서버에서 서비스 받을 수 없음
- 티켓서버에 접속한 IP주소와 서비스서버에 접속한 IP주소가 다를 경우 즉시 서버와 연결종료
와 같이 사용할 수 있습니다.
2-4) 커버로스 버전1[시연]
총 4가지 경우가 있는 것을 확인할 수가 있습니다.
그렇다면 커버로스 버전1은 어떠한 문제점이 있고,
커버로스 버전2에서는 어떠한 기능이 추가되었을까요?
2-5) 커버로스 버전1의 취약점
[1] 서버에 접속할 때마다
비밀번호를 입력해야 한다!!
[2] 내 패스워드 스니핑해서 보세요~
non_encryption
3. 커버로스 버전2에 대해서 알아봅시다.
3-1) 커버로스 버전1의 취약점 해결
[1] 티켓을 재사용해서
비밀번호를 계속입력받지 않는
참신한?방법이 있지만,
또 다른 문제가 있다.
그것은 바로 !!!!!!
다른 서버에 접속할 경우는
또오오 입력을 해야한다.
[2] 패스워드를 공유할 수는 없으므로
티켓발행서버(Ticket_Granting_Server)를
도입한다!!
이러면!! 서버를 또 만들어야한다..는
말로 들리는데...
어쨋든 구현해 봅시다~
3-2) 커버로스 버전2의 프로토콜
# 위에서 어느정도 설명을 했으니
여기서는 이론을 적어보도록 할게요~
[1] 클라이언트는 인증서버에게
클라이언트ID와 티켓서버의ID를 던져줍니다.
[2] 인증서버는 클라이언트에게
[클라이언트ID, 클라이언트 접속IP, 티켓서버의ID, TimeStamp1, Lifetime1]을
티켓서버의 키로 암호화 합니다.
# Timestamp1은 티켓 생성시간을 의미하고
# Lifetime1은 티켓 유효기간을 의미해요~
이 말은 나중에 이 티켓이 유효한지에
대해 검증한다는 말로 들리죠?ㅎㅎ
[3] 클라이언트는 티켓서버에게
클라이언트ID와 접속할 서버의ID, 티켓(티켓서버를 이용할 수 있는)을
전송합니다.
[4] 티켓서버는 클라이언트에게
접속할 서버의 티켓을 발행해줍니다.
접속할 서버의 티켓의 내용은?
[클라이언트ID, 클라이언트 접속IP, 접속할 서버의ID, TimeStamp2, Lifetime2]을
접속할 서버의 키로 암호화 한 것을 말합니다.
[5] 클라이언트는 접속할 서버에게
클라이언트ID와 티켓(접속할 서버를 이용할 수 있는)을 전송합니다.
이 정도 되었으면 구현하러 떠나봅시다~
역시 이론보다는 ^^
3-3) 커버로스 버전2[소스]
# mylib.py는 커버로스 버전1에서 사용한 것과
동일하므로 소스는 위에꺼 사용해도 됩니다.
[1] client.py
import socket
import pickle
import mylib
import base64
import sys
def getTicket_tgs(IDc, IDv): # (ID, PW, 접속할 서버번호)
host = '192.168.234.102' # 접속할 인증서버 IP
port = 10100 # 접속할 인증서버의 port
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 클라이언트 소켓 = 전송할 주소체계 타입(IPv4, TCP)
conn.connect((host, port))
# 소켓으로 연결시도((ip, port))
send_data = [IDc, IDv]
send_pickle = pickle.dumps(send_data)
# 리스트를 전송하기위해 시리얼라이즈
conn.send(send_pickle)
# 인증서버로 리스트덤프를 전송
ticket_tgs = conn.recv(1024)
# 접속할 티켓서버에 대한 티켓을 받음
if not ticket_tgs: # 티켓서버의 티켓을 정상적으로 받지 못한 경우
print('Not receive a ticket_tgs!!')
conn.close()
sys.exit(1)
conn.close() # 인증서버와의 세션끊기
return ticket_tgs
def getTicket_server(IDc, IDv, ticket_tgs):
host = '192.168.234.102' # 접속할 티켓서버의 IP
port = 20100 # 접속할 티켓서버의 port
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
conn.connect((host, port))
send_data = [IDc, IDv, ticket_tgs]
# 티켓서버로 전송할 데이터
send_pickle = pickle.dumps(send_data)
# 클라이언트의 ID와 티켓이 담긴 리스트를 전송하기위해 시리얼라이즈
conn.send(send_pickle)
# 접속할 티켓서버로 리스트덤프를 전송
ticket_v = conn.recv(1024)
# 티켓서버로로부터 결과 받기
if not ticket_v: # 티켓이 오지 않았다면
print('Not receive a Ticket_v!!')
conn.close()
sys.exit(1)
conn.close() # 접속세션과의 세션끊기
return ticket_v
def server_connect(IDc, ticket):
host = '192.168.234.102' # 접속할 서비스서버의 IP
port = 30100 # 접속할 서비스서버의 port
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
conn.connect((host, port))
send_data = [IDc, ticket]
# 서비스서버로 전송할 데이터
send_pickle = pickle.dumps(send_data)
# 클라이언트의 ID와 티켓이 담긴 리스트를 전송하기위해 시리얼라이즈
conn.send(send_pickle)
# 접속할 서버로 리스트덤프를 전송
recv_pickle = conn.recv(1024)
# 서버로로부터 결과 받기
if not recv_pickle: # 서버로부터 데이터를 받지 못했다면
print('Data not received!!')
conn.close()
sys.exit(1)
recv_data = pickle.loads(recv_pickle)
# 덤프를 디시리얼라이즈
conn.close() # 접속서버와의 세션끊기
return recv_data
def main():
IDc = input('user_ID : ') # user100
IDtgs = input('ticket_server : ') # 10
# 클라이언트의 ID, 접속할 티켓서버번호 입력
IDv = input('service_server : ') # 100
# 접속할 서비스서버번호 입력
ticket_tgs = getTicket_tgs(IDc, IDtgs)
# 클라이언트의 ID, 접속할 티켓서버번호로 티켓서버티켓을 얻는 과정
ticket_v = getTicket_server(IDc, IDv, ticket_tgs)
# 클라이언트의 ID, 접속할 서비스서버번호와 티켓서버티켓으로 서비스서버 티켓을 얻는 과정
recv = server_connect(IDc, ticket_v)
# 클라이언트의 ID와 서비스서버티켓으로 접속시도
print('recv : {}'.format(recv))
if __name__ == '__main__':
main()
[2] AS_server.py
import socket
import pickle
import mylib
import base64
import sys
import time
def authentication(IDc, IDtgs):
if IDc == 'user100' and IDtgs == '10':
# user_ID와 티켓서버의 번호를 확인
return True
else:
return False
def makeTicket(IDc, addr, IDtgs):
key = 'kerberos_v2'
iv = 'AS_Server'
timestamp = [str(x) for x in time.localtime()[:6]]
timestamp = ''.join(timestamp)
lifetime = 3000 # 30분 00초
ticket = [IDc, addr, IDtgs, timestamp, lifetime]
# 티켓 = [클라이언트ID, 클라이언트address, 티켓서버번호, 티켓발행시간, 티켓유효기간]
ticket_pickle = pickle.dumps(ticket)
# 티켓리스트를 시리얼라이즈
# ticket_pickle += b'\x80'
ticket_pickle = ticket_pickle.decode('utf-8', 'ignore')
# des3.enc()에서 str타입으로 받으므로 타입변환
des3 = mylib.MyDES3(key, iv) # 암호화 하기 위해 객체 생성
ticket_pickle_des3 = des3.enc(ticket_pickle)
# 트리플DES로 티켓덤프를 암호화
ticket_pickle_des3_base64enc = base64.b64encode(ticket_pickle_des3)
# 암호화 티켓을 전송하기위해 base64로 인코딩
return ticket_pickle_des3_base64enc
def main():
host = ''
port = 10100
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 서버 소켓 = 전송할 주소체계 타입(IPv4, TCP)
server.bind((host, port))
# 소켓을 포트에 바인딩(매핑)
print(' [*] AS Server')
print(' [*] Listening...')
try:
server.listen(1) # 접속이 있을때까지 포트열고 대기
conn, addr = server.accept()
# 클라이언트와 연결되어 통신가능한 (소켓, 주소)로 반환
print(' [+] Connect Client')
print(' [+] addr = {}, port {}'.format(addr[0], addr[1]))
recv_pickle = conn.recv(1024)
# 클라이언트가 티켓을 얻기 위한 정보(리스트덤프)
recv_data = pickle.loads(recv_pickle)
# 리스트덤프를 디시리얼라이즈
if not authentication(recv_data[0], recv_data[1]):
conn.close() # 정보가 일치하지 않으면 연결종료
print('user_ID or ticket_server do not match!!')
sys.exit(1)
ticket_pickle_des3_base64enc = makeTicket(recv_data[0], addr[0], recv_data[1])
# 티켓을 만드는 과정
conn.send(ticket_pickle_des3_base64enc)
# 인코딩된 티켓을 클라이언트에게 전송
conn.close() # 클라이언트와 세션종료
except KeyboardInterrupt:
print('\b\b [-] '+str(sys.exc_info()[0]).split()[1][1:-2])
# [Ctrl + c] 등으로 인한 키보드 인터럽트 발생 시 출력
finally:
print('AS_Server end')
if __name__ == '__main__':
main()
[3] ticket_server.py
import socket
import pickle
import mylib
import base64
import sys
import time
# ticket_server = 10
def ticket_verify(ticket_tgs_pickle_des3_base64enc):
key = 'kerberos_v2'
iv = 'AS_Server'
des3 = mylib.MyDES3(key, iv)
ticket_tgs_pickle_des3 = base64.b64decode(ticket_tgs_pickle_des3_base64enc)
# base64 디코딩 후 저장
ticket_tgs_pickle = des3.dec(ticket_tgs_pickle_des3)
# 트리플 DES3로 복호화 후 저장
ticket_tgs_pickle = ticket_tgs_pickle.encode()
# byte타입으로 변환
ticket_tgs_pickle = b'\x80'+ticket_tgs_pickle[:-3] + b'\xb8\x0be.'
# 트리플 DES3 복호화시 패딩관련 추가설정
ticket_tgs = pickle.loads(ticket_tgs_pickle)
# 티켓서버에 대한 티켓을 메모리에 로드
return ticket_tgs
def authentication(IDc, addr, ticket_tgs):
timestamp = [str(x) for x in time.localtime()[:6]]
# 현재시간을 속성별로 리스트에 저장
timestamp = int(''.join(timestamp))
# timestamp를 int형으로 변환
ticket_tgs[3] = int(ticket_tgs[3])
# ticket_tgs에 저장된 timestamp도 int형으로 변환
if IDc == ticket_tgs[0] and addr == ticket_tgs[1] and '10' == ticket_tgs[2] and timestamp - ticket_tgs[4] < ticket_tgs[3]:
# ticket_tgs = [IDc, addr, IDtgs, Timestamp, Lifetime]
# 분기문의 내용은
# 현재 티켓서버에 접속한 ID와 티켓에 적힌 ID가 동일한지
# 현재 티켓서버에 접속한 IP주소와 티켓에 적힌 IP주소가 동일한지
# 현재 티켓서버의 번호와 티켓에 적혀있는 서버의 번호가 동일한지
# 티켓의 유효기간이 지나지 않았는지
return True
else:
return False
def makeTicket(IDc, addr, IDv):
key = 'kerberos_v2'
iv = 'service_server'
timestamp = [str(x) for x in time.localtime()[:6]]
# 현재시간을 속성별로 리스트로 저장
timestamp = ''.join(timestamp)
lifetime = 3000 # 30분 00초
ticket = [IDc, addr, IDv, timestamp, lifetime]
# 티켓 = [클라이언트ID, 클라이언트address, 티켓서버번호, 티켓발행시>간, 티켓유효기간]
ticket_pickle = pickle.dumps(ticket)
# 티켓리스트를 시리얼라이즈
ticket_pickle = ticket_pickle.decode('utf-8', 'ignore')
# des3.enc()에서 str타입으로 받으므로 타입변환
des3 = mylib.MyDES3(key, iv) # 암호화 하기 위해 객체 생성
ticket_pickle_des3 = des3.enc(ticket_pickle)
# 트리플DES로 티켓덤프를 암호화
ticket_pickle_des3_base64enc = base64.b64encode(ticket_pickle_des3)
# 암호화 티켓을 전송하기위해 base64로 인코딩
return ticket_pickle_des3_base64enc
def main():
host = ''
port = 20100
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((host, port))
print(' [*] Ticket Server')
print(' [*] Listening...')
try:
server.listen(1) # 접속이 있을때까지 포트열고 대기
conn, addr = server.accept()
# 클라이언트와 연결되어 통신가능한 (소켓, 주소)로 반환
print(' [+] Connect Client')
print(' [+] addr = {}, port {}'.format(addr[0], addr[1]))
recv_pickle = conn.recv(1024)
# [클라이언트ID, 접속할서버번호, 티켓]의 리스트덤프 받기
recv_data = pickle.loads(recv_pickle)
# 받은 리스트덤프를 디시리얼라이즈
IDc, IDv, ticket_tgs_pickle_des3_base64enc = recv_data[0], recv_data[1], recv_data[2]
# 클라이언트ID와 티켓덤프를 암호화후 인코딩한 정보를 저장
ticket_tgs = ticket_verify(ticket_tgs_pickle_des3_base64enc)
# 티켓을 검증하는 과정
if not authentication(IDc, addr[0], ticket_tgs):
conn.close() # 정보가 일치하지 않으면 연결종료
print('user_ID or address or service_number or ticket_valid_period do not match!!')
sys.exit(1)
ticket_pickle_des3_base64enc = makeTicket(IDc, addr[0], IDv)
# 티켓을 만드는 과정
conn.send(ticket_pickle_des3_base64enc)
# 인코딩된 티켓을 클라이언트에게 전송
conn.close() # 클라이언트와 세선종료
except KeyboardInterrupt:
print('\b\b [-] '+str(sys.exc_info()[0]).split()[1][1:-2])
# [Ctrl + c] 등으로 인한 키보드 인터럽트 발생 시 출력
finally:
print('ticket_server end')
# 서버종료 시 출력
if __name__ == '__main__':
main()
[4] service_server.py
import socket
import pickle
import mylib
import base64
import sys
import time
from datetime import datetime
def ticket_verify(ticket_pickle_des3_base64enc):
key = 'kerberos_v2'
iv = 'service_server'
des3 = mylib.MyDES3(key, iv)
ticket_pickle_des3 = base64.b64decode(ticket_pickle_des3_base64enc)
# base64 디코딩 후 저장
ticket_pickle = des3.dec(ticket_pickle_des3)
# 트리플 DES3로 복호화 후 저장
ticket_pickle = ticket_pickle.encode()
# byte타입으로 변환
ticket_pickle = b'\x80'+ticket_pickle[:-3] + b'\xb8\x0be.'
# 트리플 DES3 복호화 시 패딩관련 추가설정
ticket = pickle.loads(ticket_pickle)
# 티켓서버에 대한 티켓을 메모리에 로드
return ticket
def authentication(IDc, addr, ticket):
timestamp = [str(x) for x in time.localtime()[:6]]
# 현재시간을 속성별로 리스트에 저장
timestamp = int(''.join(timestamp))
# timestamp를 int형으로 변환
ticket[3] = int(ticket[3])
# ticket에 저장된 timestamp도 int형으로 변환
if IDc == ticket[0] and addr == ticket[1] and '100' == ticket[2] and timestamp - ticket[4] < ticket[3]:
# ticket = [IDc, addr, IDv, Timestamp, Lifetime]
# 분기문의 내용은
# 현재 서비스서버에 접속한 ID와 티켓에 적힌 ID가 동일한지
# 현재 서비스서버에 접속한 IP주소와 티켓에 적힌 IP주소가 동일한지
# 현재 서비스서버의 번호와 티켓에 적혀있는 서버의 번호가 동일한지
# 티켓의 유효기간이 지나지 않았는지
return True
else:
return False
def main():
host = ''
port = 30100
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((host, port))
try:
print(' [*] Service Server')
print(' [*] Listening...')
server.listen(1) # 접속이 있을때까지 포트열고 대기
conn, addr = server.accept()
# 클라이언트와 연결되어 통신가능한 (소켓, 주소)로 반환
print(' [+] Connect Client')
print(' [+] addr = {}, port {}'.format(addr[0], addr[1]))
recv_pickle = conn.recv(1024)
# [클라이언트ID, 티켓]의 리스트덤프 받기
recv_data = pickle.loads(recv_pickle)
# 받은 리스트덤프를 디시리얼라이즈
IDc, ticket_pickle_des3_base64enc = recv_data[0], recv_data[1]
# 클라이언트ID와 티켓덤프를 암호화 후 인코딩한 정보를 저장
ticket = ticket_verify(ticket_pickle_des3_base64enc)
# 티켓을 검증하는 과정
if not authentication(IDc, addr[0], ticket):
conn.close() # 정보가 일치하지 않으면 연결종료
print('user_ID or address or service_number or ticket_valid_period do not match!!')
sys.exit(1)
send_data = [datetime.now().year, datetime.now().month, datetime.now().day]
# 시간정보를 리스트로 저장
send_data_pickle = pickle.dumps(send_data)
conn.send(send_data_pickle)
conn.close()
except KeyboardInterrupt:
print('\b\b [-] '+str(sys.exc_info()[0]).split()[1][1:-2])
# [Ctrl + c] 등으로 인한 키보드 인터럽트 발생 시 출력
finally:
print('time_server end')
# 서버종료 시 출력
if __name__ == '__main__':
main()
소스에 대한 설명을 보태면
접속할 서버는 시간에 대한 서비스를 하는 서버입니다.
하하..
3-4) 커버로스 버전2[사용방법]
[1] [python3 AS_server.py] 명령어로 AS서버 동작
[2] [python3 ticket_server.py] 명령어로 ticket서버 동작
[3] [python3 service_server.py] 명령어로 service서버 동작
[4] [python3 client.py] 명령어로 클라이언트 동작
[5] 클라이언트에서 총 3개를 입력을 받음
▶ user_ID를 (user100)으로 입력
▶ ticket_server번호를 (10)으로 입력
▶ service_server번호를 (100)으로 입력
- AS_server에서 검증 후 user_ID나 ticket_server번호가 일치하지 않을 경우 티켓발급 불가
- user_ID나 ticket_server번호는 일치하고 service_server번호가 일치하지 않을 경우,
AS_server에서 ticket_server의 티켓은 발급되지만 service_server에서 검증 후 서비스거부
- 접속한 클라이언트 IP주소가 티켓의 IP주소가 다른경우 즉시 서버와 연결종료
- 타임스탬프 확인 및 티켓 발행시간으로부터 30분이상 경과한 경우 티켓 무효화
와 같이 검증이 가능합니다.
3-5) 커버로스 버전2[시연]
그렇다면 커버로스 버전2의 문제점은
무엇인지 알아볼까요?
티켓서버 티켓이나 접속할 서버의 티켓의
유효기간의 문제가 있다.
이는 재전송공격에 대한 위험을
가지고 있다.
서버를 사용자에게 인증하는 절차도 보이지 않는다.
티켓을 받은사람이랑 티켓을 제시하는 사람이 동일할까?
이를 해결하기 위해 상위버전의 커버로스에서는
AS와 TGS가 공유하는 비밀값(세션키)를 활용한다고 한다.
끄적끄적하다보니 생각보다 길어졌지만
스크롤바가 여기에 있는 분들은
커버로스를 습득하셨습니다!!
'Theory > Cipher protocol' 카테고리의 다른 글
Ciphertext Only Attack(암호문 단독 공격) 구현 (0) | 2019.05.31 |
---|---|
Known Plaintext Attack(기지 평문 공격) 구현 (0) | 2019.05.31 |
RSA를 이용한 전자서명(디지털서명) 구현 (0) | 2019.05.31 |
RSA공개키를 이용한 암호화 구현 (0) | 2019.05.30 |
3DES를 이용한 암호화 (0) | 2019.04.11 |
댓글