Language/Java

'HMAC'?? 'MAC'이 뭐에요?

아르비스 2014. 10. 13. 09:17

1. MAC(Message Authentication Code)

메시지 인증 코드(Message Authentication Code, 약칭 MAC)는 메시지의 인증에 쓰이는 작은 크기의 정보이다. 이 MAC을 이용하여, 메시지의 무결성 및 신뢰성을 보장하는데 사용한다. 


MAC의 Algorithm은 인증을 위한 Secret Key와 임의 길이의 Message를 입력 받아 MAC을 출력하는 Keyed Hash Function을 사용한다. 

MAC은 Cryptographic Hash Function과 같은 특성을 가진다. 그 메인 속성은 다음과 같다.

  • Hash value로 계산하기 쉽다.
  • 생성된 Hash를 통해서 Message를 generate 하는 것이 불가능하다.
  • Hash를 수정하지 않고, Message를 수정하는것이 불가능하다.
  • 다른 두 Message가 동일한 Hash를 보내는것이 불가능하다.

사용되는 Hash algorithm은 MD5, SHA-1, SHA-2 등 일반적인 암호화 알고리즘을 그대로 사용할 수 있으며, 사용된 알고리즘에 따라서 고정길이의 Hash value가 생성된다.



MAC을 간단하게 도식화 하면 다음과 같다.



MAC에 사용되는 Key에 따라서, CMAC, HMAC, UMAC, VMAC 등으로 나뉜다.


2. HMAC(Hash-based Message Authentication Code)

HMAC은 Keyed-Hash Message Authentication Code로  Key를 조합하여 Hash 함수를 구하는 방식이다.  



간단히 HMAC을 설명하면, 송신자와 수신자만이 공유하고 있는 Key와 Message를 혼합하여 Hash 값을 만드는 것이다.

 채널을 통해 보낸 메시지가 훼손되었는지 여부를 확인하는데 사용할 수 있다. MAC의 특성상 역산이 불가능 하므로, 수신된 메시지와 Hash 값을 다시 계산하여,계산된 HMAC과 전송된 HMAC이 일치하는지를 확인하는 방식이다.


대칭블록암호에 기반을 둔 MAC 방식으로, 알고리즘에 따라서 HMAC value size가 달라진다. 


HMAC_MD5("", "")    = 0x74e6f7298a9c2d168935f58c001bad88

HMAC_SHA1("", "")   = 0xfbdb1d1b18aa6c08324b7d64b71fb76370690e1d

HMAC_SHA256("", "") =        

                 0xb613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad


아래 그림은 SHA-1 알고리즘을 적용한 예이다.




3. 그 외

- CMAC

  암호 기반 메시지 인증 방식으로, Cipher-based MAC이다. 

  AES와 triple-DES를 이용하는 방법 많이 사용된다.


- UMAC
  Universal Hashing 기반으로 Message 인증코드를 사용하는 방식이다.

  message authentication code based on universal hashing

  Universal Hashing 이란?

 다음의 특성을 가지는 Hash 함수F  를 선택하기 위한 확률적 알고리즘이다. 

  F(x) = F(y)


- VMAC

  블럭 암호 기반 메시지 인증 방식으로, (block cipher-based message authentication code)

  보편적 해시 알고리즘을 사용한다.



위와 같이 MAC은 여러종류가 있다.

그럼 이런 것을 왜 사용하는가?

글쎄... 아래 이유로 사용하지 않을까 싶다.

1. 짧고, 고정 길이 이다.

2. 중복을 방지할 수 있다.

3. 메세지 구조를 숨길 수 있다.

4. 그러면서도 메시지에 대한 유효성 및 인증이 가능하다.


암호화 알고리즘과 동일한 방법을 사용하므로, 알고리즘에 따라서 속도가 많이 달라진다.

속도는 MD5가 빨라서 많이 사용했으나, 암호화 결함이 발견되어 사용이 줄어들었다.

보안관련 용도는 SHA-256을 권장하지만, 아직까지 한국에서는 SHA-1을 많이 사용한다.


구현 예시

// input으로 const char* key와 const int key_len이 들어오겠죠..

#define HASHED_OUTPUT 20 ;  // 해시된 결과의 길이입니다. SHA-1을 사용한다면 20이겠죠?

#define DATA_BUFFLEN  1024 ;  

int input_blocksize = 64 ;  

unsigned char Ki[HASH_BLOCK_SIZE] = {0, } ;  // K0 ^ ipad

unsigned char Ko[HASH_BLOCK_SIZE] = {0, } ; // K0 ^ opad

char data[DATA_BUFFLEN] = {0, } ;  // 중간 계산값을 저장하기 위한 버퍼입니다.

char hashed_data[HASHED_OUTPUT + 1] = {0, } ;  // 마찬가지로 해시된 값을 저장하기 위한 버퍼입니다.


// 키 길이가 B보다 크면 해시합니다.

// 키 길이는 물론 해시 함수의 출력길이가 되겠죠.

if(key_len > input_blocksize)

{

    Ki = Hash(key) ;

    key_len = HASHED_OUTPUT ;

}

else

    memcpy(Ki, key, key_len) ;


memcpy(Ko, Ki, key_len) ;


// 이후 B만큼 나머지 길이를 0으로 채웁니다.

for(i=key_len ; i<input_blocksize ; i++)

{

    Ki[i] = 0x00 ;

    Ko[i] = 0x00 ;

}


// ipad와 opad를 이용하여 K0를 미리 계산합니다.

for(i=0 ; i<input_blocksize ; i++)

{

    Ki[i] ^= 0x36 ;

    Ko[i] ^= 0x5C ;

}


// 위에서 계산한 Ki ^ ipad 와 HMAC의 대상인 text를 연접합니다.

memcpy(data, Ki, input_blocksize) ;

memcpy(data + input_blocksize, text, text_size) ;


// 해시합니다.

hashed_data = Hash(data) ;


// Ko ^ opad와 위의 해시한 결과를 다시 연접합니다.

memset(data, 0x00, DATA_BUFFLEN) ;

memcpy(data, Ko, input_blocksize) ;

memcpy(data + input_blocksize, hashed_data, HASHED_OUTPUT) ;


// 위의 값을 다시 해시합니다.

hashed_data = Hash(data) ;


기본 알고리즘은 위와 같지만, 실제 사용은 더 쉽다.

C, C++, Java, JavaScript, Python, Perl, Ruby 등 여러가지 Language에서 기본적으로 HMAC을 제공해주고 있다.

자 그럼 simple하게 Java를 이용해서 HMAC을 구현해 보자.


public class Signature {

private static final Logger logger = LoggerFactory.getLogger(Signature.class);

private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";

public static String doHMAC(String data, String key) {

String result = null;

// get an hmac_sha1 key from the raw key bytes

SecretKeySpec signingKey = 

                        new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM);


// get an hmac_sha1 Mac instance and initialize with the signing key

Mac mac;

try {

mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);

mac.init(signingKey);


// compute the hmac on input data bytes

byte[] rawHmac = mac.doFinal(data.getBytes());

// base64-encode the hmac

result = encodeBase64(rawHmac);

} catch (NoSuchAlgorithmException e) {

logger.error("NoSuchAlgorithmException ", e);

} catch (InvalidKeyException e) {

logger.error("InvalidKeyException ",e);

}

return result;

}


/**

 * @author Juseok

 * @see Signature.encodeBase64

 * @description :

 * @param rawHmac

 * @return

 */

public static String encodeBase64(byte[] rawData) {

String result;

byte[] resultArray = Base64.encodeBase64(rawData);

result = new String(resultArray);

return result;

}

}

그럼 끝~~