8 20 2009
Java UTF-8과 서울신용평가정보 가상식별 실명확인서비스
공공 프로젝트에서 서울신용평가정보의 가상식별 실명확인서비스를 적용하다 보니
확인된 성명(한글)이 깨지는 문제를 만나게 되었다.
여러가지 테스트를 해보니 문제 상황은 다음과 같다.
Java 소스 및 JSP와 Tomcat의 Connector의 URIEncoding은 UTF-8이다.
Tomcat을 Locale을 UTF-8 상태에서 실행하거나
Tomcat 실행 옵션에 file.encoding 시스템 프로퍼티를 UTF-8로 설정하고 실행하면 문제가 발생되었다.
만약 Locale이 EUC-KR이거나 file.encoding=EUC-KR 이면 성명이 깨지지 않았다.
이제 UTF-8 상태에서 Tomcat을 실행하는 것이 기본이 된 것 같은데 아직도 2% 부족하다는 것을 알 수 있다.
단순한 서비스 하나를 위해서 EUC-KR로 Tomcat을 실행하는 것은 앞뒤가 맞지 않다고 생각한다.
이를 해결하기 위해 서울신용평가정보에서 제공된 클래스의 메소드 com.sci.vname.secu.aes.SciPacketConversion#RecvWritePacket(String)를 보니 역시 bytes를 String으로 만들 때 그냥 만들도록 되어있었다.
문제를 해결할 겸 좀 더 쉽게 사용하기 위해서 클래스를 만들었다.
/**
* @(#)SciVname.java
*
* Copyright (C) 2009 D.TRIBE. All rights reserved.
*
* THIS SOFTWARE IS THE PROPRIETARY INFORMATION OF DTRIBE, INC.
* USE IS SUBJECT TO LICENSE TERMS.
*/
package com.dtribe.scivname;
import com.dtribe.model.BaseModel;
import com.sci.vname.secu.aes.SciHttpSecuX;
import com.sci.vname.secu.hmac.SciHmac;
/**
* 서울신용평가정보 가상 식별 실명 확인<br/>
* <p>
* 서울신용평가정보에서 제공하는 클래스 코드 사용 예:
* <pre>{@code
* String org = request.getParameter("retInfo");
* String result = com.sci.vname.secu.aes.SciPacketConversion.RecvWritePacket(org).toString();
* String[] data = result.split("/");
* String name = "";
* if(data.length == 7) {
* log.debug("reqNum=" + data[0]);
* log.debug("vDiscrNo=" + data[1]);
* log.debug("name=" + data[2]);
* log.debug("result=" + data[3]);
* log.debug("discrHash=" + data[4]);
* log.debug("hashMsg=" + data[5]);
* String checksum = com.sci.vname.secu.hmac.SciHmac.HMacEncript(data[0]+data[1]+data[3]);
* if(checksum.equals(data[5])) {
* name = data[2];
* }
* }}</pre>
* </p>
*
* <p>디트라이브 클래스 코드 사용 예:
* <pre>{@code
* String org = request.getParameter("retInfo");
* SciVname vname = SciVname.parse(org);
* if(vname != null && vname.isValid()) {
* //
* }}</pre>
* </p>
*
* @author Barney Kim
* @version $Revision: 1.2 $, $Date: 2009/08/19 20:36:53 $
*/
public class SciVname extends BaseModel {
/** 시리얼 버전 UID */
private static final long serialVersionUID = 429479309042299567L;
/** 요청 번호 */
private String reqNum;
/** 가상 식별 번호 */
private String vDiscrNo;
/** 성명 */
private String name;
/** 식별검증 결과 */
private String result;
/** 중복가입확인 정보 */
private String discrHash;
/** 위/변조 검증값 */
private String hashMsg;
// 속성 ------------------------------------------------------------------
/**
* 요청 번호 (최대 30 자)
* 실명확인 요청마다 유일하게 생성.
*/
public String getReqNum() {
return reqNum;
}
/**
* 요청번호
*/
public void setReqNum(String reqNum) {
this.reqNum = reqNum;
}
/**
* 가상 식별 번호 (13 자)
* 주민등록번호와 매칭되는 SCI에서 생성한 가상의 13자리 번호
* 성공: SCI+식별번호11자리
* 실패: SCI+99999999999
*/
public String getVDiscrNo() {
return vDiscrNo;
}
/**
* 가상 식별 번호
*/
public void setVDiscrNo(String discrNo) {
vDiscrNo = discrNo;
}
/**
* 성명 (최대 20자)
* 실패: null
*/
public String getName() {
return name;
}
/**
* 성명
*/
public void setName(String name) {
this.name = name;
}
/**
* 식별검증 결과 (1자)
* 1: 정상
* 2: 오류.
* 입력된 주민번호의 성명이 실명확인 DB의 성명과 불일치,
* 주민번호가 숫자가 아닌경우, 주민번호가 13자리가 아닌경우
* 성명이 한글이 아닌경우, 성명이 2~10자 아닌 경우
* 3: 없음.
* 입력된 주민번호와 성명이 실명확인 DB에 업ㅎㅅ는 경우
* 4: 주민민번호 오류
* 입력된 주민번호의 조합이 오류인 경우: 생년이 70년이후인 경우만.
* 5: 시스템 오류
*/
public String getResult() {
return result;
}
/**
* 식별검증 결과
*/
public void setResult(String result) {
this.result = result;
}
/**
* 중복가입확인 정보 (최대 64자)
* 아이핀서비스를 이용하는 경우만 값이 유효
*/
public String getDiscrHash() {
return discrHash;
}
/**
* 중복가입확인 정보
*/
public void setDiscrHash(String discrHash) {
this.discrHash = discrHash;
}
/**
* 위/변조 검증값 (최대 40자)
*/
public String getHashMsg() {
return hashMsg;
}
/**
* 위/변조 검증값
*/
public void setHashMsg(String hashMsg) {
this.hashMsg = hashMsg;
}
// 메소드 ----------------------------------------------------------------
/**
* 서울신용평가정보의 실명확인결과의 암호화된 문자열을 복호화하여
* 평문으로 반환한다.
*
* SciPacketConversion.RecvWritePacket은 file.encoding 시스템 프로퍼티가
* EUC_KR이 아닌 UTF-8일 경우 한글이 깨지기 때문에 이를 방지하기 위해
* RecvWritePacket 메소드를 일부 수정함.
*
* @param s 인코드된 문자열
* @return 디코드된 문자열
* @see com.sci.vname.secu.aes.SciPacketConversion#RecvWritePacket(String)
*/
public static String decode(String s) {
if(s == null) {
return null;
}
StringBuffer sb = new StringBuffer("");
int buffer = 1000;
try {
byte dec[] = SciHttpSecuX.Decryption(s);
byte bytes[] = new byte[buffer];
int i=0, j=0;
do {
if(j >= dec.length) {
break;
}
bytes[i] = 0;
if(dec[j] == 0 || dec[j] == 10) {
// ORG
// sb.append((new String(bytes)).trim());
// for UTF-8
sb.append((new String(bytes, "EUC_KR")).trim());
break;
}
if(dec[j] >= 1 && dec[j] <= 126) {
bytes[i] = dec[j];
i++;
} else {
bytes[i] = dec[j];
bytes[i + 1] = dec[j + 1];
i += 2;
j++;
}
if(i >= 998) {
// ORG
// sb.append(new String(bytes));
// for UTF-8
sb.append(new String(bytes, "EUC_KR"));
i = 0;
bytes = new byte[buffer];
}
j++;
} while(true);
}
catch(Exception ex) {
ex.printStackTrace();
}
return sb.toString();
}
/**
* 서울신용평가정보의 실명확인결과의 암호화된 문자열을 복호화하고 이를 이용하여
* SciVname 객체를 생성하여 반환한다.
*
* @param s 인코드된 문자열
* @return SciVname 객체
*/
public static SciVname parse(String s) {
if(r == null) {
return null;
}
String r = decode(s);
String data[] = r.split("/");
if(data.length != 7) {
return null;
}
SciVname vname = new SciVname();
vname.setReqNum (data[0]);
vname.setVDiscrNo (data[1]);
vname.setName (data[2]);
vname.setResult (data[3]);
vname.setDiscrHash(data[4]);
vname.setHashMsg (data[5]);
return vname;
}
/**
* 가상 식별 실명 확인이 성공하고 올바른지 확인한다.
*
* @return 위/변조 검증값이 올바르고 결과가 성공이면 true
*/
public boolean isValid() {
if (this.reqNum == null || this.reqNum.length() == 0 ||
this.vDiscrNo == null || this.vDiscrNo.length() != 13 ||
this.result == null || this.result.length() != 1 ||
this.hashMsg == null || this.hashMsg.length() == 0) {
return false;
}
StringBuffer sb = new StringBuffer();
sb.append(this.reqNum).append(this.vDiscrNo).append(this.result);
String checksum = SciHmac.HMacEncript(sb.toString());
return (checksum.equals(this.hashMsg) && this.result.equals("1"));
}
/**
* 디코드된 문자열로 가상 식별 실명 확인이 성공하고 올바른지 확인한다.
*
* @param s 디코드된 문자열
* @return 위/변조 검증값이 올바르고 결과가 성공이면 true
*/
public static boolean isValid(String s) {
String[] data = s.split("/");
if(data.length != 7) {
return false;
}
if (data[0] == null || data[0].length() == 0 ||
data[1] == null || data[1].length() != 13 ||
data[3] == null || data[3].length() != 1 ||
data[5] == null || data[5].length() == 0) {
return false;
}
StringBuffer sb = new StringBuffer();
sb.append(data[0]).append(data[1]).append(data[3]);
String checksum = SciHmac.HMacEncript(sb.toString());
return (checksum.equals(data[5]) && data[3].equals("1"));
}
}
// EOF
이것을 Spring Framework에서 사용하면 다음과 같다.
import com.dtribe.scivname.SciVname;
public ModelAndView output(HttpServletRequest request,
HttpServletResponse response)
throws Exception
{
String org = ServletRequestUtils.getRequiredStringParameter(request, "retInfo");
SciVname vname = SciVname.parse(org);
log.debug("org=" + org);
log.debug("vname=" + vname);
ModelAndView mav = new ModelAndView("vname/output");
if(vname != null && vname.isValid()) {
mav.addObject("vname", vname);
}
return mav;
}
JSP 페이지 (vname/output.jsp)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"
%><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"
%><c:if test="${vname != null}">
<script type="text/javascript">
top.document.getElementById("name").value="${vname.name}";
</script>
</c:if>

