Google Authenticator(OTP)를 이용한 SSH 2차 인증

개요

일반적으로 SSH 인증 방벙들 중 사용자로부터 패스워드를 입력받는 방법인 password authentication을 많이 사용한다. 가장 심플하며 사용하기도 쉽고, 간편한 방법이다. 하지만 보안상 안전한 방법은 아니다. 패스워드가 간단한 규칙이거나 복잡하지 않을수록 brute force attack(무작위 대입 공격)에 취약해진다. 또 패스워드를 직접 입력해야만 하는 방식인 만큼, 이른바 shoulder surfing 등의 사용자 부주의로 인한 패스워드 노출에도 신경 써야 할것이다.

SSH key authentication은 보다 안전한 방법이다. client의 허락된 ssh public key를 server에 미리 넣어둠으로 패스워드 입력없이 서버에 접근 할수 있다. 사용자 입력이 없으니 key logger나 위에서 언급한 shouler surfing 등에서 자유롭고, 사용자는 패스워드를 기억할 필요도 없으니 어딘가에 적어둘 필요도 없어 노출될 가능성도 매우 적다. 그러나 이 방법 역시 단점이 없는것은 아니다. 많은 서버들로 구성된 대규모 환경에서 사용자들의 public key(authorized_keys) 관리는 예상보다 훨씬 간단하지 않다. 또한 어떻게든 사용자의 private key가 유출된다면 아무 의미없는 인증이 되어 버린다.

보다 나은 여러 방법이 있겠지만, 이 문서는 Google Authenticator(https://github.com/google/google-authenticator)를 이용하여 OTP(one-time password) 2차 인증에 대해 기술할 것이다. 위의 인증방법과 더불어 하나의 인증이 추가되는 2차 인증(Two-Factor Authentication: 2FA)이니만큼, 보안적으론 더 안전할수밖에 없다. 그러나 언제나 그렇듯 보안과 편의는 반비례 관계이다. 로그인할때마다 OTP를 확인하고 입력하는 추가작업은 생각보다 불편할 것이다. 하지만 비교적 높은 수준의 보안을 간단하게 구성할 수 있으니, 여러 인증방법중 하나로 충분히 고려해볼만 할것이다.

OTP

One Time Password. 말그대로 일회성 비밀번호이다. 아마 대부분은 온라인 뱅킹등에서 이미 경험했을것이라 추측된다. 일반적으로 인증에 사용되는 값들은 고정되어 있어 유출될 가능성을 항상 염두해야한다. 하지만 OTP의 경우 그 값이 동적으로 바뀌게되므로 유출시도 안전한 방법으로 볼수 있다.

여러 OTP 중에서 google authenticator를 선택했다. 일단 Opensource Project(Apache License 2)이고, 현재 가장 널리 사용중인 OTP이다. 대부분의 모바일 OS(ios, android, windows등)에서 모두 지원하고, 서버 패키지 역시 대부분의 OS(debian, redhat)가 지원했다. 다른 대체가능한 것으로는 FreeOTP(https://freeotp.github.io/)가 있고, 상용제품으로 authy(https://authy.com/)가 많이 사용되는듯 보인다.

설치

위에서 언급했던대로 debian의 공식 repository에 packages가 등록되어있다. (jessie이상) 따라서 debian, ubuntu등에서는 아래의 명령으로 쉽게 설치가 가능하다.

# apt-get install libpam-google-authenticator

pam모듈과 함께 간단한 cli둘도 설치가 된다. rhel계열이라면 아래의 package명이다.

# yum install google-authenticator

google-authenticator 설정

google-authenticator는 OS 사용자마다 다른 설정을 할수 있다. 설정에 대한 정보는 $HOME/.google-authenticator 파일로 저장되며, google-authenicator라는 명령으로 생성할수 있다.

$ google-authenticator

google-authenticator를 실행하면 설정에 대한 몇가지 질문이 interactive하게 진행된다.

Do you want authentication tokens to be time-based (y/n)

google-authenticator는 2가지 인증 방법을 지원한다. TOTP(time-based)와 HOTP(HMAC-based; Hash-based Message Authentication Code)이다. 이는 인증에 대한 알고리즘인데 시간을 기반으로 할것인지, Event(google-authenticator는 counter를 쓴다)를 기반으로 할것인지를 선택한다.

이 OTP인증에서 서버와 클라이언트(핸드폰의 OTP앱 등)간에 서로 어떠한 정보도 주고 받지 않는다. 동일한 인자값(input)으로 동일한 알고리즘을 사용하여, 결과값인 token이 서로 같은지/다른지만 판단하여 인증을 처리한다. TOTP의 경우 Secret Key와 함께 시간을 인자값으로 사용한다. 동일한 secret key와 시간이라면 같은 token의 결과가 나오게 되고, 이 인증이 통과되는것이다. 따라서 TOTP는 ntp등의 시간 동기화가 매우 중요하다. 시간이 다를 경우 token이 달라지므로 인증이 실패하게 된다. (물론 이 시간차를 대비한 설정도 아래에서 진행한다.)

HOTP는 secret key와 함께 counter를 사용하여 인증한다. 이 counter는 1부터 시작하며 인증이 성공하거나 클라이언트의 증가 요청(Reload 버튼)있을 경우 1씩 증가한다. 따라서 30초마다 token이 변경되는 TOTP와 다르게, HOTP는 EVENT(요청)가 없다면 그 token이 변경되지 않는다. 그래서 HOTP보단 TOTP가 보안상 더 안전하다고 한다.

Your new secret key is: IQM4TUAPUVLGUFM7
Your verification code is 424471
Your emergency scratch codes are:
  68300423
  73586279
  29748570
  20970148
  42616217

그럼 바로 위와 같은 형태로 몇개의 Key들이 생성되고 표시된다. (더불어 QR Code가 Console상에 뿌려지는데 클라이언트에서 이 QR Code를 찍게되면 자동으로 설정이 진행된다.) secret key는 시간과 함께 알고리즘의 인자값으로 사용될 중요한 key다. 노출을 조심하도록 하자. 뒤이어 나온 verification code는 token값이다. otp라면 30초마다 한번씩 변경될것이다. 마지막으로 emergency scratch code 5개가 출력되는데 이것은 말그대로 emergency에 사용된다. 시간과 상관없이 위 code를 입력하면 인증이 성공된다. 한번 인증이 진행된 code는 더이상 사용될수 없게 지워진다.

Do you want me to update your "$HOME/.google_authenticator" file (y/n)

설정 파일을 업데이트 할지를 결정한다. n를 선택한다면 바로 종료된다.

Do you want to disallow multiple uses of the same authentication
token? This restricts you to one login about every 30s, but it increases
your chances to notice or even prevent man-in-the-middle attacks (y/n)

30초마다 생성된 동일한 token의 중복 인증 허용여부를 설정한다. 즉 한번 인증이 성공된다면 동일한 token으로는 인증이 실패하게된다. 30초가 지나 새로운 token이 생성되어야만 인증이 성공하게 된다. 당연히 yes로 진행해야 보안상 유리할것이다.

By default, tokens are good for 30 seconds and in order to compensate for
possible time-skew between the client and the server, we allow an extra
token before and after the current time. If you experience problems with poor
time synchronization, you can increase the window from its default
size of 1:30min to about 4min. Do you want to do so (y/n)

TOTP의 경우 서버와 클라이언트의 시간이 틀어지게되면 인증이 실패하게 된다. 이를 대비하여 현재시간 앞뒤로 여유시간을 둬서 유효기간을 늘릴수 있다.

OTP 인증에서 window size라는 값으로 설정할수 있는데, default window size는 3이다. 이 여유 폭을 늘려 4분 차이까지 유효 인증을 처리할지, 즉 17로 변경하겠느냐는 설정이다. TOTP에서 window size 1은 30초이다. 현재시간을 기점으로 앞뒤로 “8(앞) + 1(현재) + 8(뒤)”의 여유 인증시간, 4분이 된다. 반면 HOTP는 window size 1는 count 1이 된다. 서버는 마지막으로 인증이 성공한 counter를 저장하고 그 이후 window size만큼 counter를 증가하면서 token값이 일치 유무를 확인하게 된다. 이 window size가 큰 만큼 보안상 좋을리는 없다.

If the computer that you are logging into isn't hardened against brute-force
login attempts, you can enable rate-limiting for the authentication module.
By default, this limits attackers to no more than 3 login attempts every 30s.
Do you want to enable rate-limiting (y/n) y

무작위 대입 공격(brute-foce)를 대비하여 30초동안 3번 이상의 로그인 시도를 제한할수 있다. 물론 enable하는것이 보안상 유리하다고 할 수 있겠다.

위 질문들이 모두 끝나면 ~/.google_authenticator 파일이 생성되어 있을것이다. 해당 파일의 permission은 400이다. 열어보면 위에서 생성된 secret와 설정들이 모두 저장됨을 확인할수 있을것이다.

IQM4TUAPUVLGUFM7
" RATE_LIMIT 3 30
" WINDOW_SIZE 17
" DISALLOW_REUSE
" TOTP_AUTH
68300423
73586279
29748570
20970148
42616217

첫번째 라인이 Secret key이고 뒤이어 “로 시작되는 OTP의 설정이 있다. 각 설정의 의미는 위에서 언급해 모두 추측할수 있을것이다. 마지막 5라인은 emergency scratch codes이다. 코드가 사용되면 설정파일에서 해당 코드가 삭제된다.

만약 HOTP로 진행했다면 설정이 약간 다를것이다.

" RATE_LIMIT 3 30
" WINDOW_SIZE 17
" HOTP_COUNTER 1

TOTP_AUTH 대신 HOTP_COUNTER가 설정되어 있음을 알수 있다. 초기값인 1이 기록되어 있고, 이후엔 마지막으로 인증성공에 사용된 counter가 기록될것이다.

위 과정을 진행하면서 눈치챌 수도 있었겠지만, TOTP는 위 설정파일을 여러 서버에 적용할수 있다. 서버 시간이 동기화되어 있다면 해당 파일을 복사하여, 하나의 client 설정으로 여러 서버들의 인증을 진행할 수 있을것이다.

pam 설정

사용하고자하는 서비스에 google_authenticator pam module을 추가한다. 우리는 ssh에 적용할것이므로 /etc/pam.d/sshd에 아래 라인을 추가하면 된다. 원한다면 ssh가 아닌 su, sudo, passwd등과 같은 명령에도 opt를 적용할수 있다.

# vi /etc/pam.d/sshd
auth required pam_google_authenticator.so

이 pam_google_authenticator이 위에서 생성한 ~/.google-authenticator를 읽어들여 인증을 진행한다. 만약 해당 파일이 없다면 당연히 인증은 실패하게 된다. pam_google_authenticator의 man page를 보면 ssh에 적용시 sshd_config에 ChallengeResponseAuthentication yes를 추가하라고 나와있다. 해당 설정이 없을 경우 default는 yes지만, 배포판의 기본 설정은 다를수 있다. 확인하고 추가하도록 하자.

# vi /etc/ssh/sshd_config
ChallengeResponseAuthentication yes

변경했다면 당연히 sshd를 restart 해야할것이다.

# systemctl restart sshd

이후 ssh로 서버에 접근하면 아래와 같이 패스워드 인증과 함께 2차 OTP 인증을 진행하게 될것이다.

# ssh 192.168.68.100 -luser
Password:
Verification code:

Client 설치

mobile os의 app market에서 “google opt”로 검색하면 client 앱을 찾을수 있을것이다. OTP 서버 추가도 간편하다. 위 설정을 진행하면서 출력된 QR CODE를 찍어도 되고, 아니면 직접 secret key를 입력하면 된다. 등록시 사용자명을 입력받는데 이는 OTP생성시 진행한 os 계정명일 필요는 없다. 구분를 위한 사용자 편의일뿐 인증에는 사용되지 않는다.

설정이 완료되면 해당 OTP에 token이 표시될것이다. TOTP라면 30초마다 리셋될것이고, HOTP라면 Count 증가를 위한 버튼이 나타난다. 이제 표시된 token으로 인증을 진행하면 된다.