SSO를 위한 Secret + Payload 해싱

공식 SSO 가이드를 따르려고 하고 있습니다. 서버에서는 Golang을 사용 중이며, payload + secret의 HMAC-SHA256 해시를 sig와 일치시키려 하고 있지만 값이 다릅니다. 해시를 어떻게 처리하고 계신가요? payload를 서명되지 않은 원본(raw) 콘텐츠로 분리한 후 다시 해시하는 방식인가요?

당신의 코드는 어떻게 되어 있나요?

페이로드를 base64 인코딩했나요?

그리고 매개변수 순서를 올바르게 설정했나요? 언어/라이브러리마다 매개변수 순서가 (키, 메시지) 또는 (메시지, 키)로 달라집니다. 매번 저를 혼란스럽게 만드는 것이 바로 이 부분입니다.

payload를 파싱한 결과 기본적으로 base64 형식인 것 같지만, base64 인코딩된 payload의 256SHA 해시가 올바르지 않습니다. OG 게시글의 예시 secret과 URL을 사용하고 있습니다.

다음 코드에서 다음 출력이 나옵니다:

 payloadURL = bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGI=

sig = 2828aa29899722b35a2f191d34ef9b3ce695e0e6eeec47deb46d588d70c7cb56
payloadRaw = nonce=cb68251eefb5211e58c00ff1395f0c0b
payload256 = KCiqKYmXIrNaLxkdNO+bPOaV4Obu7EfetG1YjXDHy1Y=

제 코드:

testURL := "http://www.example.com/discourse/sso?sso=bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGI%3D%0A&sig=2828aa29899722b35a2f191d34ef9b3ce695e0e6eeec47deb46d588d70c7cb56"	
	ssoSecret := "d836444a9e4084d5b224a60c208dce14"

	// URL 쿼리에서 payload와 sig를 가져옵니다
	u, err := url.Parse(testURL)
	if err != nil {
		log.Fatal(err)
	}

	q := u.Query()
	payloadURL := strings.Trim(fmt.Sprint(q["sso"]), "=[]")
	sig := strings.Trim(fmt.Sprint(q["sig"]), "[]")
	
	fmt.Printf("payloadURL = %s\n", payloadURL)
	fmt.Printf("sig = %s\n", sig)

	// base64 버전에서 raw payload를 가져옵니다
	payloadRaw, err := base64.StdEncoding.DecodeString(payloadURL)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("payloadRaw = %s\n", payloadRaw)
	
	key := []byte(ssoSecret)
	message := []byte(payloadURL)
	hash := hmac.New(sha256.New, key)
	hash.Write(message)
	payload256 := base64.StdEncoding.EncodeToString(hash.Sum(nil))
	if err != nil {
		log.Fatal(err)
	}
	
	fmt.Printf("payload256 = %s\n", payload256)

	// sig는 hex 인코딩되어 있으므로, 비교할 수 있도록 해시된 payload를 hex 인코딩합니다.
	//hashAsString := hex.EncodeToString(hash.Sum(nil))

	// payload의 hex 인코딩된 해시를 hex 인코딩된 sig와 비교합니다
	if strings.Compare(payload256, sig) != 0 {
		log.Fatal(err)
	}

업데이트: 결과로 생성된 HMAC-SHA256 해시를 hex로 인코딩한 후 이 문제를 수정했습니다. (서명도 hex 인코딩되어 있습니다. 또한 SSO 가이드의 예시가 올바르지 않다는 내용을 언급한 이 게시글도 참고하세요.) 따라서 이 코드를 변경했습니다:

key := []byte(ssoSecret)
message := []byte(payloadURL)
hash := hmac.New(sha256.New, key)
hash.Write(message)
payload256 := hex.EncodeToString(hash.Sum(nil))
if err != nil {
	log.Fatal(err)
}

payload256은 어디에서 가져온 값인가요?

죄송합니다. 더 깔끔한 코드로 OP를 업데이트했습니다.

&sig 바로 앞의 %0A는 무엇을 하는 건가요? 그것은 URL 인코딩된 개행문자입니다… 그래서 payloadURL을 출력한 후 디버그 출력에 빈 줄이 생기는 것입니다. 여전히 끝에 붙어 있기 때문입니다.

좋은 지적입니다! 여기서는 .Trim() 매개변수에 줄바꿈 문자를 추가하여 문제를 수정했습니다:

payloadURL := strings.Trim(fmt.Sprint(q["sso"]), "[]\n")

이제 제 출력은 다음과 같습니다:

payloadURL = bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGI=
sig = 2828aa29899722b35a2f191d34ef9b3ce695e0e6eeec47deb46d588d70c7cb56
payloadRaw = nonce=cb68251eefb5211e58c00ff1395f0c0b
payload256 = HOFJT5RIS29qCSvpsVzMHNr7H4Rgo4OPuw4Ig8Q5BHE=

줄바꿈이 실제로 어디서 발생하고 있는지 확인하고, 추가되지 않도록 방지해야 합니다.

또한, 페이로드에는 nonce 외에도 더 많은 name=value 쌍이 포함되어야 하는 것 아닌가요?

이것은 Discourse SSO 가이드의 예시일 뿐이라 줄바꿈이 어디서 오는지 알 수 없습니다.Apparently 가이드가 잘못된 것 같습니다(이 게시물에 따르면) 그리고 예시에서 복사/붙여넣기 오류가 있었던 것 같습니다.

아마도 다른 버전들에서 복사한 부분이 조금 많으신 것 같습니다.

하지만 바로 위 게시물에서 확인해 보면, payloadURL이 URL 인코딩되어 있지 않고, payload256은 16진수 인코딩이 아니라 Base64 인코딩되어 있습니다.

현재 코드와 출력을 다시 게시해 주실 수 있을까요?