SSH StrictHostKeyChecking의 yes, accept-new, ask, no: known_hosts는 새 키와 바뀐 키를 어떻게 다루나
OpenSSH 클라이언트가 처음 보는 호스트 키와 바뀐 호스트 키를 StrictHostKeyChecking 값마다 어떻게 처리하는지, known_hosts 줄 형식과 ssh-keygen -F·-R·-H 사용법을 OpenBSD 매뉴얼과 RFC 4251 기준으로 정리했다.
SSH로 처음 접속하는 서버는 클라이언트가 아직 그 서버의 호스트 키를 모른다. 이미 접속한 적 있는 서버가 예전과 다른 키를 내밀 수도 있다. OpenSSH 클라이언트가 이 두 경우를 어떻게 처리할지는 StrictHostKeyChecking 옵션이 정하고, 판단 근거가 되는 키는 known_hosts 파일에 있다. 자동화 스크립트에 StrictHostKeyChecking=no를 넣기 전에 각 값이 정확히 무엇을 허용하는지 볼 필요가 있다.
이 글은 OpenBSD 매뉴얼 서버의 OpenBSD-current 매뉴얼 ssh_config(5)(페이지 날짜 2026년 10월 9일), sshd(8)(2026년 9월 2일), ssh-keygen(1)(2026년 10월 1일)와 SSH 프로토콜 구조를 정의한 RFC 4251을 근거로 하며, 모두 2026-10-19에 확인했다. 리눅스 배포판이나 macOS에 들어 있는 OpenSSH는 버전이 달라 기본값이나 옵션이 다를 수 있으니 man ssh_config로 직접 확인하는 것이 좋다. OpenSSH가 아닌 SSH 구현은 다루지 않는다. 경고 메시지의 실제 문구와 호스트 키 검증 과정을 자세히 설명하는 ssh(1) 매뉴얼은 이번에 근거로 쓰지 않았기 때문에 이 글에 인용하지 않는다.
호스트 키 검사가 막는 것
RFC 4251 §4.1에 따르면 서버 호스트 키는 키 교환 과정에서 클라이언트가 정말 올바른 서버와 통신하고 있는지 확인하는 데 쓰인다. 그러려면 클라이언트가 서버의 공개 호스트 키를 미리 알고 있어야 한다. RFC는 두 가지 신뢰 모델을 든다.
- 클라이언트가 호스트 이름(사용자가 입력한 이름)과 공개 호스트 키를 짝지은 로컬 데이터베이스를 가진다. 중앙 기반 시설이 필요 없지만 관리가 번거로워질 수 있다.
- 신뢰하는 인증 기관(CA)이 이름과 키의 연결을 인증한다. 클라이언트는 CA 루트 키만 알면 된다.
OpenSSH의 known_hosts는 첫 번째 모델의 데이터베이스다. 아래에서 볼 @cert-authority 표시는 두 번째 모델을 같은 파일 안에서 쓰는 방법이다.
같은 절은 처음 접속할 때 이름과 키의 연결을 검사하지 않는 선택지가 프로토콜에 있다고 적는다. 이때도 수동적인 도청은 막지만 능동적인 중간자(MITM) 공격에는 취약해지므로, 구현은 이런 연결을 보통 기본으로 허용해서는 안 된다(SHOULD NOT). 대신 처음 접속할 때만 검사 없이 키를 받아 로컬 데이터베이스에 저장하고 이후 접속마다 그 키와 비교하는 전략을 예로 든다. 또 검증할 수 없는 호스트 키를 받지 않는 옵션을 모든 구현이 제공해야 한다고(SHOULD) 적는다.
§9.3.4는 키를 미리 안전하게 받아 두었다면 중간자가 내민 키는 저장된 키와 맞지 않게 되고, 이때 사용자에게 경고해야 한다고(SHOULD) 설명한다. 호스트와 키의 연결을 신뢰할 수 있게 확인하지 않고 프로토콜을 쓰는 것은 본질적으로 안전하지 않으며 권장되지 않는다(NOT RECOMMENDED)는 것이 이 절의 결론이다.
키는 어디에서 찾나
ssh_config(5)에 따르면 클라이언트는 두 종류의 파일을 읽는다.
| 옵션 | 기본값 | 의미 |
|---|---|---|
UserKnownHostsFile |
~/.ssh/known_hosts, ~/.ssh/known_hosts2 |
사용자별 호스트 키 데이터베이스. none이면 사용자별 파일을 무시한다. |
GlobalKnownHostsFile |
/etc/ssh/ssh_known_hosts, /etc/ssh/ssh_known_hosts2 |
시스템 전체 호스트 키 데이터베이스 |
sshd(8)의 SSH_KNOWN_HOSTS FILE FORMAT 절은 전역 파일은 관리자가 미리 준비하는 것(선택 사항)이고, 사용자별 파일은 알 수 없는 호스트에 접속할 때마다 그 키가 추가되며 자동으로 관리된다고 설명한다. 다만 클라이언트가 실제로 키를 자동으로 추가하는지는 클라이언트의 정책과 설정에 달려 있다. 아래 StrictHostKeyChecking 표에서 보듯 yes는 키를 자동으로 추가하지 않고, ask는 사용자가 확인한 뒤에만 추가한다. 두 파일의 내용이 서로 충돌할 수도 있는데, 어느 쪽에서든 유효한 정보를 찾으면 인증이 받아들여진다. KnownHostsCommand를 지정하면 이 파일들을 읽은 뒤 명령을 실행해 같은 형식의 호스트 키 줄을 추가로 얻는다. 이 명령이 비정상 종료하거나 0이 아닌 종료 상태를 돌려주면 연결이 끊긴다.
StrictHostKeyChecking 값별 동작
ssh_config(5)의 설명을 새 호스트(키가 아직 없는 경우)와 바뀐 키(저장된 키와 다른 경우)로 나눠 보면 이렇다.
| 값 | 새 호스트 키 | 바뀐 호스트 키 |
|---|---|---|
yes |
자동으로 추가하지 않는다. 새 호스트는 모두 사용자가 직접 추가해야 한다. | 접속을 거부한다. |
accept-new |
사용자의 known_hosts 파일에 자동으로 추가한다. |
접속을 허용하지 않는다. |
ask (기본값) |
사용자가 정말 원하는지 확인한 뒤에만 추가한다. | 접속을 거부한다. |
no 또는 off |
사용자 known hosts 파일에 자동으로 추가한다. | “몇 가지 제한 아래에서” 접속을 계속하게 둔다. |
매뉴얼은 어느 값이든 이미 알고 있는 호스트의 키는 자동으로 검증된다고 덧붙인다. 즉 이 옵션이 바꾸는 것은 키가 없거나 다를 때의 처리이고, 저장된 키와 맞는지 비교하는 일 자체는 끄지 않는다.
yes는 매뉴얼 표현으로 MITM 공격에 대해 최대한의 보호를 제공하지만, /etc/ssh/ssh_known_hosts가 잘 관리되지 않거나 새 호스트에 자주 접속하는 환경에서는 번거로울 수 있다.
no의 “몇 가지 제한”이 구체적으로 무엇인지는 ssh_config(5)에 적혀 있지 않다. 이 글은 그 내용을 다룬 ssh(1)을 근거로 쓰지 않았으므로 제한의 내용은 설명하지 않는다. 위 표에서 확실히 말할 수 있는 것은 네 값 중 바뀐 키에서 접속을 거부하지 않는 값이 no(off)뿐이라는 점이다.
어떤 값을 고를까
이 절은 매뉴얼과 RFC의 내용에서 끌어낸 판단이다.
- **
accept-new**는 RFC 4251 §4.1이 예로 든 “처음에는 받아 저장하고, 이후에는 비교” 전략과 같은 모양이다. 처음 연결이 안전한 경로라는 전제를 받아들일 수 있는 자동화 작업이라면, 대화형 확인 없이 돌아가면서도 바뀐 키는 막는다. 대신 첫 연결에서 받은 키가 진짜인지는 확인하지 않는다. - **
yes**는 키를 미리 배포해 둘 수 있을 때 쓴다. sshd(8)은known_hosts줄을 손으로 입력하지 말고 스크립트나 ssh-keyscan(1)으로 만들거나, 서버의/etc/ssh/ssh_host_*_key.pub내용 앞에 호스트 이름을 붙여 만들라고 권한다. 네트워크 너머에서 키를 받아 오는 방법은 받은 키가 진짜인지 따로 확인하지 않는 한 첫 연결과 같은 문제를 안는다. RFC 4251 §9.3.4는 서버 관리자가 호스트 키 자체에 기대지 않는 수단(보안 웹 페이지, 종이 등)으로 지문을 확인할 수 있게 하라고 권한다. - **
no**는 호스트 키가 바뀌었을 때 접속을 일률적으로 거부하지 않는 유일한 값이다. 접속은 “몇 가지 제한 아래에서” 계속될 뿐이고, ssh_config(5)는 바뀐 키를 받아들이거나 저장한다고 적지 않는다. 그래도 RFC 4251 §9.3.4가 권장하지 않는다고 한 바로 그 사용법에 가깝다.
known_hosts 한 줄의 형식
sshd(8)의 SSH_KNOWN_HOSTS FILE FORMAT 절에 따르면 각 줄에는 공백으로 구분된 다음 필드가 올 수 있다. 표시(marker, 선택), 호스트 이름들, 키 종류, base64로 인코딩한 키, 주석(선택)이다. 표시와 주석은 선택 사항이므로 아래 예시에는 필드가 세 개나 네 개뿐인 줄도 있다. 주석은 줄 끝까지 이어지며 쓰이지 않는다. #으로 시작하는 줄과 빈 줄은 무시된다.
아래는 sshd(8)에 실린 예시 파일을 줄별로 옮긴 것이다(키 값은 원문에서도 생략되어 있다).
# Comments allowed at start of line
cvs.example.net,192.0.2.10 ssh-rsa AAAA1234.....=
# A hashed hostname
|1|JfKTdBh7rNbXkVAQCRp4OQoPfmI=|USECr3SWf1JUPsms5AqfD5QfxkM= ssh-rsa AAAA1234.....=
# A revoked key
@revoked * ssh-rsa AAAAB5W...
# A CA key, accepted for any host in *.mydomain.com or *.mydomain.org
@cert-authority *.mydomain.org,*.mydomain.com ssh-rsa AAAAB5W...
호스트 이름 필드의 규칙은 이렇다.
- 쉼표로 구분한 패턴 목록이고
*와?는 와일드카드다. 패턴 앞에!를 붙이면 부정이다. 이름이 부정 패턴에 맞으면 같은 줄의 다른 패턴에 맞아도 그 줄은 받아들여지지 않는다. - 기본 포트가 아닌 포트는
[호스트]:포트처럼 이름이나 주소를 대괄호로 감싸고 콜론과 포트 번호를 붙인다. - 해시된 이름은
|로 시작한다. 한 줄에 해시된 이름은 하나만 올 수 있고, 부정이나 와일드카드를 쓸 수 없다.
클라이언트가 서버를 인증할 때 이 패턴과 비교하는 이름은 사용자가 입력한 호스트 이름이다. HostKeyAlias를 지정했다면 그 값이, CanonicalizeHostname 옵션을 썼다면 정규화된 서버 이름이 대신 쓰인다. ssh_config(5)는 HostKeyAlias가 SSH 연결을 터널링하거나 한 호스트에서 여러 서버를 돌릴 때 유용하다고 설명한다.
표시는 둘 중 하나다. @cert-authority는 그 줄의 키가 CA 키라는 뜻이다. 서버가 인증서를 내밀면 그 인증서에 서명한 CA의 키가 이 표시와 함께 있을 때 인증이 받아들여진다. @revoked는 폐기된 키로, 인증에도 CA로도 절대 받아들여지지 않고 발견되면 ssh(1)이 경고를 낸다. 같은 이름에 여러 줄이나 서로 다른 키를 두는 것은 허용되지만 권장되지는 않는다.
CheckHostIP를 yes로 두면 클라이언트는 known_hosts에서 호스트의 IP 주소도 추가로 검사한다. 매뉴얼은 이것이 DNS 스푸핑으로 호스트 키가 바뀐 경우를 잡아내며, 그 과정에서 StrictHostKeyChecking 설정과 상관없이 목적지 주소를 ~/.ssh/known_hosts에 추가한다고 적는다. 현재 기본값은 no다.
ssh-keygen으로 known_hosts 다루기
ssh-keygen(1)에는 known_hosts를 편집하는 옵션이 세 가지 있다. 아래 명령은 사용법을 보이기 위한 것이고, 이 글을 쓰면서 실행하지는 않았다. server.example.net은 예시 이름이다.
# 해당 호스트의 줄을 찾아 출력한다 (해시된 이름도 찾는다)
ssh-keygen -F server.example.net -f ~/.ssh/known_hosts
# 해당 호스트의 키를 모두 지운다. 기본 포트가 아니면 [이름]:포트로 쓴다
ssh-keygen -R '[server.example.net]:2222' -f ~/.ssh/known_hosts
# 파일 안의 호스트 이름과 주소를 모두 해시로 바꾼다
ssh-keygen -H -f ~/.ssh/known_hosts
-F는 지정한 호스트 이름(포트 포함 가능)을known_hosts파일에서 찾아 모든 항목을 보여 준다. 해시된 이름을 찾을 때 유용하다.-R은 지정한 호스트 이름(포트 포함 가능)에 속한 키를 모두 지운다. 해시된 호스트를 지울 때 유용하다.-H는 모든 호스트 이름과 주소를 해시로 바꾸고, 원래 내용은.old접미사가 붙은 파일로 옮긴다. 이미 해시된 이름은 건드리지 않으므로 두 형식이 섞인 파일에도 안전하게 쓸 수 있다.
예시에서 -f를 명시한 것은, 확인한 매뉴얼 본문이 -f를 생략했을 때 어떤 파일을 쓰는지 적고 있지 않기 때문이다. -H 뒤에 남는 .old 파일에는 해시 전의 이름이 그대로 있다는 점도 기억해 둘 만하다. 이름을 감추려고 해시했다면 그 파일도 처리해야 한다는 것은 위 설명에서 끌어낸 결론이다.
새로 추가되는 줄을 처음부터 해시하려면 ssh_config(5)의 HashKnownHosts를 켠다. 기본값은 no이고, 켜더라도 이미 파일에 있는 이름은 자동으로 바뀌지 않으므로 ssh-keygen -H를 따로 써야 한다.
바뀐 키를 만났을 때
서버를 다시 설치했거나 키를 교체했다는 확실한 이유가 있더라도, -R로 줄을 지우고 다시 접속하는 순간 새 키는 다시 “처음 보는 키”가 된다. RFC 4251 §9.3.4의 권고대로 새 지문을 호스트 키 자체와 무관한 경로로 확인한 다음에 받아들이는 것이 순서다. 이 순서는 RFC의 권고를 실제 절차에 적용한 것이며, 매뉴얼이 정한 절차는 아니다.
키 교체를 위한 UpdateHostKeys
서버 쪽에서 키를 바꿀 계획이라면 ssh_config(5)의 UpdateHostKeys가 관련된다. 이 옵션을 켜면 클라이언트는 인증이 끝난 뒤 서버가 보내는 추가 호스트 키 알림을 받아 UserKnownHostsFile에 더한다. 서버가 옛 키를 지우기 전에 새 공개 키를 미리 보낼 수 있어서 키를 무리 없이 교체할 수 있다.
추가 키는 다음 조건을 모두 만족할 때만 받아들여진다.
- 호스트 인증에 쓰인 키가 이미 신뢰되었거나 사용자가 명시적으로 받아들인 키다.
- 호스트가
GlobalKnownHostsFile이 아니라UserKnownHostsFile로 인증되었다. - 인증서가 아니라 일반 키로 인증되었다.
사용자가 UserKnownHostsFile 기본값을 바꾸지 않았고 VerifyHostKeyDNS를 켜지 않았다면 UpdateHostKeys는 기본으로 켜지고, 그렇지 않으면 no가 된다. ask로 두면 known_hosts 수정 전에 확인을 묻는데, 이 확인은 현재 ControlPersist와 함께 쓸 수 없어 그 경우 꺼진다. 매뉴얼은 이 기능에 쓰이는 “hostkeys@openssh.com” 확장을 지원하는 서버가 현재 OpenSSH 6.8 이상의 sshd(8)뿐이라고 적는다.
점검할 것
- 설정 파일과 스크립트에서
StrictHostKeyChecking no(또는off)를 찾는다. 자동화 목적이라면accept-new로 충분한지 검토한다. - 첫 접속의 키를 무엇으로 확인하는지 정한다. 키를 미리 배포할 수 있다면
yes와 전역 또는 사용자known_hosts를 함께 쓴다. - 비표준 포트 서버는
[이름]:포트형식으로 저장되고 지워진다는 점을-F,-R을 쓸 때 반영한다. ssh-keygen -H를 실행했다면.old파일을 어떻게 할지 정한다.- 쓰고 있는 OpenSSH의 매뉴얼에서 이 글의 기본값(
ask,HashKnownHosts no,CheckHostIP no,UpdateHostKeys조건)이 같은지 확인한다.
참고 자료
모두 2026-10-19에 확인했다. 매뉴얼은 OpenBSD-current 판이다.
- ssh_config(5), OpenBSD manual pages (페이지 날짜 2026년 10월 9일): CheckHostIP, GlobalKnownHostsFile, HashKnownHosts, HostKeyAlias, KnownHostsCommand, StrictHostKeyChecking, UpdateHostKeys, UserKnownHostsFile, VerifyHostKeyDNS
- sshd(8), OpenBSD manual pages (페이지 날짜 2026년 9월 2일): SSH_KNOWN_HOSTS FILE FORMAT, FILES
- ssh-keygen(1), OpenBSD manual pages (페이지 날짜 2026년 10월 1일): -F, -H, -R
- RFC 4251: The Secure Shell (SSH) Protocol Architecture (2006): §4.1, §9.3.4