Amazon S3 REST API로 살펴보는 HMAC 인증

개요

  • HMACAPI 서버에 요청자의 신원과 요청 메시지의 무결성을 검증하는데 특화된 해쉬 문자열이다. 일종의 대칭키 암호화에 해당하는데 PKI에 비해 구현의 부담이 적어 널리 사용되고 있다. 이번 글에서는 Amazon S3 REST API를 예로 들어 HMAC을 이용한 클라이언트 인증 방식을 살펴보고자 한다.

HMAC 인증 방법

  • Amazon S3 REST API는 서버와 클라이언트 만이 공유하는 비밀키(Shared Secret, 대칭키)를 이용하여 생성된 서명(Signature)으로 매번 클라이언트로부터의 요청이 올바른지 인증한다.

  • 클라이언트와 서버 사이의 요청-응답 메시지에 대한 보안은 TLS가 적용된 HTTPS 프로토콜을 사용하는 것으로 자연스럽게 수행된다. Signature는 단지 요청한 클라이언트가 진짜인지 가짜인지를 증명하는 역할을 수행한다.

  • HMAC 서명을 생성하는 알고리즘으로는 SHA256이 사용된다. 업계 표준으로 쓰이고 있어 신뢰할 수 있으며 CPU에 가해지는 부담이 적다.

  • 생성된 서명이 포함된 요청의 유효 시간은 15분이다. 15분이 경과하면 유효하지 않은 요청으로 간주한다. 이 경우 timestamp의 포맷은 ISO 8601에 타임존을 추가하여 정의한다.

HMAC 인증 절차

  • 클라이언트와 서버는 서로만 아는 비밀키를 공유한다. 이 키는 클라이언트가 원할 경우 언제든지 재발급 받을 수 있으며 절대 외부에 공개되어서는 안된다.

  • 클라이언트는 서버에 대한 모든 요청시 둘 간에 미리 공유된 비밀키(Shared Secret)와 계산 공식에 의해 생성된 서명(Signature)을 헤더에 첨부한다. Amazon S3 REST APIAuthorization 요청 헤더에 서명을 담아 전송하도록 명시하고 있다.

  • 서버는 요청 헤더로부터 서명을 확인하여 요청이 올바른 클라이언트로부터 왔는가를 확인하여 처리 여부를 결정한다.

HMAC 서명의 생성과 검증

  • 서명을 생성하는데 필요한 것은 요청 자체에 담긴 여러 데이터와 비밀 키이다. 클라이언트는 미리 서버와 약속된 공식에 따라 데이터를 가공한 후 비밀 키를 이용하여 HMAC 서명 문자열로 변환한다. 서명 문자열을 만들어내는 방법은 다양하다. DomainTools의 경우 client_id + timestamp + request_uri를 조합한 문자열에 client_secret를 이용한 HMAC을 생성한다. [관련 링크]

  • 서버 측은 요청 데이터에 대해 동일한 방법을 이용하여 HMAC 서명 문자열을 생성하는데 클라이언트 측이 첨부한 것과 일치하면 유효한 요청으로 간주하고 불일치할 경우 비정상적인 요청으로 간주한다.

HMAC 인증 장점

  • 단순히 client_id, client_secret을 전송하여 인증하는 방식보다 보안이 우수하다. 공격자가 클라이언트로 위장하려면 비밀키를 알아야 하고, 비밀키를 알았다고 하더라도 계산 공식까지 알아야 한다.

  • PKI 대비 복잡성이 적어 최소한의 비용으로 인증 보안을 유지할 수 있다.

HMAC 인증 단점

  • 서버와 클라이언트 간에 비밀키를 유출되지 않고 공유하는 방법을 고민해야 한다.

  • 단순히 client_id, client_secret을 사용하는 인증 방식에 비해 구현자의 부담이 증가한다.

Java에서의 HMAC 시그너쳐 생성 예

String sharedSecret = "5pKRnC5MGNuqEdKkzYy4MA";
String algorithm = "HmacSHA256";
String message = "2017-11-07T18:00:00+09:00 GET http://jsonobject.com/posts/2017";

// HMAC 생성기 초기화, 공유키, 알고리즘, 원본 메시지 지정
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(sharedSecret.getBytes(), algorithm));

// Signature 바이트 배열 생성
byte[] signatureBytes = mac.doFinal(message.getBytes()));

// 생성된 Signature를 Base64 문자열로 변환, UMuelgDclhzNZPiNqF6NYkZtJnOFqlgu4i4t+4M1fJs=
String signature = Base64.getEncoder().encodeToString(signatureBytes);

참고 글

저작자 표시 비영리 동일 조건 변경 허락
신고