SSH

https://www.ssh.com/academy/ssh/protocol#how-does-the-ssh-protocol-work 이미지 출처: https://www.ssh.com/academy/ssh/protocol#how-does-the-ssh-protocol-work


SSH는 Secure Shell의 줄임말로, 원격 호스트에 접속하기 위해 사용되는 보안 프로토콜이다.
기존 원격 접속은 텔넷(Telnet)이라는 방식을 사용했는데, 암호화를 제공하지 않기 때문에 보안상 취약하다. 실제로 WireShark 같은 패킷 분석 프로그램을 이용하면 누구나 쉽게 원격 접속 과정에서 옮겨지는 비밀번호나 파일 내용 등의 데이터를 탈취할 수 있다. 때문에 이를 암호화하는 SSH가 등장했고, 현재 원격 접속 보안을 위한 필수적인 요소로 자리잡고 있다. 그리고 클라우드 서비스에서 제공하는 서버는 기본적으로 원격 접속을 해서 접근하고 사용한다. 그래서 AWS 같은 CSP(Cloud Service Provider)에서 서버 생성시 필수적으로 SSH 보안 과정을 거친다.
SSH 방식을 알기 전 암호화에 대해서 간단하게 알아보자. 암호화에는 단방향 암호화, 양방향 암호화가 있다.

  • 단방향 암호화: 암호화 후 복호화 할 수 없다.
  • 양방향 암호화: 암호화, 복호화 둘 다 가능하다.
    • 대칭키 암호화(비밀키 암호화): 암호화 할 때 사용하는 키와 복호화 할 때 사용하는 키가 같다.
    • 비대칭키 암호화(공개키 암호화): 암호화 할 때 사용하는 키와 복호화 할 때 사용하는 키가 다르다. 비밀키로 암호화 하면 공개키로 복호화 할 수 있고, 공개키로 암호화 하면 비밀키로 복호화 할 수 있다.

SSH 공개키 인증 과정

SSH 통신에는 대칭키 암호화, 비대칭키 암호화 방식이 모두 사용된다.

  1. 클라이언트는 서버가 올바른 서버인지 검증한다.
  2. 클라이언트와 서버는 세션 키(대칭키)를 구한다.
  3. 서버는 클라이언트가 올바른 클라이언트인지 검증한다.
  4. 세션 키로 데이터들을 암호화, 복호화를 통해 통신한다.
  5. SSH 통신이 종료되면 세션 키는 만료되어 사용할 수 없다.

클라이언트가 서버에 접속할 수 있는 세팅은 다 되어 있다고 가정하자. 예를 들어 보안 설정이라든지 서버의 ~/.ssh/authorized_keys에 클라이언트의 개인키에 매칭되는 공개키가 저장되어 있다든지.. 그리고 서버는 sshd라는 데몬 프로세스를 실행한다. sshd을 구동했을 때 기본적으로 포트 번호가 22/TCP로, 클라이언트의 접속을 기다리게 된다.

1. 서버 검증 (클라이언트 관점)

서버는 클라이언트에게 서버의 /etc/ssh에 있는 서버의 공개키를 전송한다.
클라이언트가 해당 서버에 최초 접속을 할 때 “Are you sure you want to continue connecting (yes/no/[fingerprint])?” 이라는 문구를 볼 수 있다. 여기서 yes를 입력한다면 서버의 공개키가 클라이언트의 ~/.ssh/known_hosts 파일에 등록된다.
클라이언트의 ~/.ssh/known_hosts 파일에 등록되어 있는 해당 서버의 공개키를 사용해 아래 과정을 거친다.

  1. 클라이언트에서 난수를 생성하고, 난수를 Hashing으로 해시값을 생성하고 저장한다.
  2. 클라이언트는 서버의 공개키로 난수를 암호화하고, 이것을 서버에 전송한다.
  3. 서버는 서버의 개인키로 데이터를 복호화해서 난수를 추출한다.
  4. 서버에서 추출한 난수를 Hashing으로 해시값을 생성하고, 이것을 클라이언트에게 전송한다.
  5. 클라이언트에 저장되어 있는 난수 해시값과 서버로부터 받은 난수 해시값을 비교한다.
  6. 난수 해시값이 서로 동일하면 해당 서버는 인증되었다.

2. 세션 키(대칭키) 교환

SSH에서 사용하는 대표적인 키교환 알고리즘인 Diffie-Hellman 알고리즘은 상대방의 공개키와 나의 개인키를 통해 비밀키(대칭키)를 얻는 방법이다.

참고
대칭키 교환에 사용되는 키 페어는 임시적이며, 서버와 클라이언트 인증에 사용되는 SSH 키 페어와 다르다. 키 교환 중에 탈취 당했다고 해도 키 교환 중에만 사용하고 폐기되기 때문에 위험 부담이 덜하다. 이러한 속성을 순방향 비밀성이라고 한다.

Diffie-Hellman 알고리즘에서 사용할 Generator를 생성한다. 설명하기 편하게 연산을 G로 표현하겠다.

  1. 클라이언트는 개인키 a를 생성하고, Generator를 통해 공개키 A(A = G(a))를 생성한다.
  2. 서버는 개인키 b를 생성하여 공개키 B(B = G(b))를 생성한다.
  3. 클라이언트와 서버는 서로 공개키 A, B를 공유한다.
  4. 클라이언트와 서버는 각자 자신의 개인키와 서로의 공개키를 가지고 있다.
    • 클라이언트: 클라이언트의 개인키 a, 서버의 공개키 B
    • 서버: 서버의 개인키 b, 클라이언트의 공개키 A
  5. 이것을 가지고 클라이언트는 비밀키 S(S = B(a)), 서버는 비밀키 S(S = A(b))를 구할 수 있다.

Diffie-Hellman 알고리즘을 이용하면 B(a)와 A(b)는 같다. 따라서 클라이언트와 서버는 같은 비밀키(대칭키)를 구할 수 있다. 이를 세션 키라고 한다.

참고
https://blog.seulgi.kim/2018/02/diffie-hellman-key-exchange.html
https://www.crocus.co.kr/1233

3. 클라이언트 검증 (서버 관점)

비밀번호 인증 방식과 공개키 인증 방식이 있다.
간단하게 비밀번호를 이용해 인증할 수 있다. 사용자가 입력한 비밀번호를 세션 키로 암호화하여 서버에게 보내고, 서버가 이를 검증하면 끝이기 때문이다. 하지만 전송되는 비밀번호가 암호화가 된다고 해도 이 방식은 권장되지 않는다. 왜냐하면 자동화된 스크립트를 이용하면 적절한 길이의 비밀번호는 쉽게 구할 수 있기 때문이다.

권장되는 방법은 SSH 공개키 인증 방식을 사용하는 것이다.
공개키(비대칭키) 인증 방식을 사용하기 위해서는 클라이언트에서 키 페어(공개키와 개인키)를 생성한 후, 해당 공개키를 서버의 ~/.ssh/authorized_keys 파일 안에 등록해야 한다. AWS EC2를 이용한다면 이 과정은 알아서 AWS가 해준다.

  1. 클라이언트는 서버에 접속하기 위해 사용할 클라이언트의 개인키에 매칭 되는 공개키가 ~/.ssh/authorized_keys 파일 안에 등록되어 있는지 확인한다.
  2. 서버에 클라이언트의 공개키가 등록되어 있다면 서버에서 난수를 생성한다.
  3. 클라이언트의 공개키로 난수를 암호화하고, 이것을 클라이언트에 전송한다.
  4. 클라이언트에서 클라이언트의 개인키로 데이터를 복호화해서 난수를 추출한다.
  5. 클라이언트에서 추출한 난수와 세션 키를 결합하여 Hashing으로 해시값을 생성하고, 이것을 서버에게 전송한다.
  6. 서버에 저장되어 있는 난수와 세션 키를 결합하여 Hashing으로 해시값을 구해 클라이언트로부터 받은 해시값과 비교한다.
  7. 해시값이 서로 동일하면 해당 클라이언트는 인증되었다.

3. 세션 키로 주고 받을 데이터를 암호화, 복호화

클라이언트와 서버는 세션 키(대칭키)로 주고 받을 데이터들을 암호화, 복호화하여 통신을 할 수 있다.

4. SSH 통신 종료

SSH 통신이 종료되면 세션 키는 만료되어 사용할 수 없게 된다.

참고
https://limvo.tistory.com/21
https://it-eldorado.tistory.com/157
https://medium.com/@labcloud/ssh-%EC%95%94%ED%98%B8%ED%99%94-%EC%9B%90%EB%A6%AC-%EB%B0%8F-aws-ssh-%EC%A0%91%EC%86%8D-%EC%8B%A4%EC%8A%B5-33a08fa76596
https://www.digitalocean.com/community/tutorials/understanding-the-ssh-encryption-and-connection-process