4,357,481 th visitor since 2017.2.1 ( Today : 1059 )
Programming
No. 738
Name. swindler
Subject. javascript - RSA로 로그인 암호화
Main Cate. Javascript
Sub Cate. HTML
Date. 2014-01-16 11:24
Hit. 7400 (211.36.27.3)
File.
로그인폼 등에서 값이 전송되는 경우 패킷스니핑을 통하여 값을 파악할 수 있는 문제가 발생한다.

가장 쉬운 방법은 SSL을 사용하여 전송계층을 암호화하는 것인데,
SSL을 사용하지 않는 경우에는 비밀번호등이 노출되는 위험이 있다.

이 경우 javascript를 통하여 RSA로 암호화를 할 수 있다.

로그인폼의 데이터를 RSA 공개키로 암호화해서 서버로 전송하므로, 중간에 가로채도 값을 알수가 없다.

이 경우 BigIntegers and RSA in Javascript 라이브러리를 사용할 수 있다.
참고로 이 라이브러리의 BASE64 인코더에 문제가 있다는 글들이 있다. 파폭에서 오동작을 한다나.



1. 공개키와 개인키를 생성
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(KEY_SIZE);

KeyPair keyPair = generator.genKeyPair();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");

PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();

HttpSession session = request.getSession();
// 세션에 공개키의 문자열을 키로하여 개인키를 저장한다.
session.setAttribute("__rsaPrivateKey__", privateKey);

// 공개키를 문자열로 변환하여 JavaScript RSA 라이브러리 넘겨준다.
RSAPublicKeySpec publicSpec = (RSAPublicKeySpec) keyFactory.getKeySpec(publicKey, RSAPublicKeySpec.class);

String publicKeyModulus = publicSpec.getModulus().toString(16);
String publicKeyExponent = publicSpec.getPublicExponent().toString(16);

request.setAttribute("publicKeyModulus", publicKeyModulus);
request.setAttribute("publicKeyExponent", publicKeyExponent);

request.getRequestDispatcher("/WEB-INF/views/loginForm.jsp").forward(request, response);



2. HTML
<!-- script 태그에서 가져오는 자바스크립트 파일의 순서에 주의해야한다! 순서가 틀릴경우 자바스크립트 오류가 발생한다. -->
<script type="text/javascript" src="<%=request.getContextPath()%>/js/rsa/jsbn.js"></script>
<script type="text/javascript" src="<%=request.getContextPath()%>/js/rsa/rsa.js"></script>
<script type="text/javascript" src="<%=request.getContextPath()%>/js/rsa/prng4.js"></script>
<script type="text/javascript" src="<%=request.getContextPath()%>/js/rsa/rng.js"></script>
<script type="text/javascript" src="<%=request.getContextPath()%>/js/login.js"></script>
</head>
<body>
<div>
<label for="username">사용자ID : <input type="text" id="username" size="16"/></label>
<label for="password">비밀번호 : <input type="password" id="password" size="16" /></label>
<input type="hidden" id="rsaPublicKeyModulus" value="<%=publicKeyModulus%>" />
<input type="hidden" id="rsaPublicKeyExponent" value="<%=publicKeyExponent%>" />
<a href="<%=request.getContextPath()%>/loginFailure.jsp" onclick="validateEncryptedForm(); return false;">로그인</a>
</div>
<form id="securedLoginForm" name="securedLoginForm" action="<%=request.getContextPath()%>/login" method="post" style="display: none;">
<input type="hidden" name="securedUsername" id="securedUsername" value="" />
<input type="hidden" name="securedPassword" id="securedPassword" value="" />
</form>
</body>

엔터를 쳐서 submit이 되는것을 막아야 한다.
위 예의 경우에는 입력용 폼과 submit용 폼을 분리한 것인데, 그닥 좋은 방법은 아닌것 같다.


3. javascript 암호화
function validateEncryptedForm() {
var username = document.getElementById("username").value;
var password = document.getElementById("password").value;
if (!username || !password) {
alert("ID/비밀번호를 입력해주세요.");
return false;
}

try {
var rsaPublicKeyModulus = document.getElementById("rsaPublicKeyModulus").value;
var rsaPublicKeyExponent = document.getElementById("rsaPublicKeyExponent").value;
submitEncryptedForm(username,password, rsaPublicKeyModulus, rsaPublicKeyExponent);
} catch(err) {
alert(err);
}
return false;
}

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

// 사용자ID와 비밀번호를 RSA로 암호화한다.
var securedUsername = rsa.encrypt(username);
var securedPassword = rsa.encrypt(password);

// POST 로그인 폼에 값을 설정하고 발행(submit) 한다.
var securedLoginForm = document.getElementById("securedLoginForm");
securedLoginForm.securedUsername.value = securedUsername;
securedLoginForm.securedPassword.value = securedPassword;
securedLoginForm.submit();



4. 서버측 복호화
/**
* 암호화된 비밀번호를 복호화 한다.
*/
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String securedUsername = request.getParameter("securedUsername");
String securedPassword = request.getParameter("securedPassword");

HttpSession session = request.getSession();
PrivateKey privateKey = (PrivateKey) session.getAttribute("__rsaPrivateKey__");
session.removeAttribute("__rsaPrivateKey__"); // 키의 재사용을 막는다. 항상 새로운 키를 받도록 강제.

if (privateKey == null) {
throw new RuntimeException("암호화 비밀키 정보를 찾을 수 없습니다.");
}
try {
String username = decryptRsa(privateKey, securedUsername);
String password = decryptRsa(privateKey, securedPassword);
request.setAttribute("username", username);
request.setAttribute("password", password);
request.getRequestDispatcher("/WEB-INF/views/login.jsp").forward(request, response);
} catch (Exception ex) {
throw new ServletException(ex.getMessage(), ex);
}
}

private 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;
}

/**
* 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];
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;
}




참고자료
http://wiki.kldp.org/wiki.php/DocbookSgml/SSL-Certificates-HOWTO SSL 인증서 HOWTO
http://kwon37xi.egloos.com/4427199 RSA기반 웹페이지 암호화 로그인 : 내가 하고자 하던 내용에 대한 기본 작업, 마지막 항목에 나와 있는 이중 암호화를 추가함
http://ohdave.com/rsa/ 자바 스크립트로 구현한 RSA
http://www.fourmilab.ch/javascrypt/ 자바 스크립트 기반 암호화 툴들
http://people.eku.edu/styere/ 자바스크립트 기반 암호화 예제
http://www-cs-students.stanford.edu/~tjw/jsbn/ RSA, BigInteger 소스 : 이곳 자바스크립트 소스를 RSA Encrypt용으로 사용함
http://www.movable-type.co.uk/ 자바스크립트 암호화 : TEA 라는 암호화 방식으로 이중암호화용으로 사용함


[바로가기 링크] : http://coolx.net/cboard/develop/738



Name
Password
Comment
Copyright © 1999-2017, swindler. All rights reserved. 367,611 visitor ( 1999.1.8-2004.5.26 ), 2,405,771 ( -2017.01.31)

  2HLAB   2HLAB_Blog   RedToolBox   Omil   Omil_Blog