웹 페이지에서 SSL 없이 RSA 암호화 로그인 하기

사용자의 비밀번호를 전송할 때는 SSL 등의 처리를 하지 않으면 해당 비밀번호를 중간에 가로채서 보는 것이 가능하다. 그러나 비영리 싸이트 혹은 SSL 인증서 구매가 어려운 경우에 JavaScript로 RSA 암호화를 이용해서 암호화된 로그인이 가능하다.

RSA는 비대칭 방식으로 암호화는 공개키(누구나 볼 수 있다)로 하고 복호화는 개인키를 가진쪽만 가능한 형태이다.

사용자가 로그인 폼을 채우면 사용자 ID와 비밀번호를 RSA 공개키로 암호화해서 전송하여, 중간에 패킷을 가로채도 해석이 불가능하게 만드는 것이다.

 

이와 같은 것을 구현하고 싶어진 계기는 회사 내부적으로 사용하는 운영용 싸이트의 로그인 정보를 암호화해야 겠다는 생각이 들었기 때문이다. 그러면서  돈 안 쓰고 특정 브라우저에 종속되지 않으면서도 안전한 사이트 이용을 가능하게 하는 방법을 찾다가 메가박스 홈페이지가 2010년 6월 현재 SSL과 ActiveX없이 그렇게 구현돼 있는 것을 보았다.

이와 같은 로그인 및 데이터 전송 방식에 대해서는 알아야 막는다 자바 JSP 해킹과 보안 책에서 정보를 얻어서BigIntegers and RSA in JavaScript 라이브러리를 사용하였다.

참고로 해당 라이브러리에서 제공하는 BASE64 인코더에 문제가 있는 것으로 보인다. 책에서 소개한대로 이 BASE64 인코딩 라이브러리를 사용하면 FireFox에서는 오동작을 한다. 그래서 BASE64를 사용하지 않고 암호화된 바이트 배열을 그냥 16진 문자열(hex)로 서버에 전송한다.

알아야 막는다 자바 JSP 해킹과 보안 책의 방식대로 하면 세션에 무한정 PrivateKey를 저장해서 메모리 누수를 일으킬 수 있다. 이 책 그대로 따라하면 안된다.

 

사실 그냥 HTTPS 사용하면 더 안전하게 아무 처리 없이 다 해결되는 문제들이다. 하지만 내부 적으로만 사용하는 서비스들에 대해서 일일이 다 인증서를 등록할 수는 없으니 지금 소개하는 방식이 가장 쉽고 돈 안들이면서 안전하게 로그인 할 수 있는 방법이 되어 줄 것이다.

 

기본 작동 원리

  1. [서버] 서버측에서 RSA 공개키와 개인키(비밀키)를 생성하여, 개인키는 세션에 저장하고 공개키는 자바스크립트 로그인 폼이 있는 페이지로 출력한다.
  2. [클라이언트] 로그인폼은 자바스크립트가 실행되지 않는 환경에서는 발행(submit)이 되면 안된다.
  3. [클라이언트] 로그인폼에 사용자의 ID와 비밀번호를 넣고 발행을 누르면 자바스크립트가 이를 가로챈다.

    1. 사용자 ID를 RSA로 암호화하여 화면에 안보이는 새로운 폼에 저장한다.
    2. 비밀번호를 RSA로 암호화하여 화면에 안보이는 새로운 폼에 저장한다.
    3. 이제 화면에 안보이는 해당 폼을 서버로 발행한다.
  4. [서버] 서버측에서 세션에 저장된 RSA개인키로 암호화된 사용자ID와 비밀번호를 복호화한다.
  5. [서버] 데이터베이스/혹은 기타 저장소에 저장된 사용자 ID와 비밀번호가 일치하는지 확인한다.

 

주의할 점. 암호화 된 값은 byte 배열이다. 이를 문자열 폼으로 전송하기 위해 16진 문자열(hex)로 변경한다. 서버측에서도 값을 받을 때 hex 문자열을 받아서 이를 다시 byte 배열로 바꾼 뒤에 복호화 과정을 수행한다.

 

Java 로그인 폼 측 키생성

로그인 폼을 보여주는 화면을 출력할 때, 그와 동시에 공개키와 개인키를 생성해서 공개키는 HTML/Javascript에서 접근 가능하게 노출하고 개인키는 HTTP 세션에 보관하여 암호를 풀 때 사용하도록 처리한다.

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

 

loginForm.jsp 에서 실제로 폼을 출력하는 역할을 한다.

 

HTML 폼

  1.         <!-- 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 버튼을 두게되면 사용자가 그냥 엔터를 쳐도 폼이 제출되게 된다. 이렇게 되면 사용자의 웹 브라우저가 Javascript를 지원하지 않아도 폼이 제출되므로 사용자가 쓴 아이디와 비밀번호가 그냥 전송되게 돼 버린다.

사용자가 ID와 비밀번호를 친 뒤에 무조건 Javascript를 타게 만들기 위해 입력용 폼과 Javascript로 암호화하여 실제로 제출하는 폼을 분리한 것이다.

만약 사용자의 브라우저가 javascript를 지원하지 않는다면, 로그인 링크의 loginFailure.jsp 페이지가 보여지게 된다.

 

자바스크립트 암호화 처리

  1. 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();

 

 

실질적인 암호화는 submitEncryptedForm에서 이뤄진다. 공개키의 rsaPublicKeyModulus와 rsaPublicKeyExponent 값을 읽어 RSA 객체를 구성하고 이를 가지고 사용자의 ID와 비밀번호를 모두 암호화하여 전송용 폼에 암호화된 값을 지정하고 폼을 제출(submit)한다.

 

Java 측 복호화하여 사용자 ID,비밀번호 확인

  1.     /**
         * 암호화된 비밀번호를 복호화 한다.
         */
        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 세션에서 앞서 저장한 개인키를 읽는다. 만약 변조된 공개키로 암호화를 했거나 개인키가 존재하지 않는 상황이라면 물론 오류가 발생한다.

그리고 세션에서 개인키를 지워버린다. 절대로 동일 개인키로 두 번이상 로그인 할 수 없게 만들었다.

 

어떻게 전송되나

아래 이미지는 실제 전송되는 데이터를 Charles 라는 HTTP 프록시 툴로 살펴본 것이다. 사용자의 ID와 비밀번호가 모두 암호화되어 원형을 알아 볼 수 없음을 볼 수 있다.

 

rsa_login_charles.png

 

이상이 끝. 관련 파일을 모두 함께 올려둔다. WAR 는 실행하고서 http://localhost:8080/RSATest 로 접속하면 된다.

RSATest.war LoginServlet.java LoginFormServlet.java

 

기타

사실 정말로 원한 것은 AES로 사용자의 정보를 암호화하고 AES키를 RSA공개키로 암호화하여 서버에 전달 한뒤, 서버에서 RSA 개인키로 AES키를 복호화해 알아내고, 그 AES키로 다시 사용자 정보를 복호화하는 이중 암호화를 시도했었다.

그러나 그닥 녹록치 않아서, 일단 RSA 기반의 단일 암호화로 만족한 상태이다.


출처 - http://kwon37xi.egloos.com/4427199






개요

일반적인 form 전송 방식은 모든 데이타가 인터네상에 깔끔하게 보이게 된다. 즉, 비밀번호 1234를 입력하면 1234을 그대로 알 수가 있다. 항상 이 부분을 찜찜하게 생각하고 있었는데 일반적인 해결책은 HTTPS를 이용하면 되는데 호스팅 업체에서 이걸 지원 해줄 리도 없기에 그냥 아쉬운 데로 그냥 사용하고 있었다.

 

회사에 security 관련 업무를 잠깐 할 일이 있었는데 그 일을 하면서 RSA 라는 것을 접하게 되었고 그걸 적용하면 이 문제를 해결 할 수 있을 것 같아 적용해 보았다. 여기서 사용한 방식은 단순히 재미로 구현 한 것으로 검증절차나 보안상에 취약점에 대한 고려는 전혀 하지 않은 것임을 알아줬으면 한다.

 

기본 개념

암호화에 대한 정의나 알고리즘들은 인터넷상에서 쉽게 찾을 수 있고 제가 여기선 그런 개념을 언급 하는 것 또한 무의미 할 것이다. 사실은 누구에게 설명 해줄만큼 잘 알질 못한다. 아무 개념도 없다면 RSA, 공개키, 개인키, 쌍키, AES, DES  등으로 인터넷 검색을 해 보면 될 것이다.

여기서는 구현을 위해 찾아두었던 두었던 사이트 링크로 대신한다.

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 라는 암호화 방식으로 이중암호화용으로 사용함

 

전체 구조

클라이언트에서 form 데이타로 전송되는 text data를 보호하기 위하여 TEA(Tiny Encryption Algorithm)을 이용하여 암호화 하였다. 이때 암호화를 위한 키는 클라이언트(자바스크립트)에서 랜덤하게 생성을 한다.

TEA 암호화는 대칭키 알고리즘이기에 암호화한 키를 이용하여 복호화가 가능하다. 그렇기 때문에 이렇게 생성된 키를 RSA Public Key를 이용하여 암호화를 한 후 키를 서버로 전달한다.  이때 사용 된 RSA Public Key는 로그인 폼 페이지 요청시 서버에서 클라이언트로 전달이 된다. 서버에는 이 Public Key에 해당되는 Private Key를 세션에 저장을 해 두고 클라이언트에서 Request가 오면 복호화에 이용하다.

 

왜 굳이 RSA Public Key를 이용하여 암호화를 하지 않느냐는 의문이 들 수 있는데 - 내가 들었기에.. 그래서 대략 찾아본 것임, 정확한지는 모르겠음, 의심이 되면 찾아보시길 - RSA키는 공개키 방식으로 암호화, 복호화 연산 수행시간이 오래 걸린다. 단순히 로그인과 같은 짧은 텍스트인 경우에는 크게 문제가 되지 않지만 그 크기가 커질 수록 수행시간이 오래 걸린다. 

 

전첵적인 흐름은 대략 다음과 같다. 구글 Docs에 있는 Drawing 도구를 이용해 보았다.

 

 

구현

구현함에 있어 항상 고려해야 할 대상은 클라이언트는 자바스크립트를 사용하고 서버는 자바를 이용해서 구현을 해야 한다는 것이다.

이러한 고려 사항 때문에 AES가 아닌 TEA라는 암호화를 사용하였다. 자바스크립트, 자바 둘 다 암호화 관련된 지식과 소스를 갖고 있질 않아 가장 구현하기 쉬운 것을 선택하였다. 아무래도 AES가 TEA보다 보안레벨이 높겠지만 여기서는 그 정도 수준까지 필요하지도 않고 단순히 인터넷상에서 내가 보내는 데이타가 보이지만 않을 정도이면 만족한다.

 

TEA 암호화

http://www.movable-type.co.uk/scripts/tea-block.html

위의 사이트 소스를 라이브러리로 하여 Tea.encrypt 함수를 이용하였으며 다음과 같이 키를 생성 함수를 추가하여 암호화를 하였다.

1
2
3
4
5
6
7
8
9
function GenerateKey(){
    time = new Date().getTime();
    random = Math.floor(65536*Math.random());
    return (time*random).toString();   
}
 
function EncryptTEA(k, text){  
    return Tea.encrypt(text, k);   
}

TEA를 이용한 가장 큰 이유가 서버에서의 복호화 과정을 쉽게 구현 할 수 있었기 때문이다. 자바스크립트 소스를 이용하여 자바 소스를 생성하였다.

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
151
152
153
154
155
156
import java.util.Arrays;
import ks.util.Base64;
 
public class TEA {
 
    private final int delta = 0x9E3779B9;
 
    private int[] S = new int[4];
 
    /**
     * Initialize the cipher for encryption or decryption.
     * @param key a 16 byte (128-bit) key
     */
    public TEA(byte[] key) {
        if (key == null)
            throw new RuntimeException("Invalid key: Key was null");
        if (key.length < 16)
            throw new RuntimeException("Invalid key: Length was less than 16 bytes");
        for (int off=0, i=0; i<4; i++) {
            S[i] = ((key[off++] & 0xff)) |
            ((key[off++] & 0xff) <<  8) |
            ((key[off++] & 0xff) << 16) |
            ((key[off++] & 0xff) << 24);
        }
 
//      System.out.println("KEY:" + Arrays.toString(S));
    }
     
    public TEA(String key){
        this(key.getBytes());
    }
 
    /*
     * encrypt text using Corrected Block TEA (xxtea) algorithm
     *
     * @param {string} plaintext String to be encrypted (multi-byte safe)
     * @param {string} password  Password to be used for encryption (1st 16 chars)
     * @returns {string} encrypted text
     */
    public byte[] encrypt(byte[] clear){
 
        int[] v = strToLongs(clear);       
        int n = v.length;
 
        // ---- <TEA coding> ----
        int z = v[n-1];
        int y = v[0];
 
        int mx, e;
        int q = 6 + 52/n;
        int sum = 0;
 
        while (q-- > 0) {  // 6 + 52/n operations gives between 6 & 32 mixes on each word
            sum += delta;
            e = sum>>>2 & 3;
            for (int p = 0; p < n; p++) {
                y = v[(p+1)%n];
                mx = (z>>>5 ^ y<<2) + (y>>>3 ^ z<<4) ^ (sum^y) + (S[p&3 ^ e] ^ z);
                z = v[p] += mx;
            }
        }
        // ---- </TEA> ----
        return longsToStr(v);
    }
 
    /*
     * decrypt text using Corrected Block TEA (xxtea) algorithm
     *
     * @param {byte[]} ciphertext byte arrays to be decrypted
     * @returns {byte[]} decrypted array
     */
    public byte[] decrypt(byte[] crypt){
        int[] v = strToLongs(crypt);
        int n = v.length;
 
        // ---- <TEA decoding> ----
        int z = v[n-1];
        int y = v[0];
 
        int mx, e;
        int q = 6 + 52/n;
        int sum = q*delta;
 
        while (sum != 0) {
            e = sum>>>2 & 3;
            for (int p = n-1; p >= 0; p--) {
                z = v[p>0 ? p-1 : n-1];
                mx = (z>>>5 ^ y<<2) + (y>>>3 ^ z<<4) ^ (sum^y) + (S[p&3 ^ e] ^ z);
                y = v[p] -= mx;
            }
            sum -= delta;
        }
 
        // ---- </TEA> ----
         
        byte[] plainBytes = longsToStr(v);
         
        // strip trailing null chars resulting from filling 4-char blocks:
        int len;       
        for(len=0; len<plainBytes.length; len++){
            if(plainBytes[len] == 0)
                break;
        }
         
        byte[] plainTrim = new byte[len];
        System.arraycopy(plainBytes, 0, plainTrim, 0, len);
                 
        return plainTrim;
    }
     
     
    /*
     * decrypt text using Corrected Block TEA (xxtea) algorithm
     *
     * @param {string} ciphertext String to be decrypted
     * @returns {string} decrypted text
     */
    public String decrypt(String ciphertext){
        String plainText = null;       
        byte[] plainTextBytes = decrypt(Base64.decode(ciphertext));
        try{
            plainText = new String(plainTextBytes, "UTF-8");
        } catch(Exception e){}
         
        return plainText;      
    }
 
    private int[] strToLongs(byte[] s) {  // convert string to array of longs, each containing 4 chars
        // note chars must be within ISO-8859-1 (with Unicode code-point < 256) to fit 4/long
        int[] l = new int[(s.length + 3)/4];
 
        for (int i=0; i<l.length; i++) {
            // note little-endian encoding - endianness is irrelevant as long as
            // it is the same in longsToStr()
            l[i] = (s[i*4+0]&0xff)<<0 |
            (s[i*4+1]&0xff)<<8 | 
            (s[i*4+2]&0xff)<<16 |
            (s[i*4+3]&0xff)<<24;
        }
 
        return l;  // note running off the end of the string generates nulls since
    }
 
    private byte[] longsToStr(int[] l){ // convert array of longs back to string
        byte[] a = new byte[l.length*4];
 
        for (int i=0; i<l.length; i++) {
            a[i*4+0] = (byte)((l[i]>>0)&0xff);
            a[i*4+1] = (byte)((l[i]>>8)&0xff);
            a[i*4+2] = (byte)((l[i]>>16)&0xff);
            a[i*4+3] = (byte)((l[i]>>24)&0xff);
        }
 
        return a;
    }
}

 

Base64 소스

 

RSA 암호화

RSA 키는 클라이언트 세션이 이루어질 때 생성을 한다. 자바에서 RSA 관련함수를 제공하는 패키지가 존재하는데 여기서는 BigInteger 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
import java.math.BigInteger;
import java.security.SecureRandom;
 
public class RSAKey {
    private BigInteger privateExponent;
    private BigInteger publicExponent;
    private BigInteger modulus;
 
    public RSAKey(BigInteger d, BigInteger e, BigInteger m){
        this.privateExponent = d;
        this.publicExponent = e;
        this.modulus = m;
    }
 
    public BigInteger getPrivateExponent(){ return this.privateExponent; }
    public BigInteger getPublicExponent(){ return this.publicExponent; }
    public BigInteger getModulus(){ return this.modulus; }
     
    public static RSAKey generate(int nbit){
        // generate an N-bit (roughly) public and private key
 
        SecureRandom random = new SecureRandom();
        BigInteger one      = new BigInteger("1");
 
        BigInteger p = BigInteger.probablePrime(nbit/2, random);
        BigInteger q = BigInteger.probablePrime(nbit/2, random);
        BigInteger phi = (p.subtract(one)).multiply(q.subtract(one));
 
        BigInteger m = p.multiply(q);
        BigInteger e = new BigInteger("65537");     // common value in practice = 2^16 + 1
        BigInteger d = e.modInverse(phi);
         
        return new RSAKey(d, e, m);
    }
     
    public static String toHex (BigInteger value) {
        byte b[] = value.toByteArray();
         
        StringBuffer strbuf = new StringBuffer(b.length * 2);
        int i;
 
        for (i = 0; i < b.length; i++) {
            if (((int) b[i] & 0xff) < 0x10)
                strbuf.append("0");
 
            strbuf.append(Long.toString((int) b[i] & 0xff, 16));
        }
 
        return strbuf.toString();
    }
}

 

클라이언트측의 RSA 암호화 관련된 함수는 http://www-cs-students.stanford.edu/~tjw/jsbn/ 를 참조하여 구현하였다.

서버측에서 복호화는 다음과 같이 구현된 함수를 이용하였다.

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
import java.math.BigInteger;
 
public class RSA {
 
    private RSAKey key;
     
    public RSA(int nbit){
        key = RSAKey.generate(nbit);
    }
     
    public RSA(){
        this(1024);
    }
     
    public RSA(RSAKey key){
        this.key = key;
    }
         
    public String decrypt(String encText){
        BigInteger modulus = key.getModulus();
        BigInteger privateExponent = key.getPrivateExponent();
          
        BigInteger enc = new BigInteger(encText, 16);
        BigInteger dec = enc.modPow(privateExponent, modulus);
                 
        String plainText =  new String(dec.toByteArray());
         
        return plainText;
    }
 
    public static String asHex (byte buf[]) {
        StringBuffer strbuf = new StringBuffer(buf.length * 2);
        int i;
 
        for (i = 0; i < buf.length; i++) {
            if (((int) buf[i] & 0xff) < 0x10)
                strbuf.append("0");
 
            strbuf.append(Long.toString((int) buf[i] & 0xff, 16));
        }
        return strbuf.toString();
    }  
}

 

 

마무리

단순히 붙여 넣기만 하는데도 생각보다 오랜 시간이 소요가 되었으며 에디터가 생각만큼 제대로 동작을 하지 않는다. 소스 하이라이트 기능과 목록 기능을 이용하기에는 많이 부족한 듯 하다.
 
차후에 기회가 된다면 다시 한번 정리를 할 것이며 혹시라도 이런 구조를 이용해보고 싶은 분이 있으시다면 여기서 사용된 전체 소스들을 전달해 드리도록 하겠습니다.
 
* 사용하고 있는 암호화 관련 javascript, login form, Java 소스들을 대략 뽑아서 첨부합니다. 구현하는데 도움이 되길 바랍니다


출처 - http://tvnuri.tistory.com/entry/%EC%95%94%ED%98%B8%ED%99%94%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9B%B9%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%B3%B4%EC%95%88




Posted by linuxism
,