보통 웹개발을 하게 되면 민감한 정보는 암호화 해서 DB에 저장하게 된다.
개인적으로는 비밀번호 같이 복호화가 필요 없는건 SHA-256, 이름이나 파일명 같은 복호화가 필요한 데이터는 AES-256을 사용해서 암호화하고 있다.
너무 흔한 방식이라 대부분 같은 방식을 사용하고 있을거라고 생각된다.
SHA는 해시함수라 항상 같은 길이의 결과물이 떨어져서 DB 컬럼 길이를 정할때 고민없이 정할 수 있다.
하지만 AES를 하게 되면 길이가 늘어나기 때문에 고민을 안 할수가 없다.
AES는 기본적으로는 블록암호화함수라 입력값과 출력값의 길이가 동일하다. 다만, 패딩이 붙기 때문에 실제 길이보다 살짝 늘어나게 된다.
여기에 흔히 암호화 후 base64 인코딩해서 저장하는 경우가 많은데 base64까지 하게 되면 base64 구조 상 당연히 길이가 늘어나게 된다. (개인적으로 만들어놓은 암호화 유틸도 base64 인코딩을 포함하고 있다)
컬럼 길이를 무작정 255로 정한다 하더라도 원본 값이 길어지면 일부는 잘려서 들어가게 될거고, 잘려서 들어가면 당연히 복호화는 안될거고.. 그렇다고 컬럼 타입을 TEXT로 지정해버릴 수도 없는 노릇이고..
결국은 사용자 입력단에서 필요한 만큼 길이 제한을 걸어줘야 하는데 암호화 후의 길이를 알수가 없으니 DB 컬럼 길이를 눈대중으로 실제로 필요한 길이보다 크게 지정하게 된다.
길이가 255로 지정된 컬럼에 대해 어느정도까지 제한을 걸어줘야 할지 정확하게 알수 없어서 영어 기준 100자로 일괄적으로 제한을 걸었던 경험이 있다.
예시로 예전에 만들었던 테이블 하나를 가져왔다.
프로필 사진 경로가 들어가는 profile 컬럼의 길이가 255로 지정되어 있다.
이 서버는 파일명을 해당 파일의 해시 값으로 사용하고 있어서
64 byte의 길이의 파일명이 암호화 후 base64 인코딩되어 저장된다.
사용하고 있는 실제 길이는 108 byte 밖에 되지 않는다.
결국 실제로 필요한 원본값 64 byte, 암호화 후 108 byte 보다 큰 값(255)이 컬럼 길이로 지정되어 있는 걸 알수 있다.
VARCHAR 타입이 가변 길이라 길이를 알아야 할 필요성은 없지만 개발하는 입장에서 정확하게 알지 못하고 눈대중으로 길이를 지정한다는 행위 자체가 좋은 행동이 아니다.
평문이 암호화 된 후의 정확한 길이를 사전에 알수 있다면 컬럼 길이를 최적화 하거나,
남는 길이만큼 사용자에게 더 입력할 수 있도록 제한을 여유롭게 둘 수 있을 것이다.
따라서 개발자는 암호화 후 암호문의 길이가 정확하게 얼마인지 알아야 할 필요성이 있다.
아래는 개인적으로 만들어놓은 AES256Util의 소스코드다.
package me.huiya.core.Encrypt;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Base64;
@Component
public class AES256Util {
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
private static byte[] KEY;
private static byte[] IV;
@Value("${core.AES-key}")
public void setKEY(String key) {
KEY = key.getBytes();
}
@Value("${core.AES-iv}")
public void setIV(String iv) { IV = iv.getBytes(); }
public static String encrypt(String planText) {
if(planText == null || planText == "") {
return planText;
}
Key key = new SecretKeySpec(KEY, "AES");
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(IV));
return new String(Base64.getEncoder().encode(cipher.doFinal(planText.getBytes())));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String decrypt(String encryptedText) {
if(encryptedText == null || encryptedText == "") {
return encryptedText;
}
Key key = new SecretKeySpec(KEY, "AES");
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(IV));
return new String(cipher.doFinal(Base64.getDecoder().decode(encryptedText)));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
소스를 보면 key와 iv는 application.properties를 통해 불러오게 되어있고,
encrypt와 decrypt로 간단하게 이루워져 있다.
소스코드에서 주목해야 할 부분은 지정된 알고리듬이다.
AES, CBC 모드, PKCS 5 방식의 패딩을 사용하고 있다.
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
기본적으로 AES는 16 byte의 패딩 길이를 사용한다. (키의 길이와 상관없이 고정이다)
PKCS 5/7 패딩 방식을 사용한다면 아래 공식으로 패딩이 포함된 암호문의 길이를 알수 있다.
(이 두개가 가장 흔한 방식이라 대부분 이 방식을 사용하고 있을거라 생각된다)
cipherLength = (plainTextLength / 16 + 1) * 16;
앞서 암호화 했던 profile 컬럼의 경우 64 byte가 들어가므로 암호화 한 후에는 위 공식대로 계산하면 80 byte가 된다.
base64 인코딩 길이는 아래 공식으로 구할 수 있다.
base64encodeLength = ((4 * textLength / 3) + 3) & ~3
암호화한 길이가 80byte이므로 위 base64 공식까지 계산해보면 정확하게 108 byte가 나온다.
profile 컬럼은 정확하게 64 byte만 들어가므로 (파일명이기 때문에) 길이를 108로 설정할 수 있다.
그 외 닉네임 같은 사용자 입력이 필요한 컬럼의 경우 위 공식을 통해 계산해보면 173 byte를 암호화 할 경우 252 byte로 떨어지게 된다.
기존에 눈대중으로 100자로 제한 걸었던 것에 비해 같은 컬럼 길이로 173 byte까지 허용할 수 있게 되었다.
정확한 기준없이 눈대중으로 길이 제한을 걸지 말고 계산을 통해서 근거있는 제한을 걸도록 하자.
출처
https://stackoverflow.com/questions/3283787/size-of-data-after-aes-cbc-and-aes-ecb-encryption
https://stackoverflow.com/questions/13378815/base64-length-calculation
'개발 > 기타' 카테고리의 다른 글
정규표현식 최소 일치 (0) | 2019.11.11 |
---|---|
시놀로시 나스에 Git 서버 올리는 방법 (0) | 2019.06.21 |