단방향 암호화 복호화 - danbanghyang amhohwa boghohwa

암호화란?

암호화 (Encryption) 란 평문을 특정 Key를 이용해 해독할 수 없는 형태로 변경하는 것을 말한다.

방식에 따른 분류

암호화 기법에는 크게 양방향과 단방향 암호화가 있는데

평문을 암호화 한 암호문을 반대로 복호화를 할 수 있는지 여부에 따라서 양방향 / 단방향으로 나눠진다.

여기서는 암호화/복호화 가 가능한 양방향 암호화에 대해서 정리해본다.

양방향

암호화 된 암호문을 복호화 할 수 있는 기법을 의미한다.

따라서 암호화/복호화 시 필요한 Key가 존재 하는데

암호화와 복호화를 같은 키를 사용하느냐 다른 키를 사용하느냐에 따라서 대칭키 / 비대칭키로 분류할 수 있다.

대칭키 (비공개키) 

암/복호화에 동일한 키를 사용하는 암호화 방식이다. 

따라서 키가 노출되면 안되므로 공개하면 안되므로 비공개키 라고도 한다.

대표적으로 AES 알고리즘이 있다.

※ 여기서 사용되는 Key를 Secret 이라고 하는데 Private Key와는 다르다.

   Secret은 노출을 비밀스럽게 해야하는 Key를 의미하고 Private Key는 절대 노출되어선 안되는 Key를 의미한다.

비대칭키 (공개키)

암/복호화에 다른 키를 사용하는 암호화 방식이다.

노출시켜선 안되는 키를 Private Key / 노출시켜도 상관 없는 키를 Public Key 라고 한다.
보통의 경우 데이터를 중요하게 다뤄야 하는 서버 측에서 Private Key를 사용하고,

누구나 사용해야 하는 클라이언트 측에서 Public Key를 사용하게 된다.

대표적으로 RSA 알고리즘을 사용한다.

※ 참고로 전자서명에서는 클라이언트가 사용하는 Key가 중요하게 다뤄져야 하므로 Private Key,

   서버 측에서는 검증만 하면 되므로 Public Key를 사용하게 된다.

단방향

단방향 암호화 복호화 - danbanghyang amhohwa boghohwa
그림2. 단방향 알고리즘 (해쉬)

[그림2] 와 같이 암호문을 평문으로 복호화 할 수 없는 기법을 의미한다.

단방향 기법은 HASH 를 사용하는데 흔히 자료구조에서 사용하던 HASH 가 아니고

암호학적으로, 수학적으로 복호화가 거의 불가능하다고 증명이 된 알고리즘을 사용해야 한다.

인증, 신원증명 등에 주로 쓰이며 대표적으로 SHA-256 알고리즘을 주로 쓴다. 

양방향 암호화(RSA) 단방향 암호화(SHA 256) 차이 

RSA SHA 256
양방향 암호화 = 복호화가 가능하다.  단방향 암호화 = 복호화가 불가능하다.
사용자는 공개된 키와 자신만 아는 개인키를 가진다. hash 함수의 일종
공개키, 개인키로 암호화/복호화를 하는 시스템 SHA 알고리즘의 한 종류로 256비트로 구성
64자리 문자열을 반환
키생성 - 키분배 - 암호화 - 복호화 순으로 이루어짐. 복호화를 하지 않아도 되 속도가 빠르다.

web(JSP) -> server(controller)로 파라미터를 보낼 때 중간에서 정보를 가로챌 수 있다. 

이러한 보안 문제를 막기 위해 JSP에서 RSA를 사용하여 비밀번호를 암호화해준다..

RSA로 암호화된 파라미터를 controller에서 복호화시키고, 

복호화시킨 파라미터 값을 단방향 암호화인 SHA256으로 암호화시킨다.

RSA 암호화 

Rsa.java 

package egovframework.let.utl.sim.service;
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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

public class Rsa {
	
    public static String RSA_WEB_KEY = "_RSA_WEB_Key_"; // 개인키 session key
    public static String RSA_INSTANCE = "RSA"; // rsa transformation

    /**
     * 복호화
     * 
     * @param privateKey
     * @param securedValue
     * @return
     * @throws Exception
     */
    public String decryptRsa(PrivateKey privateKey, String securedValue) throws Exception {
        Cipher cipher = Cipher.getInstance(Rsa.RSA_INSTANCE);
        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 배열로 변환한다.
     * 
     * @param hex
     * @return
     */
    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 공개키, 개인키 생성
     * 
     * @param request
     */
    public void initRsa(HttpServletRequest request) {
        HttpSession session = request.getSession();
 
        KeyPairGenerator generator;
        try {
            generator = KeyPairGenerator.getInstance(Rsa.RSA_INSTANCE);
            generator.initialize(1024);
 
            KeyPair keyPair = generator.genKeyPair();
            KeyFactory keyFactory = KeyFactory.getInstance(Rsa.RSA_INSTANCE);
            PublicKey publicKey = keyPair.getPublic();
            PrivateKey privateKey = keyPair.getPrivate();
 
            session.setAttribute(Rsa.RSA_WEB_KEY, privateKey); // session에 RSA 개인키를 세션에 저장
 
            RSAPublicKeySpec publicSpec = (RSAPublicKeySpec) keyFactory.getKeySpec(publicKey, RSAPublicKeySpec.class);
            String publicKeyModulus = publicSpec.getModulus().toString(16);
            String publicKeyExponent = publicSpec.getPublicExponent().toString(16);
 
            request.setAttribute("RSAModulus", publicKeyModulus); // rsa modulus 를 request 에 추가
            request.setAttribute("RSAExponent", publicKeyExponent); // rsa exponent 를 request 에 추가
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

login.jsp 

RSA 양방향 암호화를 사용하기 위해선 RSA 자바스크립트 라이브러리 추가가 필요하다. 

여기서 js를 다운로드 받아 프로젝트에 넣는걸 추천.

JSEncrypt

Introduction When browsing the internet looking for a good solution to RSA Javascript encryption, there is a whole slew of libraries that basically take the fantastic work done by Tom Wu @ http://www-cs-students.stanford.edu/~tjw/jsbn/ and then modify that

travistidwell.com

<script type="text/javascript" src="http://www-cs-students.stanford.edu/~tjw/jsbn/rsa.js"></script>
<script type="text/javascript" src="http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js"></script>
<script type="text/javascript" src="http://www-cs-students.stanford.edu/~tjw/jsbn/prng4.js"></script>
<script type="text/javascript" src="http://www-cs-students.stanford.edu/~tjw/jsbn/rng.js"></script>
// 비밀번호 암호화
function frm_check(){
 var pw = document.loginForm.password.value;
 // rsa 암호화
 var rsa = new RSAKey();
 rsa.setPublic($('#RSAModulus').val(),$('#RSAExponent').val());
 $("#password").val(rsa.encrypt(pw));
}

서버 측에서 RSA 공개키와 개인키(암호키)를 생성하여 개인키는 세션에 저장하고 공개키는 HTML 로그인 폼 페이지에 Input [type=hidden] value 값에 세팅.

<form:form id="loginForm" name="loginForm" action="${pageContext.request.contextPath}/actionLogin.do" method="post" onsubmit="return frm_check();" >
   <div class="user_login_ultop">
    <input type="hidden" id="RSAModulus" value="${RSAModulus}"/>
    <input type="hidden" id="RSAExponent" value="${RSAExponent}"/>
    <ul>
      <li>
          <label for="id"></label>
          <input type="text" class="input_style" title="아이디를 입력하세요." id="id" name="id" maxlength="10"/>
      </li>
      <li>
          <label for="password"></label>
          <input type="password" class="input_style" maxlength="25" title="비밀번호를 입력하세요." id="password" name="password"/>
      </li>
      <li>
          <input type="checkbox" name="checkId" id="checkId" /><label for="checkId">ID저장</label>
      </li>
    </ul>
  	<input type="submit" alt="로그인 하기" value="로그인" class="btn_style"/>
   </div>
</form:form>
단방향 암호화 복호화 - danbanghyang amhohwa boghohwa

RSA 복호화 

LoginController.java 

Rsa r = new Rsa();
HttpSession session = request.getSession();
//로그인전에 세션에 저장된 개인키를 가져온다.
PrivateKey privateKey = (PrivateKey) session.getAttribute(Rsa.RSA_WEB_KEY);
//암호화 된 비밀번호를 복호화 시킨다.
String password = r.decryptRsa(privateKey, LoginVO.getPassword());
// ShA 256 암호화 = 단방향 
String sha = EgovFileScrty.encryptPassword(password);
LoginVO.setPassword(sha);

SHA 256 암호화 

EgovFileScrty.java 

/**
* 비밀번호를 암호화하는 기능(복호화가 되면 안되므로 SHA-256 인코딩 방식 적용).
* 
* deprecated : 보안 강화를 위하여 salt로 ID를 지정하는 encryptPassword(password, id) 사용
*
* @param String data 암호화할 비밀번호
* @return String result 암호화된 비밀번호
* @exception Exception
*/
@Deprecated
public static String encryptPassword(String data) throws Exception {

  if (data == null) {
  return "";
  }

  byte[] plainText = null; // 평문
  byte[] hashValue = null; // 해쉬값
  plainText = data.getBytes();

  MessageDigest md = MessageDigest.getInstance("SHA-256");

  hashValue = md.digest(plainText);

 return new String(Base64.encodeBase64(hashValue));
}
단방향 암호화 복호화 - danbanghyang amhohwa boghohwa

참고 : 

GitHub - kenu/egov

Contribute to kenu/egov development by creating an account on GitHub.

github.com

단방향 암호화 복호화 - danbanghyang amhohwa boghohwa

RSA (비대칭 암호화 알고리즘)

사용하는 목적WEB 서버에 SSL 설치 없이 로그인처리할때 평문으로 전송할경우 중간에서 정보를 가로채어 가로챈 계정정보를 권한이 없는 사용자가 시스템에 로그인 한후 시스템을 손상시킬수도

velog.io

단방향 암호화 복호화 - danbanghyang amhohwa boghohwa