마침, 질문하신 분이 계셔서 한번 알아보기로 했습니다. ^^
naver 소켓 로그인
; http://www.sysnet.pe.kr/3/0/986
위의 글에 보면, Naver 로그인 시에 https로 정보가 날아가기 때문에 어떤 식의 HTTP 통신이 발생하는 지 알 수 없다는 것인데요. 하지만 실제로 살펴본 결과, 네이버는 부분적으로 HTTPS 통신과 GZIP 압축을 곁들이기 때문에 패킷을 가로채는 도구로는 알 수 없는 바이너리 데이터가 나타난 것이었습니다. gzip 압축의 경우에는, IE 설정을 통해서 강제로 gzip 지원을 하지 않는다고 표시를 하면 이후의 통신에서 웹 서버 측에서도 gzip 압축을 사용하지 않기 때문에 패킷 모니터링 도구로 보는 것이 가능합니다. 이를 위해 아래와 같이 HTTP 1.1 통신 설정을 해제해 주면 됩니다. 물론, 이렇게 했어도 HTTPS 통신의 경우에는 패킷 모니터링 도구로는 답이 없습니다. 하지만, Fiddler의 도움을 받으면 그런 경우에도 문제 없이 패킷 내용을 볼 수 있습니다.참고로, 이 글에서는 기왕에 fiddler를 사용하는 것이니, HTTP 1.1 해제 옵션은 사용하지 않겠습니다.
자, 이제 네이버를 IE에서 방문한 다음 '로그인' 버튼을 누르는 순간부터 발생하는 HTTP 통신을 캡쳐해 보겠습니다. 재미있군요. ^^ 곧바로 로그인 처리를 하는 웹 페이지로 폼 데이터를 전송하지 않고 static.nid.naver.com으로부터 keys_js.nhn 파일을 가져오고 있습니다. 내용을 한번 볼까요? 보시는 것처럼, keys_js.nhn 요청에 대한 응답은 gzip으로 압축되어 있어서 기본적으로 볼 수 없는데요. Fiddler 도구에서는 이를 위해 "Response is encoded and may need to be decoded before inspection. Click here to transform." 영역을 사용자가 누르는 시점에 다음과 같이 정상적으로 압축 해제된 결과를 확인할 수 있도록 해주고 있습니다.==== 요청 ====
GET http://static.nid.naver.com/enclogin/keys_js.nhn HTTP/1.1
Accept: application/javascript, */*;q=0.8
Referer: http://static.nid.naver.com/login.nhn?svc=me&url=http%3A%2F%2Fwww.naver.com
Accept-Language: ko-KR
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Accept-Encoding: gzip, deflate
Host: static.nid.naver.com
Connection: Keep-Alive
Cookie: npic=...[생략]...nid_enctp=1; nid_me=0
==== 응답 ====
HTTP/1.1 200 OK
Server: nginx/0.8.54
Date: Mon, 15 Aug 2011 03:01:54 GMT
Content-Type: text/html
Connection: keep-alive
X-Powered-By: PHP/5.1.4
Expires: -1
Cache-Control: no-store, no-cache, must-revalidate
Cache-Control: post-check=0, pre-check=0
Pragma: no-cache
Cache-Control: no-store, no-cache, must-revalidate
Content-Length: 1551
sessionkey = 'gOS...[생략]...N0';
keyname = '1...[생략]...42';
evalue = '5DF...[생략]...8D0DC5413';
nvalue = '010001';
$('encnm').value = '1...[생략]...42';
if (!sessionkey || !keyname || !evalue || !nvalue) {
//if (true) {
if ($('enctp').value == 3) {
showErrorDiv2(4);
} else {
showErrorDiv(4);
}
$('frmNIDLogin').action="https://nid.naver.com/nidlogin.relogin";
} else {
if ($('enctp').value == 1) {
var rsa = new RSAKey();
rsa.setPublic(evalue, nvalue);
$('encpw').value = rsa.encrypt(getLenChar(sessionkey) + sessionkey
+ getLenChar($('uid').value) + $('uid').value
+ getLenChar($('upw').value) + $('upw').value);
$('upw').value = "";
$('uid').value = "";
$('frmNIDLogin').submit();
} else if ($('enctp').value == 2) {
keySetting('gO...[생략]...001');
loginClick();
} else if ($('enctp').value == 3) {
document.CKKeyPro.E2EInitEx("naver", sessionkey, evalue,nvalue, $('id').value);
$('encpw').value = document.CKKeyPro.GetEncData("","frmNIDLogin", "pw");
$('id').value = "";
$('pw').value = "";
$('frmNIDLogin').submit();
}
}
보시는 것처럼, RSA 암호화에 필요한 값들을 네이버는 '로그인' 버튼이 눌리는 시점에 서버로부터 가져와서 처리를 하고 있습니다. 그리고 나서 enctp 값에 따라 if 문 처리를 하고 있는데, GET 요청에 보면 "nid_enctp=1"값으로 미뤄볼 때 결국 다음과 같은 if 문이 실행되는 것을 짐작할 수 있습니다.var rsa = new RSAKey();
rsa.setPublic(evalue, nvalue);
$('encpw').value = rsa.encrypt(getLenChar(sessionkey) + sessionkey
+ getLenChar($('uid').value) + $('uid').value
+ getLenChar($('upw').value) + $('upw').value);
$('upw').value = "";
$('uid').value = "";
$('frmNIDLogin').submit();
보시면, 원래의 HTTP Form 필드에서 upw/uid 값은 빈 문자열("")로 초기화 하고 모든 값을 encpw 필드에 rsa.encrypt로 암호화 해서 넣고 있습니다. 아하~~~ 매우 바람직한 방법으로 보입니다. 패킷을 가로채는 악의적인 사용자로 하여금 암호뿐만 아니라 아이디 조차도 알 수 없게 만들어 버린 것입니다.이렇게 해서 암호화된 encpw 값이 전송되는 부분은 Fiddler의 "/nidlogin.login"에서 찾아볼 수 있습니다.==== 요청 ====
POST https://nid.naver.com/nidlogin.login HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Referer: http://static.nid.naver.com/login.nhn?svc=me&url=http%3A%2F%2Fwww.naver.com
Accept-Language: ko-KR
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Host: nid.naver.com
Content-Length: 365
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: npic=...[생략]...nid_enctp=1; nid_me=0
enctp=1&encpw=...[생략]...&encnm=100005842&svctype=0&url=http%3A%2F%2Fwww.naver.com&enc_url=http%253A%252F%252Fwww.naver.com&postDataKey=&saveID=0&nvme=0&smart_level=1&id=&pw=
==== 응답 ====
HTTP/1.1 302 Found
Date: Mon, 15 Aug 2011 03:01:55 GMT
Server: Apache
Set-Cookie: nid_enctp=1; expires=Sun, 13-Nov-2011 03:01:55 GMT; path=/; domain=.nid.naver.com;
Set-Cookie: nid_inf=...[생략]...; path=/; domain=.naver.com;
Set-Cookie: nid_pwa=expired; expires=Wed, 02-Jun-1999 00:00:00 GMT; path=/; domain=.naver.com;
Set-Cookie: adsession=expired; expires=Wed, 02-Jun-1999 00:00:00 GMT; path=/; domain=.naver.com;
Set-Cookie: NID_DH_UI=...[생략]...; path=/; domain=.naver.com;
Set-Cookie: NID_AUT=...[생략]...=; path=/; domain=.naver.com;
Set-Cookie: NID_MATCH_M=...[생략]...; path=/; domain=.naver.com;
Set-Cookie: nid_me=...[생략]...; expires=Sun, 11-May-2014 03:01:55 GMT; path=/; domain=.nid.naver.com;
Set-Cookie: NID_SES=...[생략]...; path=/; domain=.naver.com;
Set-Cookie: nid_sid=; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/; domain=.naver.com;
Location: http://static.nid.naver.com/sso/cross-domain.nhn?sid=...[생략]...
Content-Length: 27
Connection: close
Content-Type: text/html
<html><body></body></html>
보시는 것처럼, 폼 데이터를 POST 방식으로 "https://nid.naver.com/nidlogin.login" 페이지로 전송하고 있는데, 개인적으로 여기서 약간 이해가 안되는 부분이 있습니다. 어차피 HTTPS통신을 사용할 거면 굳이 폼 데이터를 RSA 암호화 할 필요는 없었는데,,, 혹시 MITM(Man-in-the-middle Attack) 공격에 대한 대비책인지는 모르겠습니다.어쨌든, 결과를 살펴보면 전형적인 쿠키 인증 절차로 보입니다. keys_js.nhn 자바 스크립트 파일에서 처리된 encpw 필드 값이 nidlogin.login 으로 전달되고, 그 웹 페이지에서는 (아마도) RSA 개인키를 통해 encpw 필드 값을 복호화해서 사용자가 입력한 계정/암호 값을 기반으로 인증을 할 것입니다. 성공하면, 위의 응답에서 본 것처럼 인증과 관련된 각종 쿠키값을 보내줄 텐데 이후의 HTTP 통신은 이렇게 설정된 쿠키값들을 기반으로 이뤄질 것입니다.나머지는 그다지 중요한 것 같진 않지만 마저 살펴보면, 위의 응답이 "HTTP/1.1 302 Found"이므로 "Location" 헤더로 설정된 "http://static.nid.naver.com/sso/cross-domain.nhn?sid=...[생략]..." 페이지로 웹 브라우저는 재방문을 해야 합니다.==== 요청 ====
GET http://static.nid.naver.com/sso/cross-domain.nhn?sid=...[생략]... HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Connection: Keep-Alive
Accept-Language: ko-KR
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Pragma: no-cache
Accept-Encoding: gzip, deflate
Cookie: npic=...[생략]...nid_enctp=1; nid_me=0
Cache-Control: no-cache
Host: static.nid.naver.com
==== 응답 ====
HTTP/1.1 302
Server: nginx/0.8.54
Date: Mon, 15 Aug 2011 04:13:35 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.1.4
Location: http://mstatic.nid.blog.me/sso/cross-domain.nhn?domain_idx=0&sid=...[생략]...
Cache-Control: no-store, no-cache, must-revalidate
0
경로에 sso 라는 이름이 포함된 것으로 보아, Single-sign-on 을 위해 또 다른 웹 페이지를 방문하고 있는 것 같습니다. 위의 응답도 역시 302 redirection이므로 연쇄적으로 또 다른 SSO 경로를 방문하는데, 위의 결과 이외에도 네이버는 다음과 같이 2개의 웹 페이지를 더 방문합니다.정리해 보면, 네이버는 로그인 한번에 인증과 함께 4개의 SSO 를 함께 처리하고 있습니다. 그리고 최종적으로 마지막의 finalize.nhn 까지 와서야 비로소 www.naver.com 으로 이동하도록 302 코드를 반환하는 것으로 끝이 납니다.
살펴보니, 제법 네이버의 로그인 체계가 마음에 듭니다. 혹시나, 자신의 웹 사이트에 로그인을 구현해야 하는 분들이 있다면 위의 과정을 모방해서 구현하는 것도 좋은 선택이 될 수 있겠습니다. ^^이것으로, 대강의 분석이 끝났으니 다시 처음 질문했던 상황으로 돌아가 볼까요? 문제는 Socket 프로그래밍으로 이것을 어떻게 해야 하느냐 입니다. 음... 간단한 문제가 아니군요. ^^ 시간 관계상 제가 직접 구현할 수는 없지만 대략 다음과 같은 순으로 진행해야 합니다.- 사용자로부터, 로그인 아이디/암호를 입력받는다.
- http://static.nid.naver.com/enclogin/keys_js.nhn 에 요청해서 암호화 관련 키들을 담고 있는 js 파일을 받아서 파싱한 후 sessionkey, evalue, nvalue 값을 얻는다.
- 자바 스크립트로 되어 있는 RSA 코드를 C/C++로 마이그레이션하고, rsa.encrypt 메서드와 동일한 동작으로 암호화해서 encpw 값을 구함.
- ssl 소켓 통신을 이용하여 https://nid.naver.com/nidlogin.login 으로 각종 폼 데이터를 전송하고 반환받은 쿠키값들을 보관
제 역할은 이것으로 끝난 것 같습니다. ^^ 혹시나 위의 구현을 하시는 분이 있다면 블로그를 통해 공개하시는 것도 좋을 것 같습니다. ^^(참고로, 저더러 네이버 로그인 처리를 구현하라고 한다면? 차라리 웹 브라우저 컨트롤을 하나 내장해서 로그인 완료과정까지 마친 다음 최종 쿠키값만을 별도로 얻도록 구성할 것 같습니다. 만약, 제가 시간이 많은 상황에 놓여 있다면 위에서 설명한 데로 구현을 바꿀 것이고, 더욱 많은 상황에 놓인다면 ssl 프로토콜 문서에 따라 직접 구현할 것입니다. ^^)
출처 - http://blog.naver.com/PostView.nhn?blogId=techshare&logNo=100135417109