고래씌

[MyBatis] 2. 암호화 설정 본문

Server/MyBatis

[MyBatis] 2. 암호화 설정

고래씌 2023. 12. 24. 20:49

1. 단방향 암호화

 
■ PasswordEncryFilter.java



import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

import com.kh.common.wrapper.PasswordEncryptWrapper;

/**
 * Servlet Filter implementation class PasswordEncryFilter
 */
@WebFilter(servletNames = {"loginServlet", "insertServlet"})
public class PasswordEncryFilter implements Filter {

    /**
     * Default constructor. 
     */
    public PasswordEncryFilter() {
    }

	/**
	 * @see Filter#destroy()
	 */
	public void destroy() {
	}

	/**
	 * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
	 */
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		PasswordEncryptWrapper pew = new PasswordEncryptWrapper((HttpServletRequest)request);
		chain.doFilter(pew, response);
		// request대신 pew가 들어감으로써 pew 객체가 실행됨
		
	}

	/**
	 * @see Filter#init(FilterConfig)
	 */
	public void init(FilterConfig fConfig) throws ServletException {
	}

}

 
■ Password EncryptWraaper.java

package com.kh.common.wrapper;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;


public class PasswordEncryptWrapper extends HttpServletRequestWrapper{

	public PasswordEncryptWrapper(HttpServletRequest request) {
		super(request);
		
	}
	@Override
	public String getParameter(String name) {
		String value = "";
		
		//매개변수로 전달받은 name변수의 값이 userPwd일 경우 암호화하기
		if(name.equals("userPwd")) {
			
			System.out.println("암호화 전 pwd : " + super.getParameter(name));
			//암호화 작업
			value = getSHA512(super.getParameter(name));
			System.out.println("암호화 후 pwd : " + value);
		}else {
			value = super.getParameter(name);//전달받은 매개변수가 비밀번호가 아닐 경우 원래 가지고 있던 값 그대로 반환
			// PasswordEncryptWrapper(HttpServletRequest request) { super(request)이니까 앞에 super 붙임}
		}
		return value;
	}
	
	
	// 암호화 작업
	private String getSHA512(String val) {
		
		//암호화 처리 객체 선언
		MessageDigest md = null;
		
		try {
			//사용할 암호화 알고리즘을 선택해서 객체 생성
			md = MessageDigest.getInstance("SHA-512");
		} catch (NoSuchAlgorithmException e) {
			
			e.printStackTrace();
		}
		//암호화시에는 BIT 단위 연산을 한다.
		// 인코딩 설정을 UTF-8로 설정하여 문자열을 byte 단위로 변환함(getBytes)
		md.update(val.getBytes(Charset.forName("UTF-8")));
		
		//비트연산한 결과값(byte[])을 String배열로 변환
		return Base64.getEncoder().encodeToString(md.digest());
		// getEncoder는 Base62의 static 메소드임
		
	}
	
}

 
 
이메일, 주소, 핸드폰 번호는 양방향 암호화을 이용해서 암호화하고 비밀번호는 단방향 암호화 이용
 
▶ DML문일 경우
<insert id="식별자 parameterType="전달받은 자바타입">

</insert>

<update></update>
<delete></delete>

=> dml문은 처리된 행의 갯수를 반환하기 때문에 resultType이나 resultMap 생략 가능하다
 
 
 


2. 양방향 암호화 지원 클래스 만들기

 
 
▶ lister 파일 생성 후, (어떤 이벤트를 감지할 것인가) 아래와 같이 체크
ServletContext가 생성될 때 감지한다
 

 
 
■ ContextListener.java

package com.kh.common.listener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

import com.kh.common.AESCryptor;

/**
 * Application Lifecycle Listener implementation class ContextListener
 *
 */
@WebListener
public class ContextListener implements ServletContextListener {


    public ContextListener() {
    }


    public void contextDestroyed(ServletContextEvent sce)  { 
    }


    public void contextInitialized(ServletContextEvent sce)  { 
    	
    	new AESCryptor();
    	
    }
	
}

 
 
■ AESCryptor.java

package com.kh.common;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;

// 양방향 암호화 지원 클래스
// java.api에서 제공하고 있음
public class AESCryptor {
	
	private static SecretKey key; // 암호화를 위한 키
	private String path; // 암호화키 저장된 경로
	
	public AESCryptor() {
		// 인스턴스화 될 때 기본설정 추가
		// 1. key파일이 있다면 key파일에서 SecretKey객체를 불러오고, 
		// 	  key가 없다면 SecretKey 객체를 생성하여 파일로 저장
		
		// 경로 우리가 임의로 지정하기
		this.path = AESCryptor.class.getResource("/").getPath(); // classes 바로 아래를 의미
		// C:\MyBastiWorkSpace\MyBatisProject\WebContent\WEB-INF\classes
		
		this.path = this.path.substring(0, this.path.indexOf("classes"));
		// C:\MyBastiWorkSpace\MyBatisProject\WebContent\WEB-INF\
		
		File f = new File(this.path+"/mkm.mk");
		//key를 저장하고 있는 파일 이름을 mkm.mk 로 지정 -> SecretKey객체를 저장시킬 예정
				
		if(f.exists()) { //key를 저장하는 파일이 있다면 
			
			try {
				ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f));
				//ObjectInputStream은 보조스트림이기 때문에 FileInputStream 필요
				
				this.key = (SecretKey)ois.readObject(); //object로 반환되기 때문에 다운캐스팅
				
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} 
		}else {
			//파일이 없다면 파일생성 후 내부에 SecretKey객체 추가
			if(key == null) {
				
				//key값 생성해주는 메서드
				getGenerator();
			}
		}
	}

	private void getGenerator() {
		//SecretKey를 생성하는 객체
		SecureRandom ser = new SecureRandom();
		
		//key를 생성해주는 클래스
		KeyGenerator keygen = null;
		
		try {
			// 1. 적용할 알고리즘 AES => AES알고리즘은 키가 한개 필요함
			keygen = KeyGenerator.getInstance("AES");
			
			keygen.init(128, ser); //keygen을 통해 secretkey가 초기화 되고 설정까지 완료
			
			this.key = keygen.generateKey(); //secretkey 반환 
			
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} 
		
		// 생성된 키 객체를 mkm.mk파일에 저장
		File f = new File(this.path+ "/mkm.mk"); 
		
		try {
			ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f));
			// 객체를 출력할거니까 objectoutputstream을 이용,
			// 파일 출력할거니까 fileoutput 쓰고 매개변수로 f넣기
			
			oos.writeObject(this.key); //매개변수로 key값넣어서 입력
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static String encrypt(String str) { //암호화 시키는 매서드, 암호화 시키려는 str 받기
		
		String resultValue= ""; //암호화 값 넣을 변수 생성
		
		try {
			//key를 가지고 암호화처리하는 클래스 (Cipher)
			Cipher cipher = Cipher.getInstance("AES");  //변환시켜주고자 하는 알고리즘 - AES
			
			cipher.init(Cipher.ENCRYPT_MODE, AESCryptor.key); 
			// encrypt_mode-> cipher 내부에 있는 상수필드, 방법은 정해져있음, / 뒤에는 key값 넣기
			
			// 매개변수로 전달받은 문자열 암호화 처리
			byte[] encrpt = str.getBytes(Charset.forName("UTF-8")); //byte배열로 변환 
			byte[] result = cipher.doFinal(encrpt);  // cipher.doFinal함수를 통해 암호화시킴
			resultValue = Base64.getEncoder().encodeToString(result); //base64방식으로 문자열로 만들어줌
			
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		} 
		
		return resultValue;
	}
	
	// 생성된 키를 가지고 복호화 하는 메서드
	public static String decrypt(String encryptedStr) {
		
		String decryptedValue = "";
		
		try {
			Cipher cipher = Cipher.getInstance("AES");
			cipher.init(Cipher.DECRYPT_MODE, AESCryptor.key); 
			//여기까지 초기화 끝, 이제 역순으로 해줘야함
			
			byte[] decodeStr = Base64.getDecoder().decode(encryptedStr.getBytes(Charset.forName("UTF-8")));
			byte[] orignStr = cipher.doFinal(decodeStr); //원본 byte배열로 만들어주기
			
			decryptedValue = new String(orignStr); //바이트 배열을 문자열로 변경해줌
			
		} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		} 
	
		return decryptedValue;
		
	}

}

 
 
▶ MemberInsertController.java에 email = AESCryptor.encrypt(email);로 수정

 
 
▶ LoginController.java도 loginUser.setEmail(AESCryptor.decrypt(loginUser.getEmail())); 로 수정