레이블이 decrypt인 게시물을 표시합니다. 모든 게시물 표시
레이블이 decrypt인 게시물을 표시합니다. 모든 게시물 표시

2016년 12월 19일 월요일

AES 암복호화 보완, (AES/CBC/PKCS5Padding 방식으로)

특정 DB컬럼의 값을 암호화하여 저장하라는 회사의 아키텍쳐 요건을 만족시키기 위해서 AES로 암호화하여 사용하고 있었다. 그런데 기존 방식이 AES/ECB/PKCS5Padding를 사용하고 있었는데 ECB가 문제가 되서 AES/CBC/PKCS5Padding로 변경하는 작업을 진행했다.
 

1.
암호화로 사용할 키는 KeyGeneratorSecureRandom으로 생성한다.

```
KeyGenerator generator = KeyGenerator.getInstance("AES");
SecureRandom random = new SecureRandom();
generator.init(128, random);
Key secureKey = generator.generateKey();

System.out.println(Base64.encodeBase64String(secureKey.getEncoded()));
```

2.
같은 문자열도 암호화할때마다 다른 문자열을 만들어내기 위해서 IV값을 랜덤하게 만들어준다.

```
/**
* 랜덤하게 IV 생성
*
* 같은 평문도 암호화할 때마다 암호문을 다르게 만들어 낸다.
* @return 랜덤하게 생성된 IvParameterSpec
*/
private static IvParameterSpec getRandomIvParameterSpec() {
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
return new IvParameterSpec(iv);
}
```

3.
IV값과 암호키로 암호화한 값을 합쳐야 하는데 java byte concat으로 검색해보니 무려 자바 System 클래스에 배열복사가 있었다!

```
// IV + 암호문으로 출력할 Byte를 붙인다.
byte[] ivcipherByte = new byte[16 + cipherByte.length];
System.arraycopy(iv.getIV(), 0, ivcipherByte, 0, 16);
System.arraycopy(cipherByte, 0, ivcipherByte, 16, cipherByte.length);
```

4.
다시 복호화할때는 입력된 byte를 IV와 암호값으로 다시 나눈다.

```
// 입력된 Byte를 IV + 암호문으로 나눈다.
byte[] originalIvByte = Arrays.copyOfRange(cipherByte, 0, 16);
byte[] originalCipherByte = Arrays.copyOfRange(cipherByte, 16, cipherByte.length);
```

5.
이렇게 만든 encrypt와 decrypt를 IN/OUT을 모두 String으로 만들어서 쓰기 좋게

```
/**
* 암호화하고, Base64로 인코딩
* @param plainText - 평문
* @return 암호문
*/
public static String encryptAndEncoding(String plainText) {
// 암호화하고 나서, 다시 문자열로 만들기위해 Base64 인코딩
return Base64.encodeBase64String(encrypt(plainText));
}

/**
* Base64로 디코딩하고, 복호화
* @param cipherText - 암호문
* @return 평문
*/
public static String decodeAndDecrypt(String cipherText) {
// 다시 Byte로 만들기위해 Base64 디코딩하고 복호화
return decrypt(Base64.decodeBase64(cipherText));
}
```


2016년 7월 2일 토요일

Mybatis Plugin으로 특정 DB컬럼값을 암호화/복호화 처리

Mybatis를 이용하여 DAO 클래스를 생성하여 DB 조회/처리를 담당하게 만들어서 사용하고 있다. DB의 특정 테이블의 특정 컬럼은 값을 암호화하여 저장하고 있다. DB에 넣기 전에 암호화하고, 읽고 나서는 복호화를 해야 어플리케이션에서 처리가 가능하다.

1. 현재 암복호화 방식
현재는 DAO 패턴을 사용하고 있고, DAO에 데이터를 항상 DVO를 이용해서 전달하고 전달받고 있다. 그리고 암호화 대상인 컬럼인 경우에는 DVO에 encrypt() 메서드와 decrypt() 메서드를 DAO에서 호출해서 암호화/복호화를 수행하고 있다.

2. 문제1
암호화 대상 컬럼이 변경되는 경우, 대상 컬럼이 포함된 기존의 DVO 소스를 모두 변경(또는 재생성)해야 한다. 암호화 대상은 자주 바뀌는 성질은 가지고 있진 않지만, 변경되는 경우 그 변경 영향도가 매우 크고, 테스트에 대한 부담감도 커지게 된다.

3. 문제2
DAO에 DVO이외의 자료구조를 이용해서 데이터를 주고 받고자 한다면, 암호화컬럼에 대한 처리 방식을 고민해야 한다.

3. 문제3
DVO마다 encrypt()와 decrypt() 메서드를 항상 구현해야 한다. 비록 그 메서드 내용이 아무것도 없다고 하더라도. 또는 모든 DVO가 특정 클래스를 상속받도록 하고, 상위 클래스에 아무것도 구현안된 encrypt()와 decrypt()를 구현해 놓아야 한다. 이러면 DVO가 일반 POJO가 아니게 된다.

4. 해결책
Mybatis의 plugin 기능으로 해결이 가능하다. 모든 쿼리에 대해서 실행 전에 Parameter 중 특정 컬럼에 해당하는 이름을 가진 필드값을 암호화하고, 실행 후에 ResultSet에서 같은 이름의 필드값에 대해서 복호화를 수행하는 것이다.

5. 여전히 문제점
아직 DAO의 Parameter와 ResultSet이 DVO로 구성된다는 가정이 존재한다. Primitive 타입이나 String, Map과 같은 Java 기본 클래스도 사용하게 된다면, 그런 자료구조에 대한 처리도 추가로 필요할 것이다.

6. 걱정
모든 쿼리의 입출력 DVO에 대해서 Reflection을 사용하고 있어서 성능상 문제가 되지는 않을지 걱정이다.

7. 한계
실제로 DB에서는 암호화 컬럼인데, SQL의 ALIAS를 이용해서 컬럼의 이름을 변경하면 복호화되지 않은 체로 ResultSet이 리턴될 수 있다.