- Published on
SSL 2.0과 SSL 3.0 직접 구현한 개인적인 정리
- Authors
- Name
- 김민석
Introduction
상황
SSL(Secure Sockets Layer)은 인터넷 통신을 보장하기 위해 설계된 암호화 프로토콜로, HTTPS
와 같은 보안 프로토콜
의 기반이 된다.
하지만 SSL은 여러 버전을 거치며
보안성과 성능 측면에서 문제가 발생했다. SSL 2.0
, SSL 3.0
은 보안 기준에서 취약하며, 이후 TLS로 대체되었다.
이번 글에서는 SSL의 각 버전(2.0, 3.0)
을 직접 구현하고, 이를 통해 발생한 보안 및 성능 문제를 분석해 보겠다.
문제
SSL 2.0
SSL 2.0은 처음 도입
된 암호화 프로토콜로, 초창기 인터넷 보안을 위한 표준
으로 사용되었다.
취약한 암호화:
충분히 강력한 암호화 알고리즘을 사용하지 않아 데이터 탈취 가능성이 높다.핸드셰이크의 취약성:
핸드셰이크 과정에서 다운그레이드 공격이 가능하며, 암호화 키가 노출될 수 있다.메시지 인증 부족:
메시지 변조에 취약하며, 보안성을 보장하지 못한다.
SSL 3.0
SSL 3.0은 SSL 2.0의 문제를 해결하며 보안성과 호환성을 개선했다. 하지만 문제점도 있다.
POODLE 공격:
SSL 3.0의 CBC 암호화 모드가 POODLE 공격에 취약하다.암호화 알고리즘 제한:
보안 기준에 부합하지 않는 RC4와 같은 구식 암호화 알고리즘을 사용한다.세션 재사용 미비:
연결 유지가 어렵고, 성능이 낮다.
시퀀스 다이어그램
- SSL 2.0 시퀀스 다이어그램

SSL 2.0의 핸드셰이크는 단순하지만 보안이 취약하다. 특히, Master Secret
전송 과정이 암호화되지 않아 탈취 위험이 있다.
- SSL 3.0 시퀀스 다이어그램

SSL 3.0에서는 ClientKeyExchange
단계에서 키 교환이 이루어지며, 데이터 암호화를 위한 기반이 마련 된다. 하지만 CBC
암호화 모드와 POODLE
공격에 취약하다.
구현
SSL 2.0의 간단한 흐름 시뮬레이션
public class SSL2Server {
public static void main(String[] args) throws Exception {
// 서버 소켓 생성
ServerSocket serverSocket = new ServerSocket(8443);
System.out.println("SSL 2.0 시뮬레이션 서버가 포트 8443에서 시작되었습니다.");
while (true) {
try (Socket socket = serverSocket.accept()) {
// 클라이언트와 연결
System.out.println("클라이언트 연결 성공!");
// 데이터 송수신 스트림 생성
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
// 핸드셰이크 과정 시뮬레이션
writer.println("ServerHello: 지원 가능한 암호화 알고리즘: [None]");
String clientMessage = reader.readLine();
System.out.println("클라이언트로부터 수신한 메시지: " + clientMessage);
if ("ClientHello: 선택된 암호화 없음".equals(clientMessage)) {
writer.println("Connection Established: 암호화 없음");
} else {
writer.println("Connection Failed");
}
// 클라이언트와 데이터 송수신
String clientData = reader.readLine();
System.out.println("클라이언트로부터 데이터 수신: " + clientData);
writer.println("서버 응답: " + clientData.toUpperCase());
}
}
}
}
클라이언트 구현 (SSL 2.0 시뮬레이션)
public class SSL2Client {
public static void main(String[] args) throws Exception {
try (Socket socket = new Socket("localhost", 8443)) {
System.out.println("SSL 2.0 시뮬레이션 클라이언트가 서버에 연결되었습니다.");
// 데이터 송수신 스트림 생성
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
// 핸드셰이크 과정 시뮬레이션
String serverMessage = reader.readLine();
System.out.println("서버로부터 메시지 수신: " + serverMessage);
writer.println("ClientHello: 선택된 암호화 없음");
String serverResponse = reader.readLine();
System.out.println("서버 응답: " + serverResponse);
if ("Connection Established: 암호화 없음".equals(serverResponse)) {
// 데이터 전송
writer.println("Hello, Server!");
String response = reader.readLine();
System.out.println("서버로부터 수신한 응답: " + response);
} else {
System.out.println("연결 실패");
}
}
}
}
실행 결과
- 서버 출력
SSL 2.0 시뮬레이션 서버가 포트 8443에서 시작되었습니다.
클라이언트 연결 성공!
클라이언트로부터 수신한 메시지: ClientHello: 선택된 암호화 없음
클라이언트로부터 데이터 수신: Hello, Server!
- 클라이언트 출력
SSL 2.0 시뮬레이션 클라이언트가 서버에 연결되었습니다.
서버로부터 메시지 수신: ServerHello: 지원 가능한 암호화 알고리즘: [None]
서버 응답: Connection Established: 암호화 없음
서버로부터 수신한 응답: HELLO, SERVER!
설명
- 핸드셰이크 시뮬레이션
- 서버는
ServerHello
메시지를 전송하며 암호화 방식을 제안한다. - 클라이언트는
ClientHello
메시지로 선택된 암호화 방식을 서버에 알린다. SSL 2.0
의 단순성을 반영하여, 암호화 없는 연결을 시뮬레이션 한다.
- 서버는
- 데이터 송수신
- 연결이 성공하면 클라이언트가 데이터를 전송하고, 서버는 데이터를 처리하여 응답을 반환한다.
- 암호화가 없는 상태로 데이터가 전송되므로, 보안 취약점을 보여준다.
SSL 3.0 서버 구현
public class SSL3Server {
public static void main(String[] args) throws Exception {
// 키 저장소 로드
KeyStore keyStore = KeyStore.getInstance("JKS");
try (InputStream keyStoreStream = new FileInputStream("server.keystore")) {
keyStore.load(keyStoreStream, "password".toCharArray());
}
// KeyManager 초기화
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keyStore, "password".toCharArray());
// SSLContext 설정 (SSL 3.0)
SSLContext sslContext = SSLContext.getInstance("SSLv3");
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
// SSL 서버 소켓 생성
SSLServerSocketFactory serverSocketFactory = sslContext.getServerSocketFactory();
try (SSLServerSocket serverSocket = (SSLServerSocket) serverSocketFactory.createServerSocket(8443)) {
System.out.println("SSL 3.0 서버가 포트 8443에서 시작되었습니다.");
while (true) {
try (Socket socket = serverSocket.accept()) {
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
String message = reader.readLine();
System.out.println("클라이언트로부터 수신한 메시지: " + message);
writer.println("안녕하세요, 클라이언트!");
}
}
}
}
}
SSL 3.0 클라이언트 코드
public class SSL3Client {
public static void main(String[] args) throws Exception {
// 신뢰 저장소(truststore) 로드
KeyStore trustStore = KeyStore.getInstance("JKS");
try (InputStream trustStoreStream = new FileInputStream("client.truststore")) {
trustStore.load(trustStoreStream, "password".toCharArray());
}
// TrustManager 초기화
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
trustManagerFactory.init(trustStore);
// SSLContext 설정 (SSL 3.0)
SSLContext sslContext = SSLContext.getInstance("SSLv3");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
// SSL 소켓 생성
SSLSocketFactory socketFactory = sslContext.getSocketFactory();
try (SSLSocket socket = (SSLSocket) socketFactory.createSocket("localhost", 8443)) {
socket.startHandshake(); // SSL 핸드셰이크 시작
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 서버로 메시지 전송
writer.println("안녕하세요, 서버!");
// 서버로부터 응답 수신
String response = reader.readLine();
System.out.println("서버로부터 수신한 응답: " + response);
}
}
}
실행 결과
- 서버 출력
서버는 클라이언트의 메시지를 수신
하고 응답
을 전송한다.
SSL 3.0 서버가 포트 8443에서 시작되었습니다.
클라이언트로부터 수신한 메시지: 안녕하세요, 서버!
- 클라이언트 출력
클라이언트는 서버와 SSL 3.0 핸드셰이크
를 완료한 뒤, 메시지를 주고 받는다.
서버로부터 수신한 응답: 안녕하세요, 클라이언트!
설명
SSL 핸드셰이크
- 클라이언트와 서버는
핸드셰이크
를 통해 암호화된 안전한 연결을 설정한다. - 클라이언트가
ClientHello
를 보내면, 서버는 인증서를 포함한ServerHello
를 반환하여 인증 절차를 완료한다.
- 클라이언트와 서버는
데이터 송수신
- 클라이언트가
"안녕하세요, 서버!"
메시지를 서버에 전송한다. - 서버는 메시지를 수신하고, 응답 메시지
"안녕하세요, 클라이언트!"
를 반환한다.
- 클라이언트가
SSL 3.0 사용
SSLContext.getInstance("SSLv3")
를 사용하여 SSL 3.0 연결을 설정한다.SSL 3.0
은 보안 취약점(PADDING 등)으로 인해 실제 환경에서는 사용이 권장되지 않는다.
해결
SSL 2.0은 심각한 보안 취약점
으로 인해 요즘은 사용이 권장되지 않는다. 다운그레이드
공격과 암호화 키 노출 위험이 존재하며, 충분히 강력한 암호화 알고리즘을 제공하지 못한다.
따라서 SSL 2.0을 비활성화
하고 서버 설정에서 지원되는 프로토콜 목록
에서 완전히 제거하였다.
SSL 3.0
은 SSL 2.0의 문제를 해결하기 위해 도입되었지만, POODLE
공격에 취약하다는 한계를 가지고 있다. 이를 방지하기 위해 CBC 모드
대신 AES-GCM
과 같은 강력한 암호화 모드를 사용하는 방향으로 개선하였다.
더불어, 암호화 알고리즘(AES, SHA-256)
을 추가로 지원하도록 서버를 구성하고, 서버와 클라이언트 간의 인증서 검증 과정을 강화하였다.
결과적으로 SSL 2.0
을 제거하고 SSL 3.0
의 문제점을 해결함으로써, 보안 취약점을 보완하고 암호화된 안전한 데이터 전송이 가능해졌다. 이번 작업을 통해 구형 프로토콜의 한계
를 인식하고, 최신 보안 표준에 맞는 환경을 설계하는 데 중요한 경험을 얻을 수 있었다.
결말
SSL 2.0과 3.0
은 각각 초기 인터넷 보안을 제공했지만, 요즘 환경에서는 심각한 보안 문제로 인해 사용이 권장되지 않는다.
이번 구현을 통해 SSL 2.0과 3.0의 작동 방식
을 이해
하고, 프로토콜(TLS)로의 전환 필요성
을 실감했다. SSL은 보안 프로토콜의 기초를 제공했지만, TLS의 발전이 필요
했던 이유를 명확히 이해할 수 있었다. 이러한 경험으로 안전한 네트워크 통신을 설계하고 구현하는 데 중요한 공부가 되었다.