종단간 암호화란 정보의 전송부터 도달까지 암호화를 유지한 채로 전송하는 방식을 말한다. 구현 난이도는 간단하지만, 구현을 위해서는 적용 환경 및 목적에 따라 약간의 응용이 필요하다. 일반적으로 RSA 키 교환과 AES 대칭키 암호를 사용한다.
국내에 종단간 암호화 구현 관련 글이 정말 없다. 구현이 천차만별이기도 하고 무엇보다 방법에있어 정답이 없다. 국내에서도 종단간 암호화 관련 블로그 글이 생겨나기를 기원한다.
이 포스트에서는 종단간 암호화 채팅을 구현하면서 겪은 문제점을 중심으로 전개해 나가려 한다. 다음 글에서는 종단간 암호화의 구체적인 구현 예시와 실 서비스를 위해 고려해야 하는 사항을 중심으로 포스팅할 예정이다.
우선 RSA 암호는 성능이 최악이다. 공개키와 비밀키를 생성하는데에도 서버에서 평균 1초가 걸릴 뿐 아니라, 모바일 환경의 경우 정상적으로 지원하는 라이브러리가 없어 애를 먹었다(애초에 성능이 개판이라). 보다 안전한 종단간 암호화를 위해서는 서버가 아닌 사용자 환경에서 키 쌍을 생성해야 한다. 그러나 현실적인 이유로 대부분 서버에서 키 쌍을 생성하고 있다. 알기로는 카카오톡 비밀채팅, 텔레그램등 상용 서비스 또한 서버에서 키를 생성하는 걸로 알고있다.
따라서 RSA키 생성은 서버에서 해야한다.
이론상 서버에서 키 쌍을 생성하게 되면 서비스 제공자가 사용자의 공개키, 비밀키를 탈취할 수 있다. 이는 사용자가 RSA로 정보를 암호화 한들 아무 의미가 없어진다. 종단간 암호화를 구현하면서 세상에 완전히 믿을만한 채팅 서비스는 없다는 사실을 깨닫는 계기가 되었다.
웹 환경은 앱 환경과 다르게 클라이언트의 데이터를 영구적으로 저장하는데 한계가 있다. 즉, 로컬에 데이터를 보관해야 하는 경우 지워질 수 있다는 전제 하에 보관해야 한다.
그러나 RSA의 비밀키는 영구적으로 저장되어야 한다. 사용자 로그아웃에도 대비해야 하고 비밀키 유출에 대해서도 대비해야 한다. 종단간 암호화를 구현할때 다음과 같은 방법을 고안했다.
(1) 회원가입 로그인마다 RSA키 쌍을 새로 생성하고 기존 비밀키를 덮어씌운다.
(2) 다른 환경에서 서로 같은 계정으로 로그인 할 경우 기존 환경은 로그아웃 한다(비밀키 중복)
다만, 이같은 방식은 사용자가 많아질 수록 비효율적이다. 서버에서 RSA 키 쌍을 생성할때 1초 이상 걸린다. 이는 싱글스레드 언어인 자바스크립트에서 특히 비효율적이다. 게다가 CPU자원도 상당부분 소요하기 때문에 사용자가 증가할 수록 로그인 요청도 증가하며 자연스레 서버는 느려지게 된다.
또한 보안에도 취약하다. 기본적으로 키 쌍은 보호되지 않는다. 즉 로그인 환경이 중간자 공격에 취약하면 사용자는 종단간 암호화 시작 전에 키 쌍을 털려버리는 불상사가 발생할 수 있다.
상대방의 공개키로 Secret Key를 암호화하고 생성된 암호문을 상대방에게 전달하며 상대방은 비밀키로 이를 해독한다. 결과 나와 상대방은 서로 같은 Secret Key를 소유하게 된다.
AES의 Key는 AES 종류에 따라 달라진다.
각각 16바이트 24바이트 32바이트다. 랜덤 문자열 생성 함수로 각 바이트를 생성해주기만 하면 된다.
다만, 주체를 설정해주어야 한다. 누가 키를 생성할건지 결정해야 한다. 채팅 그룹을 만들때 관리자를 설정해서 키를 생성하고 이를 새 사용자가 요청할때 공유하는 방식을 택했다.
사실 이 부분부터 귀찮아서 개발하지 않았다. 앞선 부분은 실제로 구현되었으며 아래 하단 깃허브 링크에서 확인할 수 있다.
사용자가 키를 생성한 만큼 키는 변조될 위협이 있다. 키가 변조되는 순간 같은 채팅방에 있더라도 메시지를 해독하지 못하는 상황이 벌어질 수 있다. 따라서 서버가 이들의 키를 검증해주어야 한다.
그럼 서명키를 사용하면 되지 않냐고 반문할 수 있다. 결론적으로 말하자면 서명키를 사용해 메시지를 검증하면 인증된 사용자가 키를 바꿔서 서명한다면 하든 말든 효과가 없다. 최초로 생성된 키에 대해서만 유효하다고 했으므로 최초 키에 대한 유효성을 검증해야지 그 키를 보낸 사용자를 유효하다고 해서 키에 대한 유효성이 입증되지는 않는다.
시크릿 키를 검증하는 단 하나의 방법은 생성과 동시에 키의 해시값을 서버로 전송하는 것이다. 서버로 해시값을 전송하게 되면 키를 받은 사용자가 서버의 해시값과 대조해볼 수 있고 따라서 키의 유효성을 확인할 수 있게된다.
이 모든 전제조건은 모든 사용자가 온라인이라는 가정 하에 있다. 만약 모든 사용자가 오프라인 상태에 있다면 키를 주고받지 못하며 따라서 실시간으로 통신이 불가능하게 된다.
따라서 서버에서는 암호화된 키 값을 보관하는 데이터베이스가 필요하다. 키를 전송하기 위해서는 우선 서버에 상대방의 공개키로 암호화된 키를 올려두고 이를 상대방에게 확인하라고 요청한다. 이때 상대방이 오프라인 상태라면 확인할 수 없다. 이후에 상대방이 온라인 상태로 전환되면 내 이름으로 된 키 데이터를 검색하고 키가 있으면 이를 복호화하는 과정을 거친다.
따라서 오프라인 상황에서도 키를 요청하고 공유할 수 있다.