ssh user@server 한 줄 뒤에서 벌어지는 일 — SSH 동작 원리

ssh 명령어 한 줄로 원격 서버에 접속할 때 클라이언트와 서버 사이에서 무슨 일이 벌어지는지, 핸드셰이크부터 키 교환·암호화·인증까지 단계별로 따라간다.

SSHNetworkSecurityDevOpsCryptography
--
ssh user@server 한 줄 뒤에서 벌어지는 일 — SSH 동작 원리

개발자가 ssh user@server를 친다.

터미널에 비밀번호나 키 한 번 입력하면 곧바로 원격 서버 쉘이 열린다. 그런데 그 사이 0.x초 동안 클라이언트와 서버가 실제로 무엇을 주고받는지 설명하라고 하면 막상 말문이 막힌다.

SSH는 신뢰할 수 없는 네트워크 위에서 안전한 통로를 만드는 프로토콜이다. 1995년 Tatu Ylönen이 평문으로 모든 걸 흘려보내던 Telnet을 대체하려고 만들었고, 지금은 서버 관리·배포·터널링의 기반이 됐다. 이 글에서는 SSH가 풀어야 하는 문제부터 시작해, 연결이 맺어지는 전체 과정을 단계별로 따라간다. 다 읽고 나면 known_hosts 경고가 왜 뜨는지, 개인키는 왜 절대 네트워크로 안 나가는지 자연스럽게 이해된다.

SSH가 풀어야 하는 세 가지 문제

암호화 한 줄로 끝나는 얘기가 아니다. SSH는 다음 세 가지를 동시에 보장해야 한다.

문제설명막아내는 공격
기밀성(Confidentiality)주고받는 데이터를 제3자가 읽을 수 없게도청(Sniffing)
무결성(Integrity)데이터가 중간에 변조되지 않았음을 보장패킷 변조(Tampering)
인증(Authentication)상대가 진짜 그 서버/사용자인지 확인중간자 공격(MITM), 위장

여기서 인증은 다시 두 갈래로 나뉜다.

서버 인증(내가 접속한 곳이 진짜 그 서버가 맞나)과 사용자 인증(접속하는 너는 진짜 권한이 있는 사람이 맞나)이다.

이 둘은 SSH 안에서 완전히 다른 단계에서 처리된다. 이걸 구분해서 보는 게 SSH 이해의 핵심이다.

전체 흐름 한눈에 보기

먼저 큰 그림부터 잡고 가자. ssh 명령 한 줄은 내부적으로 대략 이런 순서로 진행된다.

ssh flow 1

크게 보면 앞부분(1~6)은 안전한 터널을 까는 단계고, 뒷부분(7~8)은 그 터널 안에서 신원을 확인하고 일을 시작하는 단계다. 이제 하나씩 뜯어본다.

1. TCP 연결과 버전 교환

모든 건 평범한 TCP 연결로 시작한다. 클라이언트가 서버의 22번 포트로 3-way handshake를 맺는다. 이 시점에는 아직 암호화가 없다.

연결되면 양쪽이 평문으로 버전 문자열을 주고받는다.

서버 → 클라이언트 : SSH-2.0-OpenSSH_9.6
클라이언트 → 서버 : SSH-2.0-OpenSSH_9.6

이 문자열은 SSH 프로토콜 버전(2.0)과 구현체·버전을 알려준다. 둘이 호환되지 않으면 여기서 연결을 끊는다. 참고로 요즘은 거의 다 SSH-2만 쓴다. SSH-1은 단일 모놀리식 구조에 약한 CRC-32 무결성 검사를 쓰는 등 설계 결함이 있어 사실상 폐기됐다.

이 버전 문자열은 평문으로 오갈 수밖에 없다. 아직 암호화 키가 존재하지 않기 때문이다. 그래서 더더욱, 이 단계에서 교환된 값은 나중에 무결성 검증용 해시에 함께 묶인다.

2. 알고리즘 협상

다음은 "어떤 암호 방식으로 대화할지" 정하는 단계다. 양쪽이 SSH_MSG_KEXINIT 메시지를 보내며 자기가 지원하는 알고리즘 목록을 선호 순서대로 나열한다.

협상 대상은 크게 네 종류다.

  • 키 교환(KEX): curve25519-sha256, ecdh-sha2-nistp256
  • 대칭 암호화: aes256-ctr, chacha20-poly1305@openssh.com
  • MAC(무결성): hmac-sha2-256
  • 압축: none, zlib

양쪽 목록을 비교해 공통으로 지원하는 것 중 클라이언트 선호도가 가장 높은 걸 고른다. 겹치는 게 하나도 없으면 협상 실패로 연결이 끊긴다. 이 "목록 + 선호 순서" 구조 덕분에 SSH는 새 알고리즘이 나와도 유연하게 갈아탈 수 있다.

3. 키 교환(KEX) — 핵심 중의 핵심

여기가 SSH에서 가장 영리한 부분이다. 목표는 단 하나, 공유 비밀(shared secret)을 네트워크에 단 한 번도 흘리지 않고 양쪽이 똑같은 값에 도달하는 것이다.

이걸 가능하게 하는 게 Diffie-Hellman 키 교환, 그리고 그 타원곡선 변형인 ECDH(예: Curve25519)다. 직관적으로 보면 이렇다.

ssh dh keyexchange 1

da, db는 네트워크에 절대 나가지 않는다. 오직 공개값 Qa, Qb만 오간다.
그런데 타원곡선의 수학적 성질 때문에 양쪽이 계산한 결과는 정확히 같은 값 K가 된다.
도청자는 Qa, Qb를 다 봐도 K를 역산할 수 없다.

여기서 중요한 디테일 하나. 이 da, db는 이 연결 한 번만을 위해 만들어졌다 버려지는 임시(ephemeral) 키다. 그래서 순방향 비밀성(Forward Secrecy)이 보장된다. 공격자가 5년 뒤 서버의 영구 키를 통째로 훔쳐도, 오늘 나눈 대화는 복호화할 수 없다. 그날의 임시 키는 세션이 끝나는 순간 영원히 사라졌기 때문이다.

한 가지 짚어둘 것: 이렇게 만들어진 K는 그 자체가 암호화 키가 아니다. 이후 다른 핸드셰이크 데이터와 함께 KDF(키 유도 함수)에 들어가, 여러 개의 대칭 키를 뽑아내는 "원재료"다.

4. 서버 인증 — known_hosts의 정체

키 교환만 끝난 상태에서는 함정이 하나 있다. 도청은 막았지만, 내가 키 교환한 상대가 진짜 그 서버인지는 아직 모른다. 중간에 누가 끼어들어 서버인 척했을 수도 있다(능동적 MITM).

그래서 서버는 자기 신원을 증명해야 한다. 서버에는 프로세스가 뜰 때 만들어진 호스트 키 쌍(공개키 + 개인키)이 있다. 서버는 지금까지의 핸드셰이크 값들을 묶어 만든 exchange hash에 자기 개인키로 서명해서 보낸다. 클라이언트는 서버의 공개키로 그 서명을 검증한다. 검증이 통과하면 "이 상대는 그 호스트 개인키를 가진 게 확실하다"가 증명된다.

문제는, 그 공개키 자체를 믿을 수 있느냐다. 클라이언트는 받은 호스트 공개키를 로컬의 ~/.ssh/known_hosts와 대조한다.

# 처음 접속할 때 뜨는 바로 그 메시지
The authenticity of host '10.10.10.10 (10.10.10.10)' can't be established.
ED25519 key fingerprint is SHA256:abc123...
Are you sure you want to continue connecting (yes/no)?

이 경고는 "이 서버의 키가 내 known_hosts에 없다"는 뜻이다. yes를 누르면 키가 known_hosts에 저장되고, 다음부터는 조용히 통과한다. 반대로 저장된 키와 다른 키가 오면 SSH는 강하게 경고하며 연결을 막는다. MITM 가능성을 의심하는 것이다.

❌ 위험: 경고가 떠도 습관적으로 yes
   → 진짜 MITM 공격을 그냥 통과시킬 수 있다

✅ 안전: fingerprint를 서버 관리자가 공유한 값과 대조
   → 처음 접속 시 딱 한 번만 확인하면 이후는 자동 검증

규모가 큰 환경에서는 호스트마다 키를 신뢰하는 대신, SSH 인증서(CA) 방식으로 CA 하나만 known_hosts에 넣고 그 CA가 서명한 모든 호스트를 통째로 신뢰하게 만들기도 한다.

5. 세션 키 생성과 암호화 전환

서버 인증까지 끝나면, 키 교환에서 얻은 공유 비밀 K를 KDF에 넣어 실제 세션 키들을 유도한다. 방향별·용도별로 여러 개가 나온다.

  • 클라이언트→서버 암호화 키 / 서버→클라이언트 암호화 키
  • 각 방향의 MAC 키 (무결성 검증용)

이 키들이 준비되면 양쪽은 SSH_MSG_NEWKEYS 메시지를 교환한다. 이 메시지 이후의 모든 통신은 새로 만든 대칭 키로 암호화·보호된다. 실제 암호화에는 협상해둔 AES-256이나 ChaCha20-Poly1305 같은 대칭 암호가 쓰인다.

여기서 가장 흔한 오해 하나를 정리하고 넘어가자.

구분비대칭 암호(공개키/개인키)대칭 암호(공유 키)
어디 쓰나키 교환 보조, 서버 인증, 사용자 공개키 인증실제 세션 데이터 암호화
속도느림빠름
대표 예RSA, Ed25519, ECDHAES-256, ChaCha20

"SSH 키 쌍(공개키/개인키)으로 세션을 암호화한다"는 건 틀린 생각이다. 키 쌍은 인증에만 쓰이고, 정작 주고받는 데이터는 키 교환으로 만든 대칭 키가 암호화한다. 대칭 암호가 훨씬 빠르기 때문이다.

무결성은 MAC(Message Authentication Code)가 책임진다. 패킷마다 MAC이라는 "봉랍 도장"이 찍히는데, 전송 중 누가 비트 하나라도 뒤집으면 MAC 검증이 실패하고 SSH는 즉시 연결을 끊는다.

6. 사용자 인증

이제야 비로소 "너 누구냐"를 묻는다. 중요한 건 이 단계가 이미 암호화된 터널 안에서 일어난다는 점이다. 그래서 비밀번호 인증조차 도청으로부터 보호된다.

클라이언트와 서버는 먼저 허용된 인증 방법 목록을 합의한다(공개키, 비밀번호, keyboard-interactive 등). 클라이언트는 이 중 하나씩 시도한다. 대표적인 두 가지를 보자.

비밀번호 인증

말 그대로 비밀번호를 입력한다. 이미 암호화된 터널 안이라 평문 노출 위험은 없지만, 추측·무차별 대입 공격에 약하다.

공개키 인증 (권장)

비밀번호보다 안전하고, 자동화에 적합하다. 흐름은 "챌린지-서명-검증"이다.

ssh pubkey auth 1

핵심은 개인키도 비밀번호도 네트워크로 절대 나가지 않는다는 점이다.

클라이언트는 개인키로 챌린지에 서명만 하고, 서버는 등록된 공개키로 그 서명이 맞는지 검증할 뿐이다.

개인키를 가졌다는 사실을, 개인키 자체를 노출하지 않고 증명하는 셈이다.

# 키 쌍 생성 (ed25519 권장)
ssh-keygen -t ed25519 -C "hyunmin@laptop"

# 공개키를 서버에 등록
ssh-copy-id user@server
# 내부적으로 ~/.ssh/authorized_keys 에 공개키가 추가된다

개인키가 유출되면 누군가 나인 척 서버에 들어올 수 있으니, 반드시 passphrase로 보호하고 유출 시 서버의 공개키를 즉시 삭제 후 새 쌍을 발급해야 한다.

실전 팁

오랫동안 SSH를 쓰며 정리한 것들이다.

  1. 비밀번호 인증은 끄고 공개키만 쓴다. 서버의 /etc/ssh/sshd_config에서:
PasswordAuthentication no
PubkeyAuthentication yes

  1. 키는 ed25519를 쓴다. RSA보다 짧고 빠르며 안전하다. 새로 만든다면 RSA 2048을 고집할 이유가 없다.
  2. known_hosts 경고를 습관적으로 무시하지 않는다. fingerprint가 바뀌었다면 서버 재설치 같은 정상적 이유일 수도, MITM일 수도 있다. 이유를 확인하기 전엔 yes를 누르지 않는다.
  3. ~/.ssh/config로 접속을 단축한다. 호스트별 설정을 모아두면 ssh myserver 한 줄로 끝난다.
Host myserver
    HostName XXX.XX.XX.XXX
    User username
    Port 22
    IdentityFile ~/.ssh/id_ed25519

  1. passphrase + ssh-agent 조합을 쓴다. 개인키에 passphrase를 걸되, ssh-agent에 한 번만 등록해두면 매번 입력하는 번거로움 없이 보안과 편의를 둘 다 챙긴다.
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

마치며

ssh user@server 한 줄 뒤에는 이런 일들이 순서대로 일어난다.

  • TCP 연결과 버전 교환으로 판을 깔고
  • 알고리즘을 협상한 뒤
  • Diffie-Hellman으로 공유 비밀을 흘리지 않고 양쪽이 도출하고
  • 호스트 키로 서버가 진짜인지 검증하고(known_hosts)
  • 거기서 유도한 대칭 키로 터널을 암호화한 다음
  • 그 안에서 비로소 사용자를 인증한다

기억할 핵심은 두 가지다. 첫째, 데이터를 실제로 암호화하는 건 빠른 대칭 키이고, 공개키/개인키 쌍은 인증에만 쓰인다. 둘째, 비밀번호든 개인키든 그 자체는 절대 네트워크로 나가지 않는다. SSH는 비밀을 직접 보내지 않고도 그 비밀을 안다는 사실만 증명하는 방식으로 동작한다.

다음에 known_hosts 경고가 뜨거나 비밀번호 없이 서버에 로그인될 때, 그 짧은 순간에 어떤 핸드셰이크가 지나갔는지 한 번쯤 떠올려봐도 좋다.

더 읽을거리

  • DigitalOcean — Understanding the SSH Encryption and Connection Process
  • Teleport — SSH Handshake Explained
  • Cisco — Understand Secure Shell Packet Exchange
  • RFC 4251~4254 (SSH 프로토콜 아키텍처 / 전송 / 인증 / 연결)

관련 글

댓글

0/2000
Newsletter

이 글이 도움이 되셨나요?

새로운 글이 발행되면 이메일로 알려드립니다.

뉴스레터 구독하기