고래씌
[MyBatis] 2. 암호화 설정 본문
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())); 로 수정
'Server > MyBatis' 카테고리의 다른 글
[MyBatis] 3-2. 게시판 설정(게시판 상세, 조회수, 댓글, 검색) (0) | 2023.12.24 |
---|---|
[MyBatis] 3-1. 게시판 설정(페이징 설정) (0) | 2023.12.24 |
[MyBatis] 1-3. 회원가입 만들기 (0) | 2023.12.20 |
[MyBatis] 1-2. 로그인 기능 (0) | 2023.12.19 |
[MyBatis] 1-1. MyBaits 시작(lombok) (0) | 2023.12.19 |