javascript - java 간 RSA 를 이용해서 암호화 복호화 하기 / 암호화 로그인 / 평문전송

2017. 4. 7. 14:00language/jsp


javascript - java 간 RSA 를 이용해서 암호화 복호화 하기

 

 

 

 

JSP 기반으로 설명함.

JSP가 아니라도 적용가능함.

 

 

 

 

프로젝트를 진행중에 로그인 시 로그인 정보의 평문전송에 대해서 이슈가 있었다.

 

RSA 를 사용하여 평문전송 이슈를 해결하였다.

 

RSA는 공개키 암호시스템의 하나로, 암호화뿐만 아니라 전자서명이 가능한 최초의 알고리즘으로 알려져 있다.

 

RSA에 관한 이론적인 설명은 없고 사용하는 방법과 소스를 첨부한다.

 

 

본 방법만을 가지고 보안처리를 하게 되면 CSRF attack, Session Hijacking, XSS 취약점에 노출 될 수 있습니다.

 

CSRF attack, Session Hijacking, XSS 의 취약점에 대해서도 대응해야 합니다.

 



 

요약하자면 다음과 같다.

controller 에서 공개키와 개인키를 생성한다.

개인키는 session 에 저장한다.

공개키를 사용하여 modulus, exponent 를 생성하고 request 에 담는다.

loginForm.jsp를 호출한다.

입력받은 id, pw 등을 modulus, exponent 사용하여 rsa 암호화 한 후 전송한다.

javascript 에서 rsa를 사용하기 위한 js 파일들 -> (biginteger javascript 링크 또는 biginteger javascript.zip)

전송받은 controller 에서 session 에 저장했던 개인키를 활용하여 복호화 한다.

 

 

 

 

소스 설명은 주석으로 대신한다.

 

Login Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.RSAPublicKeySpec;
 
import javax.crypto.Cipher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
 
@Controller("loginController")
public class LoginController {
 
    private static String RSA_WEB_KEY = "_RSA_WEB_Key_"// 개인키 session key
    private static String RSA_INSTANCE = "RSA"// rsa transformation
 
    // 로그인 폼 호출
    @RequestMapping("/loginForm.do")
    public ModelAndView loginForm(HttpServletRequest request, HttpServletResponse response) throws Exception {
 
        // RSA 키 생성
        initRsa(request);
 
        ModelAndView mav = new ModelAndView();
        mav.setViewName("loginForm");
        return mav;
    }
 
    // 로그인
    @RequestMapping("/login.do")
    public ModelAndView login(HttpServletRequest request, HttpServletResponse response) throws Exception {
 
        String userId = (String) request.getParameter("USER_ID");
        String userPw = (String) request.getParameter("USER_PW");
 
        HttpSession session = request.getSession();
        PrivateKey privateKey = (PrivateKey) session.getAttribute(LoginController.RSA_WEB_KEY);
 
        // 복호화
        userId = decryptRsa(privateKey, userId);
        userPw = decryptRsa(privateKey, userPw);
 
        // 개인키 삭제
        session.removeAttribute(LoginController.RSA_WEB_KEY);
 
        // 로그인 처리
        /*
          
         ...  
           
         */
 
        ModelAndView mav = new ModelAndView();
        mav.setViewName("index");
        return mav;
    }
 
    /**
     * 복호화
     * 
     * @param privateKey
     * @param securedValue
     * @return
     * @throws Exception
     */
    private String decryptRsa(PrivateKey privateKey, String securedValue) throws Exception {
        Cipher cipher = Cipher.getInstance(LoginController.RSA_INSTANCE);
        byte[] encryptedBytes = hexToByteArray(securedValue);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        String decryptedValue = new String(decryptedBytes, "utf-8"); // 문자 인코딩 주의.
        return decryptedValue;
    }
 
    /**
     * 16진 문자열을 byte 배열로 변환한다.
     * 
     * @param hex
     * @return
     */
    public static byte[] hexToByteArray(String hex) {
        if (hex == null || hex.length() % != 0) { return new byte[] {}; }
 
        byte[] bytes = new byte[hex.length() / 2];
        for (int i = 0; i < hex.length(); i += 2) {
            byte value = (byte) Integer.parseInt(hex.substring(i, i + 2), 16);
            bytes[(int) Math.floor(i / 2)] = value;
        }
        return bytes;
    }
 
    /**
     * rsa 공개키, 개인키 생성
     * 
     * @param request
     */
    public void initRsa(HttpServletRequest request) {
        HttpSession session = request.getSession();
 
        KeyPairGenerator generator;
        try {
            generator = KeyPairGenerator.getInstance(LoginController.RSA_INSTANCE);
            generator.initialize(1024);
 
            KeyPair keyPair = generator.genKeyPair();
            KeyFactory keyFactory = KeyFactory.getInstance(LoginController.RSA_INSTANCE);
            PublicKey publicKey = keyPair.getPublic();
            PrivateKey privateKey = keyPair.getPrivate();
 
            session.setAttribute(LoginController.RSA_WEB_KEY, privateKey); // session에 RSA 개인키를 세션에 저장
 
            RSAPublicKeySpec publicSpec = (RSAPublicKeySpec) keyFactory.getKeySpec(publicKey, RSAPublicKeySpec.class);
            String publicKeyModulus = publicSpec.getModulus().toString(16);
            String publicKeyExponent = publicSpec.getPublicExponent().toString(16);
 
            request.setAttribute("RSAModulus", publicKeyModulus); // rsa modulus 를 request 에 추가
            request.setAttribute("RSAExponent", publicKeyExponent); // rsa exponent 를 request 에 추가
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
cs

 

 



JSP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
    String ctxPath = (String) request.getContextPath();
%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Login</title>
<script type="text/javascript" src="<%=ctxPath %>/script/jquery/jquery-1.11.0.min.js"></script>
<!-- 순서에 유의 -->
<script type="text/javascript" src="<%=ctxPath %>/script/RSA/rsa.js"></script>
<script type="text/javascript" src="<%=ctxPath %>/script/RSA/jsbn.js"></script>
<script type="text/javascript" src="<%=ctxPath %>/script/RSA/prng4.js"></script>
<script type="text/javascript" src="<%=ctxPath %>/script/RSA/rng.js"></script>
 
<script type="text/javascript">    
    function login(){
        var id = $("#USER_ID_TEXT");
        var pw = $("#USER_PW_TEXT");
    
        if(id.val() == ""){
            alert("아이디를 입력 해주세요.");
            id.focus();
            return false;
        }
        
        if(pw.val() == ""){
            alert("비밀번호를 입력 해주세요.");
            pw.focus();
            return false;
        }
        
        // rsa 암호화
        var rsa = new RSAKey();
        rsa.setPublic($('#RSAModulus').val(),$('#RSAExponent').val());
        
        $("#USER_ID").val(rsa.encrypt(id.val()));
        $("#USER_PW").val(rsa.encrypt(pw.val()));
        
        id.val("");
        pw.val("");
 
        return true;
    }
</script>
</head>
<body>
    <form name="frm" method="post" action="<%=ctxPath%>/login.do" onsubmit="return login()">
        <input type="hidden" id="RSAModulus" value="${RSAModulus}"/>
        <input type="hidden" id="RSAExponent" value="${RSAExponent}"/>    
        <input type="text" placeholder="아이디" id="USER_ID_TEXT" name="USER_ID_TEXT" />
        <input type="password" placeholder="비밀번호" id="USER_PW_TEXT" name="USER_PW_TEXT" />
        <input type="hidden" id="USER_ID" name="USER_ID">
        <input type="hidden" id="USER_PW" name="USER_PW">
        <input type="submit" value="로그인" />
    </form>
</body>
</html>
cs