ISMS AES-128 암호화키 관리

📝 ISMS 암호통제 지침

암호통제 지침 중 여러 조항이 있지만 개발과 관련있는 부분이다
아래 지침내용은 회사 내부 지침 일부 발췌

📝 암호화 기술 및 프로그램 선택기준

기밀성을 위한 암호 기술은 최소 128비트 이상의 키길이를 사용하는 암호화키를 사용하는 안정성이 입증된 대칭키 암호화 알고리즘을 이용한다
(암호화 대상 정보: 고유식별정보, 신용카드번호, 계좌번호, 바이오 정보 등)

보안강도 대칭키 암호 알고리즘 안정성
80bit 미만 DES 권고하지 않음
80bit 2TDEA 권고하지 않음
112bit 3TDEA 권고하지 않음
128bit SEED, HIGHT, ARIA-128, AES-128 권고
192bit ARIA-192, AES-192 권고
256bit ARIA-256, AES-256 권고

📝 암호화키 관리

  1. 암호화키가 외부 매체에 저장될 시에는 비인가자가 접근할 수 없는 안전한 매체에 저장되어야 한다
  2. 암호화키를 문서 형태로 보관시 암호화하여 보관한다
  3. 암호화키는 평문 파일로 또는 프로그램 소스에 삽입되어 운영시스템에 저장되어서는 안된다
  4. 운영시스템에는 암호화키가 삽입된 프로그램 소스는 삭제되어야 하며 로드모듈로만 저장해야 한다
  5. 서버에 저장되는 암호화키는 암호화되어 저장되어야 한다

📝 구현

Oracle DBMS_CRYPTO
앞서 포스팅 했던 대로 오라클 패키지 함수 내 암호화 키가 평문으로 함수 매개변수에 삽입되어 있어 수정해야 했다. 기존 암호화된 데이터가 AES128 방식으로 암호화 되어 있어 자바 소스에서도 AES128 암호화를 선택했다

✍ 설계

AS-IS:
1. 평문 암호키가 저장된 오라클 패키지 함수 호출

TO-BE:
1. 암호화키를 암/복호화 할 모듈 생성
2. 운영데이터 암/복호화 할 암호화키를 모듈로 암호화하여 txt 파일에 저장
3. txt파일 WAS 서버에 저장
4. WAS에서 AES128.jar 사용하여 txt파일의 암호키 복호화 후 오라클 패키지 함수 호출

✍ 모듈 소스

package com.erp.common;

import java.security.Key;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
 
public class AES128 {
		
	//IV
	private String ips;
	//키 클래스
    private Key keySpec;
	
    //생성자
	public AES128() {
		//암호화키를 암/복호화하는 암호화키 16byte
		String key = "test1234ttest1234";
        try { 
        	//암호화키 저장할 byte[] 변수
            byte[] keyBytes = new byte[16];
            byte[] b = key.getBytes("UTF-8");
            System.arraycopy(b, 0, keyBytes, 0, keyBytes.length);
            //비밀키 클래스 선언
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            //운영중인 오라클 패키지 함수에서 IV를 사용하지 않기 때문에  null 16bytes
            this.ips = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
            //AES방식과 암호화 키 값을  Key 클래스에 저장
            this.keySpec = keySpec;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
	//생성자 오버로딩
	public AES128(String key) {
        try {
            byte[] keyBytes = new byte[16];
            byte[] b = key.getBytes("UTF-8");
            System.arraycopy(b, 0, keyBytes, 0, keyBytes.length);
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            this.ips = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; //null 16bytes
            this.keySpec = keySpec;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
	//암호화
    public String encrypt(String plainText) {
    	//암/복호화 암호 기능 제공 클래스 선언
        Cipher cipher;
        try {
        	//암호방식, 체인방식, 패딩방식 선언 및 인스턴스 득
            cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            //인스턴스 얻은 후 암호화키와 IV로 암호 초기화
            cipher.init(Cipher.ENCRYPT_MODE, keySpec
            , new IvParameterSpec(ips.getBytes()) );
            //암호화 실행1
            byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
            //오라클 DBMS_CRYPTO 함수에서 HEX String을 반환하기 떄문에  byte[]->HEX 변환
            String encryptStr = new String(byteArrayToHex(encrypted).toUpperCase());

            //암호화된 문자열 반환
            return encryptStr;
        } catch (Exception e) {
        	return null;
        }
    }
 
    //복호화
    public String decrypt(String encryptStr) {
        try {
        	//암호방식, 체인방식, 패딩방식 선언 및 인스턴스 득
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            //인스턴스 얻은 후 암호화키와 IV로 암호 초기화
            cipher.init(Cipher.DECRYPT_MODE, keySpec
            , new IvParameterSpec(ips.getBytes("UTF-8")));
            //HEX String->byte[] 로 변환
            byte[] byteStr = hexToByteArray(encryptStr);
            //복호화 실행
            String decryptStr = new String(cipher.doFinal(byteStr), "UTF-8");

            return decryptStr;
        } catch (Exception e) {
            return null;
        }
    }
    
    //byte[]->hex String
    private String byteArrayToHex(byte[] encrypted) {
        
        if(encrypted == null || encrypted.length == 0){
            return null;
        }
        
        StringBuffer sb = new StringBuffer();
        for (byte b : encrypted) {
        	//앞 빈자리 0으로 채우는 16진수 (16진수 2자리가 1byte)
        	sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
    //hex String->byte[]
    private byte[] hexToByteArray(String hex) {
        
        if(hex == null || hex.length() == 0){
            return null;
        }
        
        //16진수 2자리가 1byte
        byte[] byteArray = new byte[hex.length() / 2 ];
         
        for(int i = 0; i<byteArray.length; i++){
        	//2자리씩 16진수로 변환
            byteArray[i] = (byte) Integer.parseInt(hex.substring(2*i, 2*i+2), 16);
        }
        
        return byteArray;
    }
}

✍ 모듈 호출 소스

암호화키 복호화 후 저장

Util클래스에 static 함수로 구현

import java.io.File;
import java.util.Scanner;

public class Util{
  public static String getEncKey(){
    AES128 aes128 = new AES128();
    String enc_key = null;

    try { 
          //enc_key.txt 내용 :
          //25570DDCCE2517C85728B94A051AF276A3D07E616EE120540EE4EFD7C88ED357
          File f1 = new File("프로젝트 경로" + "/enc_key.txt");
          Scanner sc = new Scanner(f1);
          while (sc.hasNextLine()) {
            String data = sc.nextLine();
            enc_key = aes128.decrypt(data);
          }
          sc.close();
        } 
    catch (Exception e) {
          e.printStackTrace();
    }
    finally{
      return enc_key;
    }
  }
}

서비스에서 DataMap paramData에 값 저장

//서비스 클래스 변수 선언
private String enc_key = Util.getEncKey();

//서비스 함수 내 쿼리실행 변수에 할당
public String getHpNo(DataMap paramData){
  paramData.put("enc_key", enc_key);
}

쿼리 사용

SELECT CRYPTO.DECRYPT(HP_NO, ${enc_key}) FROM DUAL

📝 마치며

ISMS 인증을 앞두고 수정한 건이라 결함으로 판단될 경우 보완하여 수정 예정

좋은 웹페이지 즐겨찾기