양방향 알고리즘은 암호화된 암호문을 복호화 할 수 있는 알고리즘을 의미
단방향 알고리즘은 암호화는 수행하지만 절대로 복호화가 불가능한 알고리즘
양방향 알고리즘은 대표적으로 대칭키(비공개키)방식과 비대칭키(공개키)방식
으로 나눠지며, 단방향은 Hash방식이 대표적
양방향 암호화는 크게 대칭키(비공개키)와 비대칭키(공개키)방식
대칭키 방식은 암호화, 복호화시 모두 동일한 키를 사용
비대칭키(공개키)방식은 암호화 복호화에 서로 다른 키 사용
대칭키(비공개키)는 사용하는 키와 복호화할 때 사용하는 키가 동일한 암호화 기법
으로 대개 암호화 알고리즘이라고 하면 대부분 이 쪽을 가리킨다.
현재 사용되는 대칭형 암호화 알고리즘은 주로 파이스텔 네트워크(Feistel
Network) / S-Box를 통하여 블럭 암호로 만들어지지만 AES처럼 파이스텔
네트워크를 사용하지 않는 알고리즘도 있다. 이쪽은 라인달(Rijndael)알고리즘
이라는 비교적 새로운 알고리즘을 적용하고 있다.
현재 가장 보편적으로 쓰이는 암호화 방식은 현 미국 표준방식인 AES.
128~256비트 키를 적용할 수 있어 보안성이 뛰어나며 공개된 알고리즘이라
누구나 사용할 수 있다. 그 전에는 DES(Data Encryption Standard)라는
알고리즘이 1975년부터 사용되고 있었으나 너무 오래되어 취약점이 발견됨에
따라 이를 대체하기 위해 등장한 것이 바로 AES이다.
대칭형 암호의 결정적인 문제는 '키 배송'에 관한 문제이다.
키를 안전하게 전달할 방법이 없다. 키 배송 문제를 해결한 방식이 비대칭형 암호
비대칭형 암호는 암호화 키와 복호화 키가 다르다.
암호화를 하면 하나의 키쌍이 생기고 이 두개의 키는 수학적으로 밀접한
관계를 가지고 있다.
회원 관리 예제 프로그램에 데이터를 암호화하는 기능을 추가해보자.
1) 회원ID는 DB에 저장할 때는 양방향 암호화방식으로 암호화해서 저장하고
화면에 출력할 때는 원래의 데이터로 복원하여 출력하도록 한다.
2) 회원 비밀번호는 단반향 암호화방식으로 암호화하여 저장한다.
요구사항 정의서
기능적인 부분을 도출 - 유스케이스(기능 하나하나)
기능을 하나하나 그림으로 표현한 것을 유스케이스 다이어그램이라고 한다.
StarUML
Select Diagram 요소를 선택합니다.
Package 모델 요소들을 논리적으로 그룹화 할 때 사용합니다.
UseCase 시스템이 제공하는 기능을 의미합니다.
Actor 시스템을 사용하는 사용자나 외부 시스템을
의미합니다.
Association 유스케이스와 액터간에 연관 관계가 있을 때
사용합니다.
DirectedAssociation
유스케이스와 액터간에 연관 관계가 있을 때
사용합니다.
종속, 포함등의 관계를 표시할 수 있습니다
Generalization
일반적인 요소와 더 구체적인 요소의 관계일 때
사용합니다. - java의 상속과 개념이 비슷
(ex. 회원, 비회원의 공통 유스케이스가 있을때, 손님이라는 actor하나로 묶음)
Dependency
어떤 유스케이스를 위해 다른 요소의 존재가
요구되어 지는 의존
적인 관계를 의미합니다.
Include 어떤 유스케이스를 수행하는 데, 반드시 수행해야
하는 유스케이스를 나타내기 위해 사용합니다.
화살표 방향은 주기능 -> 부기능
Extend 한 유스케이스가 특정 시점에 여러가지 형태로
분류될 경우에 사용합니다.
System Boundary 시스템과 외부 시스템의 경계를 의미합니다
화살표 방향은 부기능(선택적인 기능) -> 주기능
필수일 때 include
선택일 때 extend
유스케이스 명세서 (유스케이스 내부에 대한 기술을 나타낸다.)
# 유스케이스명 : 회원등록(가입)
# 액터명 : 고객(비회원)
# 유스케이스 개요 및 설명 : 고객이 쇼핑몰 시스템을 사용하기 위해
회원가입을 하는 유스케이스
# 사전조건 : 고객이 회원 가입이 되어있지 않아야 된다.
# 이벤트 흐름
정상 흐름
선택 흐름
3-1. 회원 약관에 동의하지 않으면 회원 가입 불가의 오류메시지를
출력하고 동의를 재요청한다.
5-1. 회원ID가 중복되면 '중복된 ID입니다'라는 오류메시지를 출력하고
새로운 회원ID를 입력받아서 확인한다.
7-1. 회원 정보 중 입력되지 않은 항목이 있는 경우 오류메시지를 출력하고
재입력을 요구한다.
7-2. 이메일 주소가 형식에 맞지 않으면 오류메시지를 출력하고 재입력을 요구한다.
package kr.or.ddit.util;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class CryptoUtil {
// 단방향 암호화 메서드
/**
* 인수값의 문자열을 SHA-512방식으로 암호화하는 메서드
* (단방향 암호화)
* @param str 암호화할 문자열 데이터
* @return 암호화된 문자열 데이터
* @throws NoSuchAlgorithmException
* @throws UnsupportedEncodingException
*/
public static String sha512(String str) throws
NoSuchAlgorithmException, UnsupportedEncodingException {
// 암호화에 사용할 알고리즘을 지정하여 MessageDigest 객체 생성
// 알고리즘은 문자열로 지정하는 데 다음과 같은 종류가 있다.
// "MD5", "SHA-256" 등
MessageDigest md = MessageDigest.getInstance("SHA-512");
// 암호화할 데이터를 byte형 배열로 넣어준다.
md.update(str.getBytes("utf-8"));
// md.digest() ==> 암호화된 데이터를 byte배열로 반환한다.
return Base64.getEncoder().encodeToString(md.digest());
}
//--------------------------------------------------------------
/**
* 양방향 암호화 알고리즘 중 AES-256 알고리즘으로 암호화하는 메서드
*
* @param str 암호화 할 문자열
* @param key 암호화에 사용할 암호키 문자열(16자 이상)
* @return 암호화된 문자열
* @throws UnsupportedEncodingException
* @throws NoSuchPaddingException
* @throws NoSuchAlgorithmException
* @throws InvalidAlgorithmParameterException
* @throws InvalidKeyException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
*/
public static String encryptAES256(String str, String key) throws
UnsupportedEncodingException,
NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, InvalidAlgorithmParameterException,
IllegalBlockSizeException, BadPaddingException {
if(key.length()<16) {
System.out.println("암호키는 16자 이상이어야 합니다.");
System.out.println("작업을 마칩니다.");
return null;
}
byte[] keyBytes = new byte[16];
System.arraycopy(key.getBytes("utf-8"), 0, keyBytes, 0, keyBytes.length);
// 비밀키 생성 (키에 사용할 byte형 배열과 알고리즘 이름을 지정한다.)
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
// Cipher객체 생성 및 초기화
/*
알고리즘/모드/패딩 (AES/CBC/PKCS5Padding)
- CBC : Cipher Block Chaining Mode ==> 동일한 평문 블록과 암호문 블록의 쌍이
발생하지 않도록 이전 단계의 암복호화한 결과를 현 단계에 사용하는 모드를 말한다.
- 패딩 : Padding ==> 마지막 블록이 블록의 길이가 부족할 때 부족한 부분을 채워 넣는 방식을 말한다.
*/
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
// 초기화 벡터값 작성
// 초기화 벡터(Initial Vector, IV) - 암호문이 패턴화되지 않도록 사용하는 데이터를 말한다.
/암호화 처리중 첫번째 블록을 암호화 할 때 사용되는 값이다.
String iv = key.substring(0, 16);
byte[] ivBytes = new byte[16];
System.arraycopy(iv.getBytes("utf-8"), 0, ivBytes, 0, ivBytes.length);
// 암호를 옵션 종류에 맞게 초기화한다.
// 옵션 종류 : ENCRYPT_MODE(암호화모드), DECRYPT_MODE(복호화모드)
c.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(ivBytes));
// 암호화할 데이터를 byte형 배열로 공급하여 암호화 작업을 수행한다.
byte[] encryptBytes = c.doFinal(str.getBytes("utf-8"));
String enStr = Base64.getEncoder().encodeToString(encryptBytes);
return enStr;
}
/**
* 암호화된 데이터를 인수값으로 받아서 원래의 내용으로 복호화하는 메서드
* @param str 복원한 암호화된 문자열
* @param key 암호키 문자열
* @return 복원된 원래의 문자열
* @throws UnsupportedEncodingException
* @throws NoSuchPaddingException
* @throws NoSuchAlgorithmException
* @throws InvalidAlgorithmParameterException
* @throws InvalidKeyException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
*/
public static String decryptAES256(String str, String key) throws
UnsupportedEncodingException,
NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
if(key.length()<16) {
System.out.println("암호키는 16자 이상이어야 합니다.");
System.out.println("작업을 마칩니다.");
return null;
}
byte[] keyBytes = new byte[16];
System.arraycopy(key.getBytes("utf-8"), 0, keyBytes, 0, keyBytes.length);
// 비밀키 생성 (키에 사용할 byte형 배열과 알고리즘 이름을 지정한다.)
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
// 초기화 벡터값 작성
// 초기화 벡터(Initial Vector, IV) - 암호문이 패턴화되지 않도록 사용하는 데이터를 말한다.
// 암호화 처리중 첫번째 블록을 암호화 할 때 사용되는 값이다.
String iv = key.substring(0, 16);
byte[] ivBytes = new byte[16];
System.arraycopy(iv.getBytes("utf-8"), 0, ivBytes, 0, ivBytes.length);
// 암호를 옵션 종류에 맞게 초기화한다.
// 옵션 종류 : ENCRYPT_MODE(암호화모드), DECRYPT_MODE(복호화모드)
c.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(ivBytes));
// 복원할 암호화된 문자열을 decoding한 byte형 배열을 구한다.
byte[] byteStr = Base64.getDecoder().decode(str);
// 암호화된 byte배열을 원래의 데이터로 복원한 후 문자열로 변환하여 반환한다.
return new String(c.doFinal(byteStr), "utf-8");
}
}
package kr.or.ddit.basic;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import kr.or.ddit.util.CryptoUtil;
public class CryptoTest {
public static void main(String[] args) throws Exception {
String sourceText = "Hello, World! 가나다라 12345 !@#$%";
String key = "a1b2c3d4e5f6g7h8"; // 키 값은 사용자가 임의로 정해서 사용한다.
System.out.println("단방향 암호화...");
System.out.println("SHA-512방식 : " + CryptoUtil.sha512(sourceText));
System.out.println();
System.out.println("양방향 암호화...");
String encryptStr = CryptoUtil.encryptAES256(sourceText, key); // key : 암호화할 텍스트
System.out.println("원본 데이터 : " + sourceText);
System.out.println("암호화한 데이터 : " + encryptStr);
String decrptStr = CryptoUtil.decryptAES256(encryptStr, key);
System.out.println("복호화한 데이터 : " + decrptStr);
}
}