라떼군 이야기
MySQL과 MariaDB에서의 AES 암호화
Problem
-
현재
MySQL
과MariaDB
의 지원하는 암호화 알고리즘 차이가 있음 -
AES_ENCRYPT
은MySQL
과MariaDB
동일하게 존재함 (현재 최신버전 기준, 추후에 지원 여부가 변경될 수 있으니 버전 확인 필요) -
AES_ENCRYPT
사용 방법INSERT INTO t VALUES (AES_ENCRYPT('text',SHA2('password',512)));
-- base64로 저장하는 경우 SELECT to_base64(AES_ENCRYPT(mobile, 'text')) FROM t; SELECT AES_DECRYPT(from_base64(mobile), 'text') FROM t;
-
block_encryption_mode
-
block_encryption_mode
옵션을 이용해 암호화 모드를 변경할 수 있다. -
MySQL
과MariaDB
에서 그냥AES_ENCRYPT
와AES_DECRYPT
를 실행할 경우 시스템 환경 변수에 기본 옵션으로 실행되기 때문에 원하는 알고리즘 및 모드인지 확인이 필요 -
MySQL
- 다음 설명에서 볼 수 있듯이 MySQL은 다양한 알고리즘을 지원하고 있다.3 특히
CBC
와256
의 키 길이를 지원한다는 것.block_encryption_mode takes a value in aes-keylen-mode format, where keylen is the key length in bits and mode is the encryption mode. The value is not case-sensitive. Permitted keylen values are 128, 192, and 256. Permitted mode values are ECB, CBC, CFB1, CFB8, CFB128, and OFB.
- 블록모드 설정과 확인은 아래처럼 할 수 있음
select @@session.block_encryption_mode; SET @@session.block_encryption_mode = 'aes-256-ecb';
- 다음 설명에서 볼 수 있듯이 MySQL은 다양한 알고리즘을 지원하고 있다.3 특히
-
MariaDB
System Variable Differences Between MariaDB 10.0 and MySQL 5.6
4 문서에도 볼 수 있듯이AES-128-ECB
즉128bit
의 키 길이와ECB
모드만 지원
-
Solution
-
MariaDB
를 사용한다면UDF(User defined function)
를 설치해서 사용lib_mysqludf_aes256
5과 같은UDF
를 설치해서AES-256-CBC
지원 가능AWS
RDB
에서는 사용 불가능
-
UDF
를 사용하지 않고AES-128-ECB
를 사용해야 한다면,- 암호화를 디비에서 구현하지 않고 다른 외부 코드에서 구현하여 암호화된 데이터만 저장하는 방식으로 진행
- 관리 불편,
like
검색과 같은 기능 필요 시 고민을 해야함
-
암호화 키 길이를 줄이고 현재 지원하고 있는
AES-128-ECB
를 사용함CBC
에 비해 암호화 보안이 상대적으로 취약할 수 있음- 그러나 잘 동작하고 사용하기도 편함
-
kotlin
으로 구현한AES
(AES-128-ECB
) 예시MariaDB
와 동일한 코드 사용으로 관리 용이
class AESUtil {
var iv: String?
var keySpec: Key?
constructor(key: String, length: Int = 32) {
iv = key.substring(0, length)
val keyBytes = ByteArray(length)
val b = key.toByteArray(StandardCharsets.UTF_8)
var len = b.size
if (len > keyBytes.size) {
len = keyBytes.size
}
System.arraycopy(b, 0, keyBytes, 0, len)
keySpec = SecretKeySpec(keyBytes, "AES")
}
fun encrypt(str: String): String? {
return try {
val c = Cipher.getInstance("AES/ECB/PKCS5Padding")
val key: SecretKey = SecretKeySpec(iv?.toByteArray(), "AES")
c.init(Cipher.ENCRYPT_MODE, key)
val encrypted = c.doFinal(str.toByteArray(StandardCharsets.UTF_8))
String(Base64.encodeBase64(encrypted))
} catch (e: NoSuchAlgorithmException) {
logger.error("AES/CBC/PKCS5Padding 해당 알고리즘을 지원하지 않습니다. ('$str'를 암호화하는데 실패했습니다.)", e)
null
} catch (e: GeneralSecurityException) {
logger.error("키가 잘못되었습니다. ('$str'를 암호화하는데 실패했습니다.)", e)
null
}
}
fun decrypt(str: String): String? {
return try {
val c = Cipher.getInstance("AES/ECB/PKCS5Padding")
val key: SecretKey = SecretKeySpec(iv?.toByteArray(), "AES")
c.init(Cipher.DECRYPT_MODE, key)
val byteStr = Base64.decodeBase64(str.toByteArray())
String(c.doFinal(byteStr), StandardCharsets.UTF_8)
} catch (e: NoSuchAlgorithmException) {
logger.error("AES/CBC/PKCS5Padding 해당 알고리즘을 지원하지 않습니다. ('$str'를 복호화하는데 실패했습니다.)", e)
null
} catch (e: GeneralSecurityException) {
logger.error("키가 잘못되었습니다. ('$str'를 복호화하는데 실패했습니다.)", e)
null
}
}
companion object {
var logger = LoggerFactory.getLogger(AES256Util::class.java)
}
}