티스토리 뷰
기본적으로 Client와 Server간의 데이터 암호화는 SSL을 이용하는게 정석이다.
부득이한 사정으로 인하여 SSL을 이용하지 못할경우 대안방법으로 RSA 알고리즘을 이용하여 암/복호화를 한다.
(SSL 대신 이용해도 된다는 뜻은 아님.)
- 필요한 라이브러리
Server : 없음
Client : jsbn.js, prng4.js, rng.js, rsa.js
(출처 : http://www-cs-students.stanford.edu/~tjw/jsbn/)
- 프로세스
Client | Server |
1. 로그인 페이지 요청 | |
2-1. 개인키 생성 후 session에 저장 2-2. 공개키 생성 후 view 페이지로 전달 |
|
3-1. 아이디/비밀번호 입력 후 submit 3-2. submit event 발생시 Server에서 받은 공개키로 아이디/비밀번호 암호화 3-3. 암호화된 아이디/비밀번호를 Server로 전송 |
|
4-1. session에 개인키가 있는지 확인(없으면 error) 4-2. session에서 얻은 개인키로 parameter로 넘어온 암호화된 아이디/비밀번호 복호화 4-3. 로그인 로직 실행 |
- 기능 구현
1. Server : RSA키 생성 및 복호화 Util class
package com.gt.board.util;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gt.board.vo.other.RSA;
/** Client -> Server 데이터 전송간 암호화 기능을 담당 **/
public class RSAUtil {
private static final Logger logger = LoggerFactory.getLogger(RSAUtil.class);
private KeyPairGenerator generator;
private KeyFactory keyFactory;
private KeyPair keyPair;
private Cipher cipher;
public RSAUtil() {
try {
generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(1024);
keyFactory = KeyFactory.getInstance("RSA");
cipher = Cipher.getInstance("RSA");
} catch (Exception e) {
logger.warn("RSAUtil 생성 실패.", e);
}
}
/** 새로운 키값을 가진 RSA 생성
* @return vo.other.RSA **/
public RSA createRSA() {
RSA rsa = null;
try {
keyPair = generator.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
RSAPublicKeySpec publicSpec = keyFactory.getKeySpec(publicKey, RSAPublicKeySpec.class);
String modulus = publicSpec.getModulus().toString(16);
String exponent = publicSpec.getPublicExponent().toString(16);
rsa = new RSA(privateKey, modulus, exponent);
} catch (Exception e) {
logger.warn("RSAUtil.createRSA()", e);
}
return rsa;
}
/** 개인키를 이용한 RSA 복호화
* @param privateKey session에 저장된 PrivateKey
* @param encryptedText 암호화된 문자열 **/
public String getDecryptText(PrivateKey privateKey, String encryptedText) throws Exception {
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedBytes = cipher.doFinal(hexToByteArray(encryptedText));
return new String(decryptedBytes, "UTF-8");
}
// 16진수 문자열을 byte 배열로 변환
private 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;
}
}
2. Server : RSA 개인키/공개키를 담는 VO
package com.gt.board.vo.other;
import java.security.PrivateKey;
public class RSA {
private PrivateKey privateKey;
private String modulus;
private String exponent;
public RSA() {
}
public RSA(PrivateKey privateKey, String modulus, String exponent) {
this.privateKey = privateKey;
this.modulus = modulus;
this.exponent = exponent;
}
public PrivateKey getPrivateKey() {
return privateKey;
}
public void setPrivateKey(PrivateKey privateKey) {
this.privateKey = privateKey;
}
public String getModulus() {
return modulus;
}
public void setModulus(String modulus) {
this.modulus = modulus;
}
public String getExponent() {
return exponent;
}
public void setExponent(String exponent) {
this.exponent = exponent;
}
}
3. Server : Client의 로그인 페이지 요청시 키 발급 및 저장/전달
// 로그인 페이지 진입
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String loginForm(HttpSession session, Model model) {
// RSA 키 생성
PrivateKey key = (PrivateKey) session.getAttribute("RSAprivateKey");
if (key != null) { // 기존 key 파기
session.removeAttribute("RSAprivateKey");
}
RSA rsa = rsaUtil.createRSA();
model.addAttribute("modulus", rsa.getModulus());
model.addAttribute("exponent", rsa.getExponent());
session.setAttribute("RSAprivateKey", rsa.getPrivateKey());
return "login";
}
4. Client : Server로부터 받은 공개키를 이용하여 parameter를 암호화 후 submit
<!-- 유저가 입력하는 form -->
<form action="/login" method="post" id="loginForm">
<fieldset>
<legend class="screen_out">로그인 폼</legend>
<label for="email">이메일</label>
<input type="text" id="email" name="email" autofocus autocomplete="off" required />
<label for="password">비밀번호</label>
<input type="password" id="password" name="password" autocomplete="off" required />
<button type="submit">
<i class="fa fa-sign-in"></i> 로그인
</button>
</fieldset>
</form>
<!-- 실제 서버로 전송되는 form -->
<form action="/login" method="post" id="hiddenForm">
<fieldset>
<input type="hidden" name="email" />
<input type="hidden" name="password" />
</fieldset>
</form>
<!-- javascript lib load -->
<script src="/resources/js/jquery.min.js"></script>
<script src="/resources/js/rsa/jsbn.js"></script>
<script src="/resources/js/rsa/prng4.js"></script>
<script src="/resources/js/rsa/rng.js"></script>
<script src="/resources/js/rsa/rsa.js"></script>
<!-- 유저 입력 form의 submit event 재정의 -->
<script>
var $email = $("#hiddenForm input[name='email']");
var $password = $("#hiddenForm input[name='password']");
// Server로부터 받은 공개키 입력
var rsa = new RSAKey();
rsa.setPublic("${modulus}", "${exponent}");
$("#loginForm").submit(function(e) {
// 실제 유저 입력 form은 event 취소
// javascript가 작동되지 않는 환경에서는 유저 입력 form이 submit 됨
// -> Server 측에서 검증되므로 로그인 불가
e.preventDefault();
// 아이디/비밀번호 암호화 후 hidden form으로 submit
var email = $(this).find("#email").val();
var password = $(this).find("#password").val();
$email.val(rsa.encrypt(email)); // 아이디 암호화
$password.val(rsa.encrypt(password)); // 비밀번호 암호화
$("#hiddenForm").submit();
});
</script>
5. Server : Client로부터 받은 암호화된 아이디/비밀번호 복호화 후 로그인 로직 실행
// 로그인 처리
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(User user, HttpSession session, RedirectAttributes ra) {
// 개인키 취득
PrivateKey key = (PrivateKey) session.getAttribute("RSAprivateKey");
if (key == null) {
ra.addFlashAttribute("resultMsg", "비정상 적인 접근 입니다.");
return "redirect:/login";
}
// session에 저장된 개인키 초기화
session.removeAttribute("RSAprivateKey");
// 아이디/비밀번호 복호화
try {
String email = rsaUtil.getDecryptText(key, user.getEmail());
String password = rsaUtil.getDecryptText(key, user.getPassword());
// 복호화된 평문을 재설정
user.setEmail(email);
user.setPassword(password);
} catch (Exception e) {
ra.addFlashAttribute("resultMsg", "비정상 적인 접근 입니다.");
return "redirect:/login";
}
// 로그인 로직 실행
// userService.login(user);
}
'Java' 카테고리의 다른 글
Java: 비밀번호 암호화를 위한 Bcrypt Hash 구현 (4) | 2017.02.08 |
---|---|
Java: Cafe24 단독 Tomcat 호스팅 서버에 WAR 배포시 주의사항 (0) | 2016.10.15 |
Java: Spring project 생성 [ Spring Legacy Project ] (0) | 2016.10.14 |
Java: Spring 프로젝트에 자동 방지 captcha 적용(OCR) (6) | 2016.10.07 |
Java: Spring프로젝트에 이메일 인증 기능 추가 (28) | 2016.10.07 |
댓글
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday