포트폴리오 중 하나인 그룹웨어 시스템에서 로그인시 보안을 위해 간단하게라도 암호화 알고리즘을 구현해야겠다고 생각했다.

 

알고리즘의 종류에는 대칭 암호화와 비대칭 암호화가 있는데,

대칭 암호화는 빠르다는 장점이 있지만 자주 키를 바꿔야 해 관리가 어렵다는 단점이 있고

비대칭 암호화는 대칭에 비해 다소 느리지만 안전하고 키 관리가 단순하다는 장점이 있다.

 

계정 정보가 해킹당할 시 사내 정보가 유출될 수 있다는 점을 염두해 안전하다는 부분에 초점을 맞췄고,

비대칭 암호화인 RSA 알고리즘을 구현하기로 했다.  

 

 

* RSA 라이브러리 참고 사이트 : http://www-cs-students.stanford.edu/~tjw/jsbn/ 

 

 

1. 로그인 전 공개키 생성


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
@Controller
public class HomeController {
    final static Logger log = LoggerFactory.getLogger(HomeController.class);
 
    /**
     * @author GREEN GO
     * @since 2017.03.14
     */
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String homeCtrl(Locale locale, HttpServletRequest request, HttpServletResponse response, Model model,
            HttpSession session) throws Exception, NoSuchAlgorithmException {
 
        session = request.getSession();
 
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
 
        generator.initialize(1024);
 
        KeyPair keyPair = generator.genKeyPair();
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();
 
        // RSA 개인키
        session.setAttribute("_RSA_WEB_Key_", privateKey);
 
        log.debug("HomeController _RSA_WEB_Key_ : " + request.getAttribute("setAttribute"));
 
        RSAPublicKeySpec publicSpec = (RSAPublicKeySpec) keyFactory.getKeySpec(publicKey, RSAPublicKeySpec.class);
 
        String publicKeyModulus = publicSpec.getModulus().toString(16);
        String publicKeyExponent = publicSpec.getPublicExponent().toString(16);
 
        // 로그인 폼 hidden setting
        request.setAttribute("RSAModulus", publicKeyModulus);
        // 로그인 폼 hidden setting
        request.setAttribute("RSAExponent", publicKeyExponent);
 
        log.debug("HomeController RSAModulus getAttribute : " + request.getAttribute("RSAModulus"));
        log.debug("HomeController RSAExponent getAttribute : " + request.getAttribute("RSAExponent"));
 
        return "member/mm_login";
    }
}
cs


로그인 페이지가 열리기 전 컨트롤러에서 공개키를 생성한 후, 로그인 form에 request 범위에 담아 hidden 타입으로 적어준다. 

 

 

2. 로그인


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
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE HTML>
<html>
<head>
    <link href="<c:url value='/resources/css/bootstrap.min.css'/>" rel="stylesheet">
    <link href="<c:url value='/resources/css/landing-page.css'/>" rel="stylesheet">
    <link href="<c:url value='/resources/font-awesome/css/font-awesome.min.css'/>" rel="stylesheet" type="text/css">
    <link href="https://fonts.googleapis.com/css?family=Lato:300,400,700,300italic,400italic,700italic" rel="stylesheet" type="text/css">
    
    <!-- RSA javascript library / cdn 올리는 순서 틀리면 안 됨 -->
    <script src="<c:url value='/resources/js/rsa/jsbn.js'/>"></script>
    <script src="<c:url value='/resources/js/rsa/rsa.js'/>"></script>
    <script src="<c:url value='/resources/js/rsa/prng4.js'/>"></script>
    <script src="<c:url value='/resources/js/rsa/rng.js'/>"></script>
    
    <script src="<c:url value='/resources/js/jquery-3.1.1.min.js'/>"></script>
    <script>
        $(document).on('click','#loginBtn',function() {
        
        // 로그인 버튼 클릭시 서버로 전송되기 전에 계정 정보 암호화 후 서버로 전송
        // $("#loginBtn").click(function() {
            var mmCode = $("#mmCode").val();
            var mmPassword = $("#mmPassword").val();
         
            // RSA 암호키 생성
            var rsa = new RSAKey();
            
            rsa.setPublic($("#RSAModulus").val(), $("#RSAExponent").val());
            
            // 계정 정보 암호화
            var secondMmCode = rsa.encrypt(mmCode);
            var secondMmPassword = rsa.encrypt(mmPassword);
            
            // console.log('secondMmCode : ' + secondMmCode);
            // console.log('secondMmPassword : ' + secondMmPassword);
            
            // Restful 기술을 이용한 컨트롤러를 이용해 ajax로 form 정보를 전송하고, 유효성 검사 후
            // 아이디, 비밀번호 일치하면 메인 페이지로 이동 (실패시 alert 창 띄움)
            $.ajax({ 
                  type: "post",  
                  url: "/smart/mm/loginRSA",
                  dataType: "json",
                  data: {"secondMmCode": secondMmCode, "secondMmPassword": secondMmPassword},
                  success: function(data) {    
                      
//                       alert('data.state : ' + data.state);
                     
                      if(data.state == true) {
                          location.href = "<c:url value='/member/mm_login'/>"
                      } else if(data.state == false) {
                         THIS.oWin.alert("로그인","로그인에 실패했습니다. <br> 아이디와 패스워드를 확인하세요.");
                      } else {
                         THIS.oWin.alert("로그인","잘못된 경로로 접근했습니다. <br>암호화 인증에 실패했습니다."); 
                      } 
                  } 
            });
        });
    </script>
</head>
<body>
    <div class="intro-header">
       <div class="container">
        <div class="row">
               <div class="col-lg-12">
                   <div class="intro-message">
                       <h1>Smart Groupware-System</h1>
                       <hr class="intro-divider">
                    <div class="col-md-4 col-md-offset-4">
                        <div class="panel panel-default">
                            <div class="panel-body">
                                <h5 class="text-center" style="color:black;">SIGN UP</h5>
                                <!-- 컨트롤러에서 생성했던 공개키를 form에 hidden 타입으로 배치-->
                                <form class="form form-signup" role="form" id="mmlogin" action="member/mm_login" method="post">
                                    <div class="form-group">
                                        <input type="hidden" id="RSAModulus" value="${RSAModulus}">
                                        <input type="hidden" id="RSAExponent" value="${RSAExponent}">
                                        <div class="input-group">
                                            <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span>
                                            <input type="text" class="form-control" name="mmCode" id="mmCode" value="2"/>
                                        </div>
                                    </div>
                                    <div class="form-group">
                                        <div class="input-group">
                                            <span class="input-group-addon"><span class="glyphicon glyphicon-lock"></span></span>
                                            <input type="password" class="form-control" name="mmPassword" id="mmPassword" value="123456"/>
                                        </div>
                                    </div>
                                    <input type="button" id="loginBtn" class="btn btn-sm btn-primary btn-block" value="로그인"/>
                                </form>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
</body>
</html>
cs


컨트롤러에서 jsp 페이지로 보낸 hidden 타입들을 form 내에 배치하고, hidden 타입 데이터와 계정 정보를 ajax로 보내

Restful 기술을 이용한 컨트롤러에서 DB 정보와 비교한 후 성공시 메인 페이지로 이동하고 실패시 alert 창을 띄운다.

 

 

3. 계정 정보 복호화


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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package com.cafe24.smart;
 
import javax.crypto.Cipher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
 
import java.security.PrivateKey;
import java.util.HashMap;
import java.util.Map;
 
import com.cafe24.smart.member.domain.Department;
import com.cafe24.smart.member.domain.Member;
import com.cafe24.smart.member.service.MemberService;
 
@RestController
public class HomeRestController {
    private static final Logger log = LoggerFactory.getLogger(HomeRestController.class);
 
    @Autowired
    MemberService memberService;
 
    // 로그인 체크
    @RequestMapping(value = "mm/loginRSA", method = RequestMethod.POST)
    @ResponseBody
    // 암호화된 키들을 다시 복호화 하여 DB 값들과 비교 => 있으면 로그인
    public Map<String, Object> mmLoginRSACtrl(HttpServletRequest request, Map<String, Object> map) {
 
        Map<String, Object> params = new HashMap<String, Object>();
 
        String mmCode = request.getParameter("secondMmCode");
        String mmPassword = request.getParameter("secondMmPassword");
 
        HttpSession session = request.getSession();
 
        // 로그인 전에 세션에 저장했던 개인키를 getAttribute
        PrivateKey privateKey = (PrivateKey) session.getAttribute("_RSA_WEB_Key_");
 
        log.debug("HomeRestController mmLoginRSACtrl privateKey : " + privateKey);
 
        // 개인키(아이디)가 들어오지 않은 경우
        if (privateKey == null) {
            
            params.put("state"false);
            // 개인키(아이디)가 들어온 경우
        } else {
            try {
                // 암호화 처리된 사용자 계정을 복호화 처리
                int _mmCode = Integer.parseInt(decryptRsa(privateKey, mmCode));
                int _mmPassword = Integer.parseInt(decryptRsa(privateKey, mmPassword));
 
                Member member = new Member();
                Department department = new Department();
                member.setMmCode(_mmCode);
 
                // 복호화한 사원코드로 값을 가져와서 DB의 사원 패스워드와 같은지 비교
                params = memberService.mmLoginServ(member);
 
                log.debug("HomeRestController mmLoginRSACtrl _mmCode : " + _mmCode);
                log.debug("HomeRestController mmLoginRSACtrl _mmPassword : " + _mmPassword);
 
                if (params != null) {
                    log.debug("mmLoginRSACtrl params not null");
 
                    session.setAttribute("mmCode", _mmCode);
                    // session.setAttribute("mmName", params.get("mmName"));
 
                    member = memberService.mmContentByMmCodeServ(_mmCode);
                    session.setAttribute("mmName", member.getMmName());
 
                    // 부서정보세팅
                    department = memberService.mmDpDetailServ(member.getDpCode());
                    session.setAttribute("dpName", department.getDpName());
                }
 
                log.debug("mmLoginRSACtrl mmCode session : " + session.getAttribute("mmCode"));
                log.debug("mmLoginRSACtrl mmCode session : " + session.getAttribute("mmName"));
 
                params.put("state"true);
            } catch (Exception e) {
                params.put("state"false);
 
                e.printStackTrace();
            }
        }
 
        log.debug("HomeRestController mmLoginRSACtrl params : " + params);
 
        return params;
    }
 
    public String decryptRsa(PrivateKey privateKey, String securedValue) {
 
        String decryptedValue = "";
 
        log.debug("mmLoginRSACtrl decryptRsa privateKey : " + privateKey);
        log.debug("mmLoginRSACtrl decryptRsa securedValue : " + securedValue);
 
        try {
            Cipher cipher = Cipher.getInstance("RSA");
 
            // 암호화 된 값 : byte 배열
            // 이를 문자열 form으로 전송하기 위해 16진 문자열(hex)로 변경
            // 서버측에서도 값을 받을 때 hex 문자열을 받아 다시 byte 배열로 바꾼 뒤 복호화 과정을 수행
            byte[] encryptedBytes = hexToByteArray(securedValue);
 
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
 
            byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
 
            // 문자 인코딩
            decryptedValue = new String(decryptedBytes, "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
 
        log.debug("mmLoginRSACtrl decryptRsa decryptedValue : " + decryptedValue);
 
        return decryptedValue;
    }
 
    // 16진 문자열을 byte 배열로 변환
    public static byte[] hexToByteArray(String hex) {
 
        if (hex == null || hex.length() % 2 != 0) {
            return new byte[] {};
        }
 
        byte[] bytes = new byte[hex.length() / 2];
 
        log.debug("mmLoginRSACtrl hexToByteArray bytes : " + bytes);
 
        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;
        }
 
        log.debug("mmLoginRSACtrl hexToByteArray final bytes : " + bytes);
 
        return bytes;
    }
}
cs


계정 정보와 hidden 타입으로 받아온 공개키들을 복호화한다.


'BackEnd > Java' 카테고리의 다른 글

Java :: NULL vs isEmpty() 차이  (0) 2018.01.16
LinkedHashMap  (0) 2017.06.29
Java :: Spring 파일 업로드 (multipart-form/data)  (2) 2017.03.14
가변인수 (Java 5.0 이상)  (0) 2017.02.13
Java :: 열거형 (Enumeration) (Java 5.0 이상)  (0) 2017.02.13

+ Recent posts