송민준의 개발노트

로그인에 RSA 구현하기 (JSP jQuery MVC2) 본문

웹/JSP

로그인에 RSA 구현하기 (JSP jQuery MVC2)

송민준 2019. 12. 19. 21:31

 웹 프로젝트를 하는데 포트폴리오에 보안 쪽 차별화를 더하면 좋다고 해서 기능을 추가했다.

SSL이면 더더욱 좋겠지만 돈이 든다고 해서... RSA를 구현하는 쪽으로 방향을 잡았다.

RSA는 정보처리기사를 공부하면서 알게된 개념인데 비대칭 방식으로 공개키와 개인키로 구분이 된다. RSA는 소인수 분해의 난해함에 기반해서 공개키만으로는 개인키를 쉽게 짐작할 수 없도록 되어 있다.

좀 더 자세한 내용은 아래 위키백과를 참고하면 좋을듯

https://ko.wikipedia.org/wiki/RSA_%EC%95%94%ED%98%B8

 

RSA 암호 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 둘러보기로 가기 검색하러 가기 RSA는 공개키 암호시스템의 하나로, 암호화뿐만 아니라 전자서명이 가능한 최초의 알고리즘으로 알려져 있다. RSA가 갖는 전자서명 기능은 인증을 요구하는 전자 상거래 등에 RSA의 광범위한 활용을 가능하게 하였다. 1978년 로널드 라이베스트(Ron Rivest), 아디 샤미르(Adi Shamir), 레너드 애들먼(Leonard Adleman)의 연구에 의해 체계화되었으며, RSA라는 이름은

ko.wikipedia.org

일단 방법은 이용자가 로그인 폼에 입력을 하고 submit을 하면 중간에 패킷을 가로채는 것을 방지하기 위해 암호화 해서 submit을 하고 자바단에서 복호화 한다.

 

일단 로그인 폼을 호출하는 서블릿에서  아래 코드를 추가해준다.

HttpSession 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();
RSAPublicKeySpec publicSpec = (RSAPublicKeySpec) keyFactory.getKeySpec(publicKey, RSAPublicKeySpec.class);
String publicKeyModulus = publicSpec.getModulus().toString(16);
String publicKeyExponent = publicSpec.getPublicExponent().toString(16);

그리고 session과 request에 각각 추가해준다.

request.setAttribute("publicKeyModulus", publicKeyModulus);
request.setAttribute("publicKeyExponent", publicKeyExponent);
session.setAttribute("__rsaPrivateKey__", privateKey);

그리고 로그인 JSP파일에서(나 같은 경우는 navbar) 4개의 스크립트 파일을 가져오고

<script src="js/jsbn.js"></script>
<script src="js/rsa.js"></script>
<script src="js/prng4.js"></script>
<script src="js/rng.js"></script>

나 같은 경우 모달로 로그인 창을 보여줬다. 보여지는 폼은 body에 있고 암호화해서 보내는 form은 footer에 있다.

원리는 이렇다. body의 폼에서 값을 입력 받고 제출 버튼을 누르면 암호화하는 함수가 실행이 되고 결과값을 hidden 폼에 담아 submit을 한다.

<div class="modal" id="login_Modal">
	<div class="modal-dialog">
		<div class="modal-content">
			<!-- Modal Header -->
			<div class="modal-header">
				<h4 class="modal-title logog">로그인</h4>
				<button type="button" class="close" data-dismiss="modal">&times;</button>
			</div>
			<!-- Modal body -->
			<div class="modal-body">
				<div class="container uniform">
					<form method="post" action="loginProcess.net" id="login_form">
					<form id="login_form">
						<fieldset>
							<div class="form-group">
								<label for="login_id" class="modalBlack">아이디</label> <input
								<label for="login_id" class="modalBlack">아이디</label> 
								<input
									type="text" class="form-control" id="login_id"
									placeholder="Enter id" name="login_id" required maxLength="30">
									<input type="hidden" id="rsaPublicKeyModulus" value="${publicKeyModulus}" />
								<span id="login_id_message"></span>
							</div>
							<div class="form-group">
								<label for="modal_pass" class="modalBlack">비밀번호</label> <input
								<label for="modal_pass" class="modalBlack">비밀번호</label> 
								<input
									type="password" class="form-control" id="login_pass"
									placeholder="Enter password" name="login_pass" required>
									<input type="hidden" id="rsaPublicKeyExponent" value="${publicKeyExponent}" />
							</div>
							<div class="6u$ 12u$(small)">
								<input type="checkbox" id="login_remember" name="login_remember"
									checked> <label for="login_remember">Remember
									me</label>
							</div>

						</fieldset>
					</form>
				</div>
			</div>

			<!-- Modal footer -->
			<div class="modal-footer">
				<button id="login_button" type="submit" class="btn btn-primary">로그인</button>
				<%-- <a id = "alogin"href="<%=request.getContextPath()%>/loginFailure.jsp">로그인</a>--%>
				<button id="alogin" type="button" class="btn btn-primary">로그인</button>
				<button type="button" class="btn btn-danger" data-dismiss="modal">취소</button>
				<button type="button" class="btn btn-info" data-dismiss="modal"
					data-toggle="modal" data-target="#addMember_Modal">회원가입</button>
				<form id="securedLoginForm" name="securedLoginForm" action="loginProcess.net" method="post" style="display:none">
					<input type="hidden" name="secured_id" id="secured_id" value="" />
            		<input type="hidden" name="secured_pass" id="secured_pass" value="" />
				</form>
			</div>

		</div>

스크립트 코드는 다음과 같다.

function validateEncryptedForm() {
    var login_id = $("#login_id").val();
    var login_pass = $("#login_pass").val();
    if (!login_id || !login_pass) {
        alert("ID/비밀번호를 입력해주세요.");
        return false;
    }
    try {
        var rsaPublicKeyModulus = $("#rsaPublicKeyModulus").val();
        var rsaPublicKeyExponent = $("#rsaPublicKeyExponent").val();
        submitEncryptedForm(login_id,login_pass, rsaPublicKeyModulus, rsaPublicKeyExponent);
    } catch(err) {
        alert(err);
    }
    return false;
}

function submitEncryptedForm(login_id, login_pass, rsaPublicKeyModulus, rsaPpublicKeyExponent) {
    var rsa = new RSAKey();
    rsa.setPublic(rsaPublicKeyModulus, rsaPpublicKeyExponent);

    // 사용자ID와 비밀번호를 RSA로 암호화한다.
    var securedlogin_id = rsa.encrypt(login_id);
    var securedlogin_pass = rsa.encrypt(login_pass);

    // POST 로그인 폼에 값을 설정하고 발행(submit) 한다.
    $("#secured_id").val(securedlogin_id);
    $("#secured_pass").val(securedlogin_pass);
    $("#securedLoginForm").submit();
}
$(function() {
		$("#alogin").click(function() {
			validateEncryptedForm();
			return false;
		})
});

 

submit 후에 로그인 처리를 하는 java파일에서 복호화를 해야 한다.

import java.io.PrintWriter;
import java.security.PrivateKey;

import javax.crypto.Cipher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import mvc.member.db.MemberDAO;
public class LoginProcessAction  implements Action {
	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		ActionForward forward = new ActionForward();
		HttpSession session = request.getSession();
		request.setCharacterEncoding("UTF-8");
		response.setContentType("text/html;charset=UTF-8");

		String secured_id = request.getParameter("secured_id");
	    String secured_pass = request.getParameter("secured_pass");

	    PrivateKey privateKey = (PrivateKey) session.getAttribute("__rsaPrivateKey__");
	    session.removeAttribute("__rsaPrivateKey__"); 
	    if (privateKey == null) {
            throw new RuntimeException("암호화 비밀키 정보를 찾을 수 없습니다.");
        }
	    String user_id ="";
	    String user_pass="";
	    try {
			user_id = decryptRsa(privateKey, secured_id);
	        user_pass = decryptRsa(privateKey, secured_pass);
	    }catch (Exception ex) {
            throw new ServletException(ex.getMessage(), ex);
        }
		MemberDAO dao = new MemberDAO();
		int result = dao.isId(user_id, user_pass);


		if(result==1) { //아이디 비밀번호 일치
			//로그인 성공
			session.setAttribute("id", user_id);
			forward.setRedirect(true);
			forward.setPath("main.net");
			return forward;

		}  else {   // 아이디,비밀번호 가 존재하지 않음
			String message = "비밀번호가 일치하지 않습니다.";
			if(result == -1)
				message = "아이디가 존재하지 않습니다.";
			
			System.out.println(message);
			PrintWriter out = response.getWriter();
			
			out.println("<script>");
			out.println("alert('"+message+"');");
			out.println("location.href ='main.net';");
			out.println("</script>");
			out.close();
			return null;
		}
	}
	private String decryptRsa(PrivateKey privateKey, String securedValue) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA");
        byte[] encryptedBytes = hexToByteArray(securedValue);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        String decryptedValue = new String(decryptedBytes, "utf-8"); // 문자 인코딩 주의.
        return decryptedValue;
    }
	public static byte[] hexToByteArray(String hex) {
        if (hex == null || hex.length() % 2 != 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기능을 class로 만들어 표현한 코드는 다음과 같다.

import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;

import javax.crypto.Cipher;

public class RSA {

   private String publicKeyModulus = "";
   private String publicKeyExponent = "";
   private PrivateKey privateKey = null;



   public static RSA getEncKey() {

      KeyPairGenerator generator;
      try {
         generator = KeyPairGenerator.getInstance("RSA");
      } catch (NoSuchAlgorithmException e) {
         e.printStackTrace();
         return null;
      } //RSA키 제네레이터 생성
      generator.initialize(1024); //키 사이즈

      KeyPair keyPair = generator.genKeyPair();

      KeyFactory keyFactory;
      try {
         keyFactory = KeyFactory.getInstance("RSA");
      } catch (NoSuchAlgorithmException e) {
         e.printStackTrace();
         return null;
      }

      PublicKey publicKey = keyPair.getPublic(); //공개키
      PrivateKey privateKey = keyPair.getPrivate(); //개인키

      RSAPublicKeySpec publicSpec;
      try {
         publicSpec = (RSAPublicKeySpec) keyFactory.getKeySpec(publicKey, RSAPublicKeySpec.class);
      } catch (InvalidKeySpecException e) {
         e.printStackTrace();
         return null;
      }
      String publicKeyModulus = publicSpec.getModulus().toString(16);
       String publicKeyExponent = publicSpec.getPublicExponent().toString(16);

       RSA rsa = new RSA();
       rsa.setPrivateKey(privateKey);
       rsa.setPublicKeyExponent(publicKeyExponent);
       rsa.setPublicKeyModulus(publicKeyModulus);

       return rsa;
   }

   public static boolean dec(PrivateKey privateKey, String encString) throws Exception{
      boolean result = false;

      if (privateKey == null) {
            throw new RuntimeException("암호화 비밀키 정보를 찾을 수 없습니다.");
        }
      try {
         decryptRsa(privateKey, encString);
         result = true;
      } catch (Exception e) {
         e.printStackTrace();
         result = false;
      }

      return result;
   }

   public static String decryptRsa(PrivateKey privateKey, String securedValue) throws Exception {
        //System.out.println("will decrypt : " + securedValue);
        Cipher cipher = Cipher.getInstance("RSA");
        byte[] encryptedBytes = hexToByteArray(securedValue);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        String decryptedValue = new String(decryptedBytes, "utf-8"); // 문자 인코딩 주의.
        return decryptedValue;
    }

   public static byte[] hexToByteArray(String hex) {
        if (hex == null || hex.length() % 2 != 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;
    }


   public String getPublicKeyModulus() {
      return publicKeyModulus;
   }
   public void setPublicKeyModulus(String publicKeyModulus) {
      this.publicKeyModulus = publicKeyModulus;
   }
   public String getPublicKeyExponent() {
      return publicKeyExponent;
   }
   public void setPublicKeyExponent(String publicKeyExponent) {
      this.publicKeyExponent = publicKeyExponent;
   }
   public PrivateKey getPrivateKey() {
      return privateKey;
   }
   public void setPrivateKey(PrivateKey privateKey) {
      this.privateKey = privateKey;
   }


}

 

로그인 처리에서 파라미터로 넘어온 id pass를 출력해보면 변형된 값들이 나온다. session으로 id값 저장할 때나 DAO에 인자값으로 넘겨야 할게 있으면 복호화한 값을 보내줘야 한다.

 

참고한 블로그

http://egloos.zum.com/kwon37xi/v/4427199#type=comment&page=2

https://nahosung.tistory.com/5

https://zero-gravity.tistory.com/179

https://vip00112.tistory.com/40

' > JSP' 카테고리의 다른 글

intellij jsp 프로젝트 생성 방법  (0) 2020.08.14
AJAX, JSON(GSON)을 활용해서 회원가입 insert, selectAll 하기  (0) 2019.11.20
AJAX 활용1  (0) 2019.11.19
JSON  (0) 2019.11.19
HashMap, List를 이용해서 회원 가입, 출력  (0) 2019.11.18