Slack Secret Signature 원리 및 코드 작성법(with Golang)

임태빈·2022년 6월 16일
0

slack

목록 보기
2/3

개요

Slack을 interactive 하게 사용하기 위해서는 서버가 필요합니다. 그래서 slack과 서버를 연동하여 사용하게되면 서버에 ip혹은 dns를 할당하여 slack에 설정해주어야 합니다.
문제는 slack에서 발생하는 hook에 관련해서 ip range를 확인할 수 없기에 클라우드 서비스 방화벽을 활용할 수가 없다는 점입니다.
이렇게 되면 서버 ip나 dns를 공개하게 되기 때문에 아무나 해당 ip에 api를 요청하게 되면 개인정보가 드러날 수 있습니다.
이런 문제를 해결하기 위해서 slack에서는 Hmac 알고리즘을 활용해 slack에서 보내는 문자인지 확인할 수 있게 해주고 있습니다.
slack이 어떻게 식별할 수 있도록하는지에 대해 문서를 작성해보겠습니다.

HMAC 알고리즘에 대하여

송수신자 간의 메시지를 주고받을 때, 메시지가 변조되었는지를 확인할 필요가 있습니다. 원본 메시지와, 전달된 메시지를 비교하여 변조 여부를 확인하는 방식이 MAC(Message Authentication Code)입니다.

HMAC(Hash based Message Authentication Code)은 RFC2104로 발표된 MAC 기술의 일종으로, 원본 메시지가 변하면 그 해시값도 변하는 해싱(Hashing)의 특징을 활용하여 메시지의 변조 여부를 확인(인증) 하여 무결성과 기밀성을 제공하는 기술입니다.

일반MAC과 HMAC은 해싱 알고리즘이 적용된 해싱 함수를 사용됩니다. HMAC이 일반 MAC과 다른점은 HMAC은 해시 암호 키를 송신자와 수신자가 미리 나눠가지고 이를 사용한다는 부분입니다.

HMAC은 송수신 자만 공유하고 있는 키와 원본 메시지를 혼합하여 해시값을 만들고 이를 비교하는 방식을 사용합니다.

HMAC = Hash(Message, Key) + Message
※ Hash( ) 함수는 SHA1, SHA2, MD5 등의 알고리즘 사용.

해시 함수에 사용하는 해시 알고리즘에 MD5, SHA 등을 사용하며, 사용 알고리즘에 따라 HMAC-MD5, HMAC-SHA1, HMAC-SHA2-256 등으로 나누어 집니다.

HMAC을 사용하기 위해서는 송신자와 수신자 사이에 암호화 채널을 사용하여 해싱에 사용할 키(key)를 공유해야 합니다.
키를 공유했다면 다음과 방법으로 인증이 됩니다.

  1. 송신자는 key를 사용하여 원본 메시지를 해싱합니다. 해싱된 메시지가 MAC입니다.
  2. 송신자는 원본 메시지와 원본 메시지를 해싱한 MAC을 수신자에 전달합니다.
  3. 수신자는 key를 사용하여 원본 메시지를 해싱하고, 송신자에게 받은 MAC과 비교합니다.
  4. 비교한 값이 동일하다면, 원본 메시지는 변조되지 않았고 신뢰할 수 있는 값으로 판단합니다.
  5. 만약 해커가 메시지를 변조했다면, 수신자의 MAC과 송신자의 MAC 값이 다른 것을 확인할 수 있습니다.

Slack Hmac 사용방법

  • Slack에서 제공해주는 방법에 경우 이 링크에서 확인할 수 있습니다.

  • HMAC SHA256방식을 사용하고 있으면 사용방법은 다음과 같습니다.
    Slack Bot을 만들었을 때 발급받은 Signing Secret Code를 확인해줍니다.

  • slack에서 서버에 보낼때 주는 헤더 값 중 하나인 X-Slack-Request-Timestamp을 현재 시간차이를 비교해줍니다. 이때 차이가 5분이 나면 안됩니다.버전 넘버인 v0, timestamp 마지막으로 slack에서 보내줄 때 나온 request body값을 하나의 문자열로 생성해줍니다.
    'v0:' + timestamp + ':' + request_body ← 파이썬 식

  • 만들어진 메시지의 앞단에 'v=0'를 붙여주고 hmac의sha256방식으로 변환해줍니다.

  • 변환해준 값과 slack header에 있는 X-Slack-Signature값과 비교해줍니다. true이면 성공 false이면 실패입니다.

Golang으로 작성해보기

func VerifyRequest(c *gin.Context, SlackSigningSecretByte []byte) bool {

	var bodyBytes []byte
    
    //slack에서 보내준 원본 body(payload) 추출
	if c.Request.Body != nil {
		bodyBytes, _ = ioutil.ReadAll(c.Request.Body)
	}
	c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))

	bString := string(bodyBytes)
    //slack 에서 보내주는 timestamp 추출
	ts := c.GetHeader("X-Slack-Request-Timestamp")
	// slack에서 미리 연산된 signature 추출
	eh := c.GetHeader("X-Slack-Signature")
    
    // timestamp 유효 시간 체크
	tsInt64, err := strconv.ParseInt(ts, 10, 64)

	if err != nil {
		log.Println(err)
		return false
	}

	if Abs(time.Now().Unix()-tsInt64) > 60*5 {
		return false
	}
    //메시지 식별하기
	return Verify(SlackSigningSecretByte, ts, bString, eh)
}

//원본데이터와타임스탬프를 연결하여 메시지 생성
func Verify(secretByte []byte, ts string, bString string, eh string) bool {
	message := "v0:" + ts + ":" + bString
	return checkMAC(message, eh, secretByte)
}

//mac을 확인하는 부분
func checkMAC(message string, eh string, secretByte []byte) bool {
    //hmac 생성
	mac := hmac.New(sha256.New, secretByte)
	if _, err := mac.Write([]byte(message)); err != nil {
		fmt.Println(err)
		return false
	}
	//원본으로 만든것과 slack을 통해 들어온 데이터 비교
	calculatedMac := "v0=" + hex.EncodeToString(mac.Sum(nil))
	return hmac.Equal([]byte(calculatedMac), []byte(eh))
}
profile
golang과 서버 개발을 하고 있는 개발자입니다.

0개의 댓글