SSH 란
SSH(Secure Shell) = 원격 셸·파일 전송·터널링을 단일 암호화된 TCP 채널(기본 포트 22) 위에서 다루는 프로토콜. 1995 Tatu Ylönen 이 평문 트로이카(telnet, rlogin, rsh, FTP) 대체로 설계.
- 텔넷 대체 맞음. 정확히는 telnet/rlogin/rsh/FTP 를 한꺼번에 갈아엎음.
- 모든 게 한 TCP 22 연결 안의 다중 채널로 흐름 (shell, exec, sftp, port-forwarding).
“telnet 대체” 가 정확히 어떤 역할?
telnet 은 두 가지로 쓰임:
- 원격 셸 프로토콜 (본업) — 평문으로 원격 로그인. SSH 가 대체한 게 이 부분.
- Generic TCP probe (부수 사용) — 임의 포트에 raw TCP 연결해서 “포트 열렸나·서비스 listening 중인가” 확인.
telnet host port→ “Connected to …” 뜨면 OK.
SSH 는 #1 만 대체. SSH 클라이언트는 22 포트의 SSH 서버하고만 말함. ssh google.com 80 같은 건 TCP 연결까지는 되지만 곧장 SSH 핸드셰이크 강제 → HTTP 서버가 SSH banner 응답 안 함 → kex_exchange_identification: read: Connection reset by peer 에러. 결과가 “SSH 가 거기서 도냐” 로 오염돼 진짜 포트 listen 여부 확인 못 함.
| 도구 | TCP 연결 후 | 포트 probe 용도? |
|---|---|---|
telnet host port | raw stdin/stdout | ✓ |
nc host port | raw stdin/stdout (telnet 후속·현대 표준) | ✓ |
ssh host port | SSH 프로토콜 핸드셰이크 강제 | ✗ |
→ telnet 의 #2 역할은 nc (netcat) 가 가져감. SSH 는 generic TCP client 가 아님. (자세한 nc 사용은 07. Linux Networking 의 ### telnet — 포트 listen 확인 참고.)
동작 단계
- TCP 22 핸드셰이크 = 평범한 3-way handshake.
- SSH 버전 교환 = 양쪽이
SSH-2.0-OpenSSH_9.6같은 배너를 평문으로 주고받음. - KEX (Key Exchange) = Diffie-Hellman 계열(요즘 Curve25519) 로 임시 세션 키 합의. 도청자가 못 알아내는 공유 비밀.
- 서버 인증 = 클라이언트가 “너 진짜 그 서버 맞아?” 검증. host key 사용.
- 사용자 인증 = 서버가 “너 진짜 그 사람 맞아?” 검증. password / publickey / GSSAPI 등.
- 채널 오픈 = 그 위에 shell, exec, sftp, port-forwarding 채널이 다중화돼서 흐름.
서버 인증 — host key + TOFU
- 서버는 영구 host key 쌍을 가진다 (
/etc/ssh/ssh_host_{rsa,ecdsa,ed25519}_key). - 첫 접속 시 클라이언트가 host key fingerprint 를 보여주고
~/.ssh/known_hosts에 기록 = TOFU (Trust On First Use). - 이후 host key 가 바뀌면 경고 (MITM 의심):
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! - CA 기반 검증은 옵션 — 아래
### SSH CA참고.
사용자 인증 — password vs publickey
password
- 채널은 암호화돼 있지만 안에 비번 평문이 흐름. 서버가 받아
/etc/shadow와 대조. - 약점 = 약한 비번·재사용·brute force. publickey
- 클라이언트가 priv key 로 챌린지(랜덤 nonce + 세션 ID) 에 서명해서 보냄.
- 서버는
~/.ssh/authorized_keys의 pub key 로 서명 검증. - 비번 자체가 서버에 안 감. priv key 는 평생 디스크 밖으로 안 나옴.
- 흐름:
ssh-keygen -t ed25519로 키 쌍 생성.ssh-copy-id user@host로 pub 만 서버에 등록.- 다음부터 비번 없이 접속.
pub/priv key 의 정체 — TLS·SSH·GPG 비교
수학은 같다 (RSA / ECDSA / Ed25519). 묶음 형식과 신뢰 모델이 다르다.
| 시스템 | 묶음 | 신뢰 모델 |
|---|---|---|
| TLS / HTTPS | X.509 인증서 | CA-PKI (브라우저 root store) |
| SSH (기본) | raw key | TOFU (known_hosts) |
| SSH CA (옵션) | OpenSSH cert | CA 키 자체를 신뢰 |
| GPG | OpenPGP packet | Web of Trust |
| 본질 공통 = “priv 를 가졌음을 pub 으로 검증할 수 있다”. 무엇으로 묶고 누가 보증하느냐의 차이. |
SSH CA — 옵션
- 기본 SSH 는 CA 없이 TOFU 로 충분 (개인·소규모).
- 호스트·사용자 수가 늘면
known_hosts·authorized_keys가 폭주 → CA 도입 가치. - 한 번 CA 만 신뢰하면 그 CA 가 사인한 모든 host/user cert 자동 신뢰.
- 발급:
ssh-keygen -s ca_key -I id -n principals -V +1h user_key.pub - 서버가 신뢰 =
sshd_config의TrustedUserCAKeys /path/to/ca.pub. - 클라이언트가 신뢰 =
~/.ssh/known_hosts의@cert-authority *.example.com ssh-ed25519 .... - 사례 = Netflix BLESS, Facebook SSH CA, GitHub SSH certificate authority.
SCP / SFTP / HTTPS 와의 관계
- HTTPS = HTTP + TLS. SSH 와 완전 별개 (포트 443). 공통점은 “그 위에 암호화 채널” 뿐.
- SFTP = SSH 채널 안에서 도는 파일 전송 프로토콜. FTP 와 무관 (이름만 비슷). 22 포트.
- SCP = 옛 단순 파일 복사 명령. 원래는 SSH 위 자체 프로토콜. OpenSSH 9.0(2022)부터 내부적으로 SFTP 사용 — SCP 프로토콜의 보안 문제(CVE-2020-15778 등) 때문. 외부 명령 인터페이스만 동일, 내부는 SFTP.
- 요약 = SFTP·SCP 는 SSH 의 자식. HTTPS 는 남.
SCP 파일 전송
scp -rp -i ssh-key-2023-08-09.key {파일} opc@192.0.2.231:/home/opc
TTY 와 PTY
- TTY = teletype, 옛 종이 단말 추상화. 오늘날 = 리눅스 커널의 “터미널 디바이스” 인터페이스 (
/dev/tty*,/dev/pts/*). echo, line buffering, signal(Ctrl-C → SIGINT) 처리. - PTY = pseudo-TTY. 실제 하드웨어 없이 소프트웨어로 만든 TTY 페어.
- SSH 가 interactive shell 줄 때 서버 쪽에 PTY 할당.
- 동작:
ssh user@host(인자 없음) → 자동 PTY, interactive shell.ssh user@host 'ls -l'→ 명령 한 줄, PTY 없음. 빠르고 script 친화.-t= 강제 할당 (ssh -t host 'sudo top'—sudo·top이 TTY 요구).-T= 강제 비할당 (input 을 파이프로 줄 때).
접근 제어
인증 (누구냐) = password / publickey / CA — 위에 다룸.
인가 (뭘 할 수 있냐) — sshd_config
AllowUsers alice bob,AllowGroups devs— 화이트리스트.Match User deploy— 사용자별 다른 정책 블록.ForceCommand /usr/bin/git-shell— 그 사용자는 정해진 명령만 (케이지).PermitRootLogin no,PasswordAuthentication no— 흔한 하드닝.
키별 제약 — authorized_keys 옵션 prefix
from="10.0.0.0/8",command="rsync ...",no-pty,no-port-forwarding ssh-ed25519 AAAA... user
from== 그 IP 대역에서만 그 키 사용 가능.command== 그 키로 들어오면 무조건 그 명령만.no-pty,no-port-forwarding= 권한 좁힘.
운영 레이어 = bastion(jump host), MaxAuthTries, LoginGraceTime, fail2ban, audit log.
AWS EC2 key pair
- EC2 콘솔/CLI 에서 key pair 생성 → AWS 가 그 자리에서 키 쌍 생성.
- public = AWS 보관 (콘솔에서 다시 조회 가능).
- private = 그 순간 1회만 다운로드. 다시는 못 받음.
- 다운로드 포맷:
.pem= OpenSSH·표준 도구용. 텍스트 base64..ppk= PuTTY 전용.
.pem= 클라이언트 priv key. EC2 인스턴스 안~/.ssh/authorized_keys가 대응 pub key.- pub 이 어떻게 거기 들어갔냐 = cloud-init 이 첫 부팅 시 EC2 IMDS(
http://169.254.169.254/latest/meta-data/public-keys/) 에서 받아 적음. AWS 가 직접 SSH 로 넣는 게 아니라 인스턴스가 스스로 받아 적는 모델. - 흔한 함정
- 퍼미션 느슨 →
Permissions 0644 for 'mykey.pem' are too open.→chmod 400 mykey.pem.04. Linux Permissions노트의 user 권한 규칙이 보안의 마지막 선. - 사용자 이름 틀림 — AL2023·Amazon Linux =
ec2-user, Ubuntu =ubuntu, RHEL =ec2-user/root, Debian =admin/debian.
- 퍼미션 느슨 →
.pem분실 시 회복 = (a) EC2 Instance Connect 임시 키 푸시, (b) SSM Session Manager 우회, (c) 인스턴스 stop → 루트 볼륨 다른 인스턴스에 붙여authorized_keys수정 → 다시 붙임.
요즘 추세 = .pem 자체를 안 쓰는 방향
- EC2 Instance Connect = IAM 권한으로 임시 pub key(60초 유효) 푸시. 영구 키 분실·유출 리스크 줄임. 22 여전히 필요.
- SSM Session Manager = SSH 포트 자체를 안 열고 SSM agent ↔ AWS 백본 ↔ 콘솔·
aws ssm start-session. 요즘 production 베스트 프랙티스. - SSH CA 자체 운영 = Bastion + IAM 연동 CA + 짧게 유효한 user cert. Netflix BLESS, Teleport.
Lima 키 페어 해부 (실습)
Lima 가 첫 실행 시 호스트(맥) 에 키 쌍을 자동 생성하고 VM authorized_keys 에 pub 만 박는다 = 정확히 publickey 인증 모델.
VM 안의 pub key
root@lima-default:/home/meatsby.linux/.ssh# cat authorized_keys
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE6xYhk4UCjq4ihWu6OLcHtdBmRQGXQcyQ5h8T6m+KiB limaauthorized_keys 한 줄 = <algorithm> <base64-pub-key> <comment>.
ssh-ed25519= 알고리즘.- base64 는 length-prefixed wire 포맷:
AAAAC3=00 00 00 0B(이름 11 바이트)NzaC1lZDI1NTE5=ssh-ed25519AAAAIE=00 00 00 20(키 32 바이트)- 그 뒤 32 바이트 = Ed25519 공개점
lima= comment. 라벨일 뿐, 검증에 안 씀.
호스트의 priv key
$ ls -al ~/.lima/_config/user*
-rw-------@ 1 meatsby staff 387 Dec 1 22:02 /Users/meatsby/.lima/_config/user
-rw-r--r--@ 1 meatsby staff 86 Dec 1 22:02 /Users/meatsby/.lima/_config/user.pub
$ cat ~/.lima/_config/user
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBOsWIZOFAo6uIoVruji3B7XQZkUBl0HMkOYfE+pviogQAAAIheOuQMXjrk
... (priv key 본문 truncate — 실 키 노출 방지) ...
BmRQGXQcyQ5h8T6m+KiBAAAABGxpbWEB
-----END OPENSSH PRIVATE KEY------ 권한
600(-rw-------) = sshd 가 요구하는 최소 보호. @= 맥 확장속성(xattr). 무해.
priv key 포맷 — PEM 인데 종류가 다름
BEGIN RSA PRIVATE KEY= 옛 PKCS#1 (RSA 전용).BEGIN PRIVATE KEY= PKCS#8 (알고리즘 무관 표준).BEGIN OPENSSH PRIVATE KEY= OpenSSH 자체 포맷 (2014~). Ed25519 같은 신 알고리즘 지원, bcrypt 기반 passphrase 암호화 옵션.- AWS 가 주는
.pem은 보통 PKCS#1. OpenSSH 가 둘 다 읽음.
passphrase 없음
priv 파일 첫 base64 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQ 디코드 → 매직 openssh-key-v1\0 다음에 none none = (cipher, KDF). 암호화 안 됨. 누구든 이 파일 읽으면 그대로 키 주인.
- Lima 는 자동화를 위해 일부러 passphrase 없이 생성.
ssh-keygen -p -f keyfile로 사후 추가 가능.
pub key 가 priv 파일 안에 들어있다 (원본 기준 3 번)
원본 priv 파일 base64 안에 E6xYhk4UCjq4ihWu6OLcHtdBmRQGXQcyQ5h8T6m+KiB (.pub 의 32 바이트) 가 세 군데 나타남:
- priv 파일의 pub 섹션 (위에 보이는 line 2
...OsWIZOFAo...+pviogQ부분). - priv 섹션 헤더 안에 (중복 저장 — truncate 한 가운데).
- 진짜 비밀 부분의 끝 32 바이트 — Ed25519 가 빠른 서명을 위해 priv 64 바이트를
secret(32) || public(32)형태로 보관 (truncate 한 가운데 + 위 마지막 줄BmRQGXQcyQ5h8T6m+KiB가 그 꼬리).
AAAABGxpbWEB = lima comment 가 priv 안에도 박힘 (마지막 줄).
매칭 검증
# 두 키가 같은 쌍이면 출력 없음 (단, -y 는 comment 누락 가능)
diff <(ssh-keygen -y -f ~/.lima/_config/user) ~/.lima/_config/user.pub
# fingerprint
ssh-keygen -lf ~/.lima/_config/user.pub
# 256 SHA256:xxxxx... lima (ED25519)VM 안에서 ssh-keygen -lf /home/meatsby.linux/.ssh/authorized_keys 와 같은 fingerprint 나와야 정합.
실제 흐름 보기
ssh -F ~/.lima/default/ssh.config -v lima-default-v 출력에서 6 단계가 한 화면에:
debug1: Local version string SSH-2.0-OpenSSH_...— 버전 교환debug1: kex: algorithm: curve25519-sha256— KEXdebug1: Server host key: ...— 서버 인증debug1: Offering public key: ~/.lima/_config/user ED25519 SHA256:...— publickey 인증 시도debug1: Authentication succeeded (publickey).— 통과
SSH 접속
ssh -i ssh-key-2023-08-09.key opc@192.0.2.231
SSH Config
~/.ssh/
├── config ← SSH 설정 파일
├── id_rsa ← 기본 개인 키 (기본값)
├── id_rsa.pub ← 기본 공개 키
├── company_id_rsa ← 회사용 개인 키
├── company_id_rsa.pub ← 회사용 공개 키
├── personal_server_id ← 개인 서버용 키
├── personal_server_id.pub
└── known_hosts ← 접속한 호스트 정보 저장
# ~/.ssh/config
# 회사 서버
Host company-server
HostName server.company.com
User ec2-user
IdentityFile ~/.ssh/company_id_rsa
IdentitiesOnly yes
# 개인 서버
Host my-vps
HostName 123.45.67.89
User ubuntu
IdentityFile ~/.ssh/personal_server_id
IdentitiesOnly yes
# 개인 서버 접속
ssh my-vps
# 회사 서버 접속
ssh company-server
References
- RFC 4251 — The Secure Shell (SSH) Protocol Architecture
- RFC 4252 — The Secure Shell (SSH) Authentication Protocol
- ssh(1) | OpenBSD manual
- sshd_config(5) | OpenBSD manual
- OpenSSH PROTOCOL.key — private key format
- OpenSSH 9.0 release notes — scp uses sftp by default
- Amazon EC2 key pairs | AWS docs
- Connect using EC2 Instance Connect | AWS docs
- AWS Systems Manager Session Manager | AWS docs
- Lima | lima-vm.io