Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

계정 보안 업데이트: 2단계 인증(2FA) 도입과 TOTP 작동 원리 완벽 가이드

비싼 인터넷 요금을 버리고 이웃집의 보안이 허술한 Wi-Fi를 몰래 쓰고 싶었던 적 있으신가요? 그런데 유일하게 발목을 잡은 것이 바로 Honeybadger의 단일 인증 방식이었다면? 좋은 소식이 있습니다.

2단계 인증(Two-Factor Authentication, 2FA) 출시

농담은 여기까지 하고, 진지하게 말씀드리자면 저희는 보안을 매우 중요하게 생각합니다. 사용자와 데이터를 더욱 안전하게 지킬 수 있는 중요한 도구를 제공하게 되어 기쁩니다.

그냥 2FA 활성화 방법만 설명할 수도 있지만, 그게 어디 재미있겠습니까? 가볍고 유쾌하게 암호학의 세계를 살짝 들여다보며 2FA가 어떻게 작동하는지 함께 알아보겠습니다.

재미없는 걸 좋아하신다면 '활성화 방법' 섹션으로 바로 건너뛰셔도 됩니다. 물론 제 감정이 살짝 상하긴 하겠죠.

⚠️ 주의: 아래에 이론이 등장합니다

많은 2FA 구현체는 시간 기반 일회용 비밀번호(Time-Based One-Time Password, TOTP)라는 알고리즘을 기반으로 합니다. 이름부터 혀에 잘 굴러가지 않죠?

일회성(One-Time)이라니?

네, 이 방식의 아름다운 점은 로그인할 때마다 동일한 "비밀번호"를 전송하지 않는다는 것입니다. 인증 앱을 사용하면 일회성 "비밀번호"가 생성되며, 짧은 시간(보통 30초) 동안만 유효합니다. 덕분에 중간자(man-in-the-middle) 공격의 효과가 크게 희석됩니다.

복잡해 보이는 부분도 있지만, 전반적인 구현은 의외로 간단합니다.

공유 비밀키(The Secret)

TOTP에서 이해해야 할 핵심(key point)은 서버(검증자, verifier)와 인증기(증명자, prover)가 반드시 공유 비밀키(shared secret)를 가져야 한다는 점입니다. 이것이 진짜 "비밀번호"인데, 딱 한 번만 전송됩니다(어쩌면 이것도 일회성이라고 할 수 있겠네요!).

이 비밀키를 전달하는 권장 방식은 QR 코드입니다. 스마트폰으로 QR 코드를 스캔하면 비밀키가 다음과 같은 표준 URI 안에 인코딩되어 담깁니다:

otpauth://totp/Honeybadger.io:inigo@honeybadger.io?secret=base32-secret-key

QR 코드를 제공하면(평문 비밀키를 노출하는 대신) 다음과 같은 장점이 있습니다:

  1. 32자리 코드를 손으로 입력할 사람이 누가 있겠습니까?
  2. 인증 앱이 URI에 포함된 이메일과 발급자 정보를 참조해 일회용 비밀번호에 유용한 라벨을 붙여줍니다. 여러 서비스에서 2FA를 사용할 때 특히 유용합니다.
  3. 드디어 QR 코드를 사용할 그럴듯한 이유가 생겼습니다!

otpauth 스킴에는 추가 파라미터들도 있지만(대부분 무시되곤 합니다), 자세한 내용은 관련 문서에서 확인할 수 있습니다.

MAC과 나(MAC and Me)

양측이 비밀키를 공유하고 나면 🤝, 언젠가 일회용 비밀번호를 입력하라고 요청받게 됩니다. 이제부터가 본격적인 시작입니다.

TOTPHMAC 기반 일회용 비밀번호(HMAC-Based One-Time Password, HOTP) 위에 구축되어 있고, HOTP는 다시 해시 기반 메시지 인증 코드(Hash-based Message Authentication Code, HMAC)를 기반으로 합니다. 하나의 거대한 암호학 양파 같죠.

TOTPHOTP는 어떤 메시지를 해싱하느냐의 변형에 불과합니다. 진짜 마법이 일어나는 곳은 HMAC입니다. HMAC은 어떤 값을 해싱 알고리즘에 통과시켜 나온 출력물인 다이제스트(digest)를 생성합니다. 루비 코드로 직접 확인해볼까요?

OpenSSL::HMAC.digest("SHA1", "my-secret-key", "the-earth-is-flat")
=> "R\xABp\xCB\xEC\xFEJ\r#\x02\xC8\xAB\x96\xB68\v\xDA0\xD7z"

오, 이 바이너리 문자열 좀 보세요! 바로 메시지 인증 코드(Message Authentication Code, MAC)입니다.

저희는 해싱 함수로 SHA1을 선택했습니다(TOTP 구현 역시 마찬가지). 선택할 수 있는 함수는 많으며, 각각 고유한 보안·성능 특성이 있습니다. 해싱 알고리즘의 놀라운 점은 어떤 입력이든 매번 동일한 출력을 낸다는 것입니다! 또한 입력이 아주 미세하게 달라져도 완전히 다르고 고유한 출력이 생성됩니다(아헴, 해싱 함수에 따라 다르긴 하지만 여기서는 충돌(collision)까지 깊이 걱정할 필요는 없습니다).

자, 그럼 이것이 메시지 인증 코드라고 불리는 이유는 수신된 메시지가 진본임을 증명하는 데 사용할 수 있기 때문입니다. 당신과 내가 비밀키(my-secret-key)를 알고 있다면, 나는 메시지 the-earth-is-flat과 함께 MAC을 보낼 수 있습니다. 받는 쪽에서는 동일한 입력값으로 똑같은 다이제스트 함수를 실행하기만 하면 되고, 두 MAC이 일치하면 메시지 인증이 완료됩니다! 변조되지 않았고 오직 나에게서 왔음을 확신할 수 있는 것이죠!

😎

타임코드(Timecode)

이제 다시 TOTP로 돌아와 시간에 대해 이야기해볼 차례입니다. TOTP에서는 the-earth-is-flat 같은 엉터리 메시지 대신, 시간 스텝 카운트(문자열로 변환된 값)를 입력으로 사용합니다. 인증 앱을 열고 코드가 바뀌는 순간을 지켜보면 패턴을 발견할 수 있습니다. 그렇습니다, 따라 외쳐보세요: "코드는 정각과 30초마다 바뀐다!" 아주 잘했습니다.

덕분에 메시지 계산이 조금 더 단순해집니다. 이 경우 우리는 시작점(유닉스 시간, unix time)부터 지금까지 흐른 30초 단위의 개수(내림한 값, 즉 타임 스텝)를 사용합니다. 다시 루비로 표현하면:

Time.now.to_i / 30
=> 51772900

어하하! 바로 이것이 메시지(Message)입니다.

이제 인증 앱에서 믿음직한 다이제스트 함수로 새로운 HMAC을 얻습니다:

hmac = OpenSSL::HMAC.digest("SHA1", "avwe8aw71j2boib23jkbjk32", "51772900")
=> "H\x7F\xC1\xACL\xDA\xDB\xE7DQ\x91kE\x1C\xE3,c\nH\xA0"
get_otp_code(hmac)
=> "332204"

마지막 단계는 이 바이너리 문자열에서 숫자 코드를 추출하는 것입니다. 비트 연산이 필요하기 때문에, 지금은 익명의 get_otp_code 함수 뒤에 잠시 숨어 있겠습니다. 원하시면 HOTP 명세(HOTP Spec)에서 작동 방식을 자세히 읽어볼 수 있습니다.

서버에 코드를 제출하면 서버는 정확히 동일한 다이제스트 연산을 수행한 뒤 두 코드를 비교합니다. 일치하면 로그인 성공! 다만 시간을 다루는 만큼 네트워크 지연(latency)이 변수가 됩니다. 가능한 지연을 보정하기 위한 전략들이 존재하는데, 이 부분은 원하신다면 숙제로 남겨두겠습니다.

이것이 바로 시간 기반 일회용 비밀번호의 작동 원리입니다!! 🎉 이 지식으로 다음 모임에서 인기를 끌게 된다면 감사 이메일 한 통 부탁드립니다!

구현(Implementation)

작동 원리를 이해하는 것도 중요하지만, 암호 관련 코드는 절대 직접 만들지 않는 것이 강력히 권장됩니다. Ruby/Rails 생태계의 훌륭한 점 중 하나는 대부분의 문제를 해결해줄 젬(gem)을 찾을 수 있다는 것입니다(경우에 따라 여러 개죠).

저희 Honeybadger는 devise를 사용하며, two_factor_authentication이라는 훌륭한 devise 플러그인이 있습니다. 2FA 구현체는 여러 가지가 있는데, 이것을 선택한 이유는 다음과 같습니다:

  • 설정이 간단합니다(마이그레이션 하나, 설정 파일과 이니셜라이저 몇 줄이면 끝)
  • 유효한 아이디/비밀번호 검증 후 별도 페이지에서 일회용 코드를 입력할 수 있습니다
  • 기본 설정도 무난하면서 커스터마이징도 쉽습니다
  • 명세에서 권장하는 최대 로그인 시도 횟수 제한 기능도 내장되어 있습니다

활성화 방법

설정은 간단합니다: 사용자 설정(User Settings) > 인증(Authentication) 페이지로 이동한 후, "Set Up Two Factor Authentication"(2단계 인증 설정)을 선택하고 안내에 따라 Authy 또는 Google Authenticator(사실상 어떤 TOTP 앱이든 무방)로 비밀키를 동기화하세요.

계정 보안 업데이트: 2단계 인증(2FA) 도입과 TOTP 작동 원리 완벽 가이드

이게 전부입니다! 다음 로그인 때 인증 코드를 입력하라는 안내가 표시됩니다. 코드를 입력하고 완전한 평온 속에서 서비스를 이용하세요. 🧘

계정 보안 업데이트: 2단계 인증(2FA) 도입과 TOTP 작동 원리 완벽 가이드

보안과 간편함을 동시에? 이보다 좋을 수 있을까요?

마지막으로: 유출 비밀번호 검사(Pwned Password Check)

약한 비밀번호 옹호자 여러분께 짧은 소식 하나 전합니다. 저희는 devise-pwned_password 젬도 통합했습니다. 이제부터 비밀번호가 필요한 모든 작업 시, 해당 비밀번호가 알려진 데이터 유출 사건에 등장하는 흔한 비밀번호인지 검사합니다. 새 비밀번호를 설정할 때 검사에 통과하지 못하면 해당 비밀번호는 사용할 수 없습니다.

아직 사용 중이 아니라면 비밀번호 관리자 사용을 강력히 권장합니다(1Password, 아헴).

Pwned Password의 흥미로운 점 중 하나는 제3자 서비스의 데이터베이스와 대조하는 과정에서도 비밀번호가 안전하게 보호된다는 것입니다. 이를 k-익명성(k-Anonymity)이라고 하며, 개요는 관련 자료에서 확인할 수 있습니다.

자, 오늘은 여기까지입니다. 다음에 또 만나요! 👋