RSA 암호화 예제 2

RSA Util
1. Server : RSA키 생성 및 복호화 Util class

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
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

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
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의 로그인 페이지 요청시 키 발급 및 저장/전달

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 로그인 페이지 진입
@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

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
<!-- 유저가 입력하는 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로부터 받은 암호화된 아이디/비밀번호 복호화 후 로그인 로직 실행


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
// 로그인 처리
@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);
}


TAGS.

Comments