RSA를 이용한 암호화 처리

Date : 2021.03.25   Modified date : 2021.04.05

RSA

1978년 로널드 라이베스트(Ron Rivest), 아디 샤미르(Adi Shamir)이 발표한 공개키 암호화 방식으로, 공개키 암호화의 개념을 수학 적으로 구체화 시킨 알고리즘이다.

RSA 암호체계의 안정성은 큰 숫자를 소인수 분해하는 것이 어렵다는 것에 기반을 두고 있다.

RSA는 메시지를 암호화하는데 쓰이는 공개키(public key)와 암호호된 메시지를 복호화하는 개인키(private key) 두 개의 키를 사용한다.

공개키 알고리즘은 누구나 어떤 메시지를 암호화할 수 있지만, 그것을 해독하여 열람할 수 있는 사람은 개인키를 지닌 단 한사람만이 존재한다는 점에서 대칭키 알고리즘과 차이를 가진다.

RSA를 사용하게 된 계기

WEB 서버에 SSL 인증서 설치 없이 HTTP를 이용한 로그인 또는 회원가입을 처리할때 개인정보를 평문으로 전송 할 경우 중간자 공격(man in the middle attack, MITM)에 취약해지기 때문에 서버에 전송하기 전 평문을 암호화 처리하기 위해서 사용했다.

필요한 라이브러리

  • jsbn.js
  • prng4.js
  • rng.js
  • rsa.js

동작 순서

  • [서버] 서버측에서 RSA 공개키와 개인키(비밀키)를 생성하여, 개인키는 세션에 저장하고 공개키는 자바스크립트 로그인 폼이 있는 페이지로 출력한다.

code


HttpSession session = request.getSession();
		
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(1024);
		
KeyPair keyPair = generator.genKeyPair();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
		
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
 
session.setAttribute("_RSA_WEB_Key_", privateKey);   //세션에 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);  //로그인 폼에 Input Hidden에 값을 셋팅하기위해서
request.setAttribute("RSAExponent", publicKeyExponent);   //로그인 폼에 Input Hidden에 값을 셋팅하기위해서


  • [클라이언트] 회원가입폼에 양식작성 후 발행(submit)을 누르면 자바스크립트가 이를 가로챈다.
    • 회원가입 폼의 아이디, 비밀번호, 이메일을 RSA로 암호화하여 암호화된 암호문을 회원가입 폼의 값으로 변경한다.

code


// RSA 암호화 생성
var rsa = new RSAKey();
rsa.setPublic($("#RSAModulus").val(), ("#RSAExponent").val());

// 암호화된 비밀번호
var en_pwd = rsa.encrypt(user_pwd);
				
$("#user_pwd").val(en_pwd);


  • [서버] 서버측에서 세션에 저장된 RSA개인키로 암호화된 암호문을 복호화 한다.

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


  • [서버] 평문이 복호화가 완료되면 유효성 검사를 한번 더 서버에서 실행하고 통과되면 DB에 현재 입력된 회원가입 정보를 저장한다.

	// 회원가입 처리
	@RequestMapping( value="/join", method = RequestMethod.POST )
	public String join(@Valid EncryptDTO encryptDTO, BindingResult bindingResult, HttpServletRequest request, RedirectAttributes rttr) throws Exception{
		
		logger.info("join POST 요청");
		
		// 현재 세션 가져오기
		HttpSession session = request.getSession();
		// 세션에 저장된 개인키를 가져오기
		PrivateKey privateKey = (PrivateKey) session.getAttribute("_RSA_WEB_KEY_");
			
		
		if(privateKey != null) {
			// 암호화된 객체를 복호화	
			if(encrypt_service.decryptRsa(privateKey, encryptDTO) != null) {
				
				// 복호화된 객체 유효성 체크
				new EncryptValidator().validate(encryptDTO, bindingResult);
				
				// 유효성에 문제가 발생하면 새로운 요청을 통해 회원가입 페이지로 이동
				if(bindingResult.hasErrors()) {
					
					rttr.addFlashAttribute("error", "member_create_error");
					return "redirect:/member/join";
					
				}
				
				// 복호화된 객체를 이용하여 회원가입 처리
				int result = member_service.member_create(encryptDTO);
				
				// 회원가입 완료 체크
				if(result == 1) {
					rttr.addFlashAttribute("result", "member_create_success");
					logger.info("회원가입 처리 완료!");
				} else {
					rttr.addFlashAttribute("result", "member_create_fail");
					logger.info("회원가입 실패!");
				}
				
			}
		} else {
			// 개인키가 존재하지 않을 경우 alert로 사용자에게 오류를 알리기 위한 구문
			rttr.addFlashAttribute("result", "member_create_fail");
		}
		
		return "redirect:/";
	}

참고

  • https://ko.wikipedia.org/wiki/RSA_%EC%95%94%ED%98%B8
  • http://kwon37xi.egloos.com/4427199