SSH: 암호화된 원격 접속과 공개 키 인증
보안 셸(SSH, Secure Shell)은 암호화되지 않은 네트워크에서도 원격 로그인, 명령 실행, 파일 전송, 포트 전달을 안전하게 수행하는 프로토콜이다. 서버 호스트 키로 접속 대상을 확인하고, 키 교환으로 세션 키를 만든 뒤 대칭 키 암호화와 메시지 인증으로 통신을 보호한다. 사용자는 비밀번호 또는 공개 키 전자서명으로 인증한다.
By 송민성
1. 개념
보안 셸(SSH, Secure Shell)은 클라이언트와 서버 사이에 암호화된 통신 채널을 만드는 응용 계층 프로토콜이다. 일반적으로 전송 제어 프로토콜(TCP) 위에서 동작하며 기본 포트는 22다.
SSH는 다음 기능을 제공한다.
- 기밀성(Confidentiality): 패킷 내용을 암호화해 도청을 막는다.
- 무결성(Integrity): 통신 내용의 변조 여부를 검증한다.
- 서버 인증(Server Authentication): 접속한 서버가 기대한 서버인지 호스트 키로 확인한다.
- 사용자 인증(User Authentication): 비밀번호나 공개 키로 사용자를 확인한다.
- 다중화(Multiplexing): 하나의 연결에서 터미널, 파일 전송, 포트 전달 등 여러 채널을 운용한다.
SSH에서 주로 사용하는 키는 역할이 서로 다르다.
| 키 | 역할 | 수명 |
|---|---|---|
| 서버 호스트 키(Host Key) | 서버 신원 증명 | 장기간 유지 |
| 사용자 키(User Key) | 사용자 인증 | 장기간 유지 |
| 세션 키(Session Key) | 실제 데이터 암호화 | 연결마다 새로 생성 |
서버의 호스트 키와 사용자의 공개 키는 암호화된 데이터를 직접 주고받는 용도가 아니다. 연결 과정에서 신원을 확인하거나 전자서명(Digital Signature)을 검증하는 데 사용한다. 실제 세션 데이터는 계산 비용이 낮은 대칭 키 암호화(Symmetric-key Encryption)로 처리한다.
2. 왜 사용하는가
텔넷(Telnet), 파일 전송 프로토콜(FTP), 원격 셸(rsh) 같은 오래된 프로토콜은 인증 정보와 데이터를 평문으로 전송할 수 있다. 공격자가 네트워크 패킷을 가로채면 비밀번호와 명령 내용을 읽을 수 있다.
SSH는 하나의 암호화된 연결로 다음 작업을 처리한다.
- 서버 원격 로그인과 관리
- 원격 명령 자동 실행
- 보안 복사(SCP)와 SSH 파일 전송 프로토콜(SFTP)을 이용한 파일 전송
- 깃(Git) 저장소 인증과 데이터 전송
- 로컬·원격·동적 포트 전달(Port Forwarding)
- 지속적 통합·배포(CI/CD) 환경의 서버 배포
공개 키 인증을 사용하면 비밀번호를 네트워크로 전송하지 않는다. 클라이언트가 개인 키로 서명하고, 서버가 등록된 공개 키로 서명을 검증한다.
3. 동작 원리
3.1 TCP 연결과 버전 교환
클라이언트는 서버의 TCP 22번 포트에 연결한다. 양쪽은 지원하는 SSH 프로토콜 버전과 구현 정보를 교환한다.
클라이언트 서버
│ │
│──────── TCP 연결 요청 ────────────>│
│<─────── TCP 연결 수립 ─────────────│
│──────── SSH 버전 문자열 ──────────>│
│<─────── SSH 버전 문자열 ───────────│이 단계에서는 아직 암호화 채널이 완성되지 않았다.
3.2 알고리즘 협상
클라이언트와 서버는 각자 지원하는 알고리즘 목록을 보낸다. 양쪽이 공통으로 지원하는 알고리즘 가운데 우선순위가 높은 조합을 선택한다.
협상 대상은 다음과 같다.
- 키 교환 알고리즘(Key Exchange Algorithm)
- 서버 호스트 키 알고리즘(Host Key Algorithm)
- 대칭 키 암호 알고리즘(Symmetric Cipher)
- 메시지 인증 코드(MAC, Message Authentication Code) 또는 인증된 암호화(AEAD)
- 압축 알고리즘
3.3 키 교환과 서버 인증
양쪽은 디피-헬먼 키 교환(Diffie–Hellman Key Exchange) 또는 타원 곡선 디피-헬먼 키 교환(ECDH)을 사용해 공유 비밀(Shared Secret)을 계산한다. 공유 비밀과 연결 과정의 정보를 바탕으로 세션 키를 유도한다.
서버는 키 교환 과정의 데이터에 호스트 개인 키로 서명한다. 클라이언트는 서버 호스트 공개 키로 서명을 검증한다.
클라이언트 서버
│ │
│──────── 키 교환 공개값 ──────────>│
│<─────── 키 교환 공개값 ───────────│
│<─────── 호스트 공개 키와 서명 ────│
│ │
│ 공유 비밀 계산 │ 공유 비밀 계산
│ 서버 서명 검증 │
│ 세션 키 유도 │ 세션 키 유도키 교환 공개값만 관찰한 공격자는 공유 비밀을 계산할 수 없다. 임시 키를 사용하는 키 교환은 순방향 안전성(Forward Secrecy)을 제공한다. 서버 호스트 개인 키가 나중에 유출되더라도 과거 세션의 임시 키까지 복원되지는 않는다.
전자서명이 유효하다는 사실만으로 서버가 신뢰할 대상인지는 알 수 없다. 클라이언트는 받은 호스트 공개 키를 known_hosts에 저장된 값과 비교해야 한다.
첫 접속에서는 저장된 키가 없으므로 다음과 같은 확인 메시지가 나타난다.
The authenticity of host 'example.com' can't be established.
Are you sure you want to continue connecting (yes/no/[fingerprint])?사용자가 키 지문(Fingerprint)을 확인하고 승인하면 호스트 키가 저장된다. 이후 서버가 다른 호스트 키를 제시하면 SSH는 중간자 공격(MITM, Man-in-the-Middle Attack) 가능성을 경고한다.
3.4 사용자 인증
암호화 채널이 만들어진 뒤 서버가 사용자를 인증한다. 공개 키 인증 과정은 다음과 같다.
- 서버는 사용자의 공개 키를
~/.ssh/authorized_keys에서 확인한다. - 클라이언트는 해당 공개 키에 대응하는 개인 키로 세션 관련 데이터에 서명한다.
- 서버는 등록된 공개 키로 서명을 검증한다.
- 검증에 성공하면 사용자가 개인 키를 보유했다고 판단한다.
개인 키 자체는 서버로 전송되지 않는다. 개인 키에 암호문(Passphrase)을 설정한 경우 암호문은 로컬에 저장된 개인 키 파일을 복호화하는 데 사용된다.
3.5 채널 생성과 데이터 전송
인증이 끝나면 하나의 SSH 연결 안에 여러 논리 채널(Channel)을 만들 수 있다.
하나의 TCP 연결
└── SSH 암호화 연결
├── 대화형 셸 채널
├── 원격 명령 채널
├── SFTP 채널
└── 포트 전달 채널각 SSH 패킷은 암호화되고 무결성 검증 정보를 포함한다. 공격자가 데이터를 읽거나 임의로 수정하면 복호화 또는 무결성 검증에 실패한다.
4. 코드 예제
키 생성과 공개 키 등록
에드워즈 곡선 전자서명 알고리즘(Ed25519) 키를 생성한다.
ssh-keygen -t ed25519 -a 100 -C "developer@example.com"기본 경로를 선택하면 다음 파일이 만들어진다.
~/.ssh/id_ed25519 개인 키
~/.ssh/id_ed25519.pub 공개 키공개 키를 서버에 등록한다.
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@example.comssh-copy-id를 사용할 수 없다면 공개 키를 서버의 authorized_keys에 추가한다.
ssh user@example.com 'mkdir -p ~/.ssh && chmod 700 ~/.ssh'ssh user@example.com 'cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys' < ~/.ssh/id_ed25519.pub원격 접속과 명령 실행
ssh user@example.com기본 포트가 아닌 2222번 포트에 접속한다.
ssh -p 2222 user@example.com원격 서버에서 명령 하나를 실행하고 결과를 로컬 터미널로 받는다.
ssh user@example.com 'uname -a'여러 명령을 실행할 때는 원격 셸이 해석할 범위를 따옴표로 묶는다.
ssh user@example.com 'cd /srv/app && git status'SSH 설정 파일
~/.ssh/config에 접속 정보를 저장하면 긴 명령을 반복하지 않아도 된다.
Host production
HostName example.com
User deploy
Port 22
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes다음 명령으로 접속한다.
ssh production설정 파일에는 개인 키 경로와 내부 서버 주소가 포함될 수 있으므로 현재 사용자만 읽을 수 있게 권한을 제한한다.
chmod 600 ~/.ssh/config로컬 포트 전달
로컬의 15432번 포트로 들어온 연결을 SSH 서버가 접근할 수 있는 db.internal:5432로 전달한다.
ssh -N -L 15432:db.internal:5432 user@example.com애플리케이션은 로컬 주소로 데이터베이스에 접속한다.
psql -h 127.0.0.1 -p 15432 -U appuser appdb-N은 원격 명령을 실행하지 않고 포트 전달만 수행한다.
원격 포트 전달
원격 서버의 8080번 포트로 들어온 연결을 로컬 개발 서버의 3000번 포트로 전달한다.
ssh -N -R 8080:127.0.0.1:3000 user@example.com원격 포트가 외부 인터페이스에 공개되는지는 서버의 GatewayPorts 설정과 바인딩 주소에 따라 달라진다.
동적 포트 전달
로컬에 SOCKS 프록시를 만든다.
ssh -N -D 127.0.0.1:1080 user@example.comSOCKS5를 지원하는 프로그램은 127.0.0.1:1080을 프록시로 사용해 SSH 서버를 경유할 수 있다.
파일 전송
SFTP로 대화형 파일 전송 세션을 연다.
sftp user@example.comSCP로 로컬 파일을 원격 서버에 복사한다.
scp ./release.tar.gz user@example.com:/tmp/release.tar.gz5. 시간 복잡도 또는 성능 특성
SSH 연결 비용은 크게 공개 키 연산과 데이터 암호화로 나뉜다.
연결 수립
키 교환과 전자서명 검증에는 공개 키 암호 연산이 필요하다. 공개 키 연산은 대칭 키 암호 연산보다 계산 비용이 크므로 짧은 명령마다 새 SSH 연결을 만들면 연결 수립 비용이 반복된다.
연결 다중화(Connection Multiplexing)를 사용하면 이미 인증된 연결을 재사용할 수 있다.
Host *
ControlMaster auto
ControlPath ~/.ssh/control-%C
ControlPersist 10m첫 연결은 키 교환과 인증을 수행하지만, 같은 대상에 대한 후속 연결은 기존 제어 연결을 재사용한다.
데이터 전송
길이가 n바이트인 데이터를 암호화하거나 복호화하는 작업량은 데이터 크기에 비례하므로 O(n)이다. 메시지 인증과 패킷 처리도 전송량에 비례한다.
실제 처리량은 다음 요소의 영향을 받는다.
- 네트워크 대역폭과 왕복 시간(RTT)
- 선택된 암호 알고리즘
- 클라이언트와 서버의 CPU 성능
- 패킷 크기와 SSH 흐름 제어
- 압축 사용 여부
- 다중 채널의 동시 사용량
압축은 텍스트처럼 반복이 많은 데이터의 전송량을 줄일 수 있지만 CPU 사용량을 늘린다. 이미지, 동영상, 압축 파일처럼 이미 압축된 데이터에는 효과가 작다.
SSH는 일정량의 데이터나 시간이 지나면 재키 교환(Rekeying)을 수행할 수 있다. 재키 교환 중에는 추가 암호 연산과 짧은 지연이 발생한다.
6. 실무 사용 사례
운영 서버 관리
운영자는 SSH로 서버에 접속해 프로세스 상태와 로그를 확인한다.
ssh production 'systemctl status web-app'ssh production 'journalctl -u web-app --since "10 minutes ago"'직접적인 관리자 계정 로그인보다 일반 사용자로 접속한 뒤 필요한 명령에만 sudo를 사용하는 방식이 권한 범위를 줄인다.
자동 배포
배포 시스템은 전용 키를 사용해 제한된 계정으로 원격 명령을 실행할 수 있다.
ssh deploy@example.com 'cd /srv/app && ./deploy.sh'배포 키는 개인 개발자의 키와 분리하고, 필요한 저장소와 명령에만 권한을 부여한다.
비공개 서비스 접근
데이터베이스 포트를 인터넷에 공개하지 않고 SSH 로컬 포트 전달로 접근한다.
개발 PC:15432
│
└── SSH 암호화 터널 ──> 배스천 호스트 ──> 내부 DB:5432이 구조에서는 데이터베이스 방화벽이 배스천 호스트(Bastion Host)의 연결만 허용하도록 제한할 수 있다.
깃 저장소 인증
SSH 공개 키를 깃 호스팅 서비스에 등록하면 비밀번호 없이 저장소에 접근할 수 있다.
git clone git@example.com:team/project.gitSSH는 키로 사용자를 인증하고, 깃은 인증된 사용자에게 저장소 권한이 있는지 별도로 확인한다.
7. 주의할 점
호스트 키 경고를 무시하지 않는다
다음 경고는 서버가 재설치됐거나 호스트 키가 변경됐을 때 발생할 수 있지만, 중간자 공격 가능성도 나타낸다.
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!원인을 확인하기 전에 known_hosts 항목을 삭제하면 서버 신원 검증을 우회하게 된다. 서버 관리자가 제공한 새 키 지문과 비교한 뒤 갱신해야 한다.
개인 키를 보호한다
개인 키는 다른 사용자나 저장소에 노출되지 않아야 한다.
chmod 600 ~/.ssh/id_ed25519개인 키에는 암호문을 설정하고, 깃 저장소·컨테이너 이미지·공유 디렉터리에 포함하지 않는다. 유출된 키는 즉시 서버와 서비스의 등록 목록에서 제거하고 새 키로 교체한다.
비밀번호 인증과 관리자 로그인을 제한한다
공개 키 로그인이 준비된 서버에서는 비밀번호 추측 공격을 줄이기 위해 다음 설정을 검토할 수 있다.
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes설정을 변경하기 전에 별도 터미널에서 공개 키 로그인이 성공하는지 확인해야 한다. 검증 없이 비밀번호 인증을 끄면 서버에 접속하지 못할 수 있다.
설정 문법을 검사한 뒤 서비스를 다시 불러온다.
sudo sshd -tsudo systemctl reload ssh배포판에 따라 서비스 이름은 sshd일 수 있다.
에이전트 전달을 최소화한다
SSH 에이전트 전달(Agent Forwarding)을 사용하면 원격 서버에서 로컬 에이전트의 키를 이용할 수 있다. 신뢰할 수 없는 원격 서버가 에이전트 연결을 악용할 수 있으므로 기본적으로 비활성화하고 필요한 호스트에만 허용한다.
Host trusted-bastion
ForwardAgent yes에이전트 전달은 개인 키 파일을 서버로 복사하지 않지만, 연결된 동안 원격 서버가 에이전트에 서명 요청을 보낼 수 있다.
포트 전달의 공개 범위를 확인한다
-L, -R, -D 옵션의 바인딩 주소를 잘못 지정하면 내부 서비스나 프록시가 외부에 노출될 수 있다. 로컬에서만 사용할 포트는 127.0.0.1에 바인딩한다.
ssh -N -L 127.0.0.1:15432:db.internal:5432 user@example.com키 형식과 알고리즘 정책을 확인한다
서버와 클라이언트는 지원 알고리즘과 보안 정책이 다를 수 있다. 오래된 알고리즘을 접속 명령에서 무조건 다시 활성화하기보다 서버 소프트웨어와 키를 갱신하는 편이 안전하다.
협상 과정을 확인하려면 상세 로그를 사용한다.
ssh -vvv user@example.com상세 로그에는 사용자명, 호스트명, 키 경로 같은 정보가 포함될 수 있으므로 공유 전에 민감 정보를 제거한다.
8. 핵심 정리
- SSH는 TCP 위에 암호화 채널을 만들고 원격 로그인, 명령 실행, 파일 전송, 포트 전달을 제공한다.
- 호스트 키는 서버 신원을 확인하고, 사용자 키는 사용자를 인증하며, 세션 키는 실제 데이터를 암호화한다.
- 키 교환으로 연결마다 세션 키를 만들고, 대칭 키 암호화와 무결성 검증으로 이후 통신을 보호한다.
- 공개 키 인증에서는 개인 키가 서버로 전송되지 않으며, 클라이언트가 만든 서명을 서버가 공개 키로 검증한다.
known_hosts경고, 개인 키 권한, 에이전트 전달, 포트 바인딩 범위를 확인해야 SSH의 보안 특성을 유지할 수 있다.