MySQL 5.6 버전부터 데이터 암호화를 지원했다. 처음에는 데이터 파일(테이블 스페이스)에 대한 암호화만 제공했다. MySQL 8.0 버전부터 리두 로그, 언두 로그, 바이너리 로그 등도 암호화 기능을 제공하기 시작했다.
MySQL 서버의 데이터 암호화
Transparent Data Encryption
MySQL 서버는 디스크 저장(at rest) 단계에서 데이터 암호화를 제공한다. 즉, 스토리지 엔진의 I/O 레이어에서 데이터 암호화 및 복호화를 한다. 결국 InnoDB 버퍼 풀에는 평문 데이터 페이지만 존재하기 때문에 MySQL 서버 및 사용자가 테이블의 암호화 유무를 구별할 필요가 없다.
2 단계 키 관리
MySQL 서버는 데이터 파일 암호화에 마스터 키와 테이블스페이스 키를 사용한다. 테이블스페이스 키는 프라이빗 키라고도한다.
MySQL 서버의 암호화 과정은 다음과 같다.
1. MySQL 서버는 외부 키 관리 솔루션이나 디스크의 파일에서 마스터 키를 가져온다.
2. 암호화된 테이블이 생성될 때마다 해당 테이블을 위한 테이블스페이스 키를 발급한다.
3. 마스터 키를 이용해 테이블스페이스 키를 암호화해서 각 테이블의 데이터 파일 헤더에 저장한다.
4. 테이블스페이스 키를 통해 데이터를 암호화 및 복호화한다.
테이블스페이스 키는 테이블이 삭제되지 않는 한 절대 변경되지 않는다. 테이블스페이스키는 MySQL 서버 외부로 노출되지 않으므로 주기적으로 변경하지 않아도 보안상 취약점이 되지 않는다.
하지만 마스터 키는 외부의 파일을 이용하기 때문에 취약점이 될 수 있다. 따라서 주기적으로 마스터 키를 변경해야 한다. 다음과 같이 변경할 수 있다.
mysql> ALTER INSTANCE ROTATE INNODB MASTER KEY;
마스터 키를 변경하면 다음과 같은 과정이 발생한다.
1. 기존의 마스터 키를 사용해 테이블스페이스 키를 복호화한다.
2. 새로운 마스터키로 테이블스페이스 키를 암호화한다.
마스터 키가 변경되는 동안 MySQL 서버의 테이블스페이스 키 자체와 데이터 파일의 데이터는 변경되지 않는다. 2 단계 암호화 방식을 통해 암호화 키 변경으로 인한 과도한 시스템 부하를 피할 수 있다.
MySQL 서버의 TDE에서 지원되는 암호화 알고리즘은 AES-256이다.
마스터 키는 키링(KeyRing) 플러그인에 의해 관리된다.
암호화와 성능
MySQL 서버의 암호화는 TDE로 실제 메모리에 적재되는 데이터 페이지는 평문이다. 따라서 다음과 같은 동작에서 성능을 고려해야 한다.
1. 디스크에서 버퍼풀로 데이터 페이지 읽기
2. 버퍼풀에서 디스크로 데이터 페이지 쓰기
읽기의 경우 복호화 과정이 필요하기 때문에 성능 저하가 발생할 수 있다. 쓰기의 경우도 암호화 과정이 필요하다. 하지만 디스크 쓰기의 경우 MySQL 서버의 백그라운드 스레드가 수행하기 때문에 실제 사용자 쿼리가 지연되는 것은 아니다.
같은 테이블에 암호화와 압축이 동시에 적용되면 MySQL 서버는 압축을 먼저 실행하고 암호화를 실행한다. 이유는 다음과 같다.
1. 일반적으로 암호화된 결과문은 랜덤한 바이트 배열을 가지게 된다. 이는 압축 효율을 떨어뜨린다.
2. InnoDB 버퍼풀에는 복호화된 데이터 페이지만 저장된다. 하지만 압축된 데이터 페이지와 압축 해제 상태의 데이터 페이지는 둘 다 존재할 수 있다. 따라서 압축 후 암호화를 하면 버퍼 풀로 데이터 페이지를 읽을 때 복호화 과정만 수행하면 된다. 반대로 암호화 이후 압축을 한 경우 압축 해제 후 복호화 과정을 거쳐 버퍼 풀에 데이터 페이지를 적재해야 한다.
암호화와 복제
MySQL 서버 노드는 각각 다른 마스터 키를 가져야 한다. 즉, 소스 서버와 레플리카 서버의 마스터 키와 테이블스페이스 키는 서로 다르다. 때문에 복제 멤버들의 데이터 파일은 암호화되기 전의 값이 동일하더라도 각 서버에 저장된 데이터 파일의 내용은 전혀 다르다.
테이블 암호화
테이블 생성
TDE를 사용하는 테이블은 다음과 같이 생성할 수 있다.
mysql> CREATE TABLE tab (
id INT,
data VARCHAR(100),
PRIMARY KEY(id)
) ENCRPYTION='Y';
tab 테이블은 데이터가 디스크에 저장될 때 자동으로 암호화되어 저장되고, 다시 메모리로 읽을 때 복호화된다.
MySQL 서버의 모든 테이블에 대해 암호화를 적용하고자 하면 default_table_encrpytion 시스템 변수를 ON으로 설정하면 된다.
응용 프로그램 암호화와의 비교
응용 프로그램에서 암호화를 한 뒤 MySQL 서버에 저장하는 경우도 있다. 이 경우 저장되는 컬럼의 값이 암호화된 값인지 MySQL은 인지하지 못한다. 그래서 MySQL은 암호화된 컬럼에 대해 인덱스를 생성하더라도 인덱스의 기능을 온전히 활용할 수 없다. 다음 예시를 보자.
mysql> CREATE app_user(
id BIGINT,
enc_birthday VARCHAR(50), // 응용프로그램에서 미리 암호화하는 컬럼
...
PRIMARY KEY(id),
INDEX ix_birthyear (birth_year)
);
app_user 테이블은 암호화되지 않지만 애플리케이션 레벨에서 enc_birthday를 암호화하여 저장하고 있다. 이제 다음 쿼리를 보자.
mysql> SELECT * FROM app_user WHERE enc_birth_year=#{encryptedYear} ;
mysql> SELECT * FROM app_user
WHERE enc_birth_year BETWEEN #{encryptedMinYear} AND #{encryptedMaxYear} ;
mysql> SELECT * FROM app_user ORDER BY enc_birth_year LIMIT 10;
첫 번째 쿼리는 동등 연산을 하기 때문에 인덱스를 사용해 검색할 수 있다.
하지만 나머지 쿼리는 일정 범위의 사용자를 검색하기 때문에 암호화된 컬럼의 인덱스는 범위를 좁히지 못한다.
MySQL 서버는 암호화된 컬럼으로 인덱스를 정렬했기 때문에 암호화되기 전의 값으로 값을 정렬할 수 없다. 만약 MySQL의 TDE 기능을 사용했다면 인덱스 관련 작업을 모두 처리한 후 디스크에 데이터 페이지를 저장하기 때문에 이와 같은 제약은 발생하지 않는다.
애플리케이션 암호화와 MySQL 서버 암호화 중 하나를 선택해야 한다면 MySQL 서버 암호화를 선택해야 한다. 하지만 응용프로그램 암호화를 적용한다면 MySQL 서버에 접속하더라도 데이터를 평문으로 확인할 수 없기 때문에 더욱 강력한 보안을 가질 수 있다.
언두 로그 및 리두 로그 암호화
테이블 암호화를 적용하더라도 디스크로 저장되는 데이터만 암호화되고 MySQL 서버의 메모리의 데이터는 복호화된 평문으로 관리된다. 이 평문 데이터가 데이터 파일 이외의 디스크 파일로 기록되는 경우에도 여전히 평문으로 저장된다. 대표적으로 언두 로그, 리두 로그, 바이너리 로그에 저장될 때이다. 그래서 MySQL 8.0.16 버전부터 리두 로그와 언두 로그를 암호화하여 저장할 수 있게 개선되었다.
테이블 암호화는 암호화가 적용되면 해당 테이블의 모든 데이터가 암호화돼야 한다. 하지만 리두 로그나 언두 로그는 그렇게 적용할 수 없다. 즉, 실행 중인 MySQL 서버에서 언두 로그, 리두 로그 암호화를 활성화해도 모든 리두 로그와 언두 로그 데이터를 한 번에 암호화하여 다시 저장할 수 없다. 그래서 암호화가 활성화되면 그때부터 생성되는 로그만 암호화 해서 저장한다.
암호화를 비활성화하면 그때부터 저장되는 로그만 평문으로 저장한다. 그래서 암호화에 사용된 키가 남아있는 로그를 복호화하는 데 사용되어 길게는 몇 달 동안 암호화 키가 필요할 수도 있다.
리두 로그와 언두 로그 암호화 역시 테이블 암호화와 비슷한 방식으로 동작한다. 아래 동작 과정을 보자.
1. 리두 로그와 언두 로그 데이터 모두 각각의 프라이빗 키로 암호화된다.
2. 프라이빗 키는 마스터 키로 암호화된다.
3. 암호화된 프라이빗 키는 리두 로그와 언두 로그 파일의 헤더에 저장된다.
바이너리 로그 암호화
테이블 암호화가 적용돼도 바이너리 로그나 릴레이 로그 파일은 평문을 저장한다. 일반적으로 리두 로그와 언두 로그는 길지 않은 시간 동안의 데이터만 가져 보안에 민감하지 않을 수 있다. 하지만 바이너리 로그는 상당히 긴 시간 동안 데이터를 보관할 수 있기 때문에 보안에 중요도가 높아질 수 있다.
바이너리 로그와 릴레이 로그 파일 암호화는 디스크에 저장된 로그 파일에 대한 암호화를 제공한다. MySQL 서버의 메모리, 레플리카 서버와의 네트워크 구간에서는 암호화를 하지 않는다.
바이너리 로그 암호화 키 관리
바이너리 로그 암호화에는 파일 키와 바이너리 로그 암호화 키를 사용한다. 파일 키로 데이터 파일을 암호화하고, 바이너리 로그 암호화 키로 파일 키를 암호화한다. 즉, 바이너리 로그 암호화 키는 마스터키와 동일하며, 파일 키는 바이너리 로그 파일 단위로 자동 생성된다.
바이너리 로그 암호화 키 변경
다음과 같은 명령어로 변경할 수 있다.
>mysql ALTER INSTANCE ROTATE BINLOG MASTER KEY;
다음과 같은 과정을 거쳐 바이너리 로그 암호화 키가 변경된다.
1. 증가된 시퀀스 번호와 함께 새로운 바이너리 로그 암호화 키 발급 후 키링 파일에 저장
2. 바이너리 로그 파일 스위치 (새로운 로그 파일로 로테이션)
3. 스위치 된 로그 파일을 위한 새로운 파일 키 생성
4. 바이너리 로그 암호화 키로 파일 키 암호화 후 로그 파일 헤더에 저장
5. 기존 바이너리 로그를 기존 파일 키로 복호화 후 새로운 파일 키로 다시 암호화 (암호화되지 않은 로그 파일은 무시)
6. 모든 바이너리 로그 파일이 새로운 바이너리 로그 암호화 키로 암호화 됐다면 기존 바이너리 암호화 키를 키링에서 제거
5번 과정은 상당히 긴 시간이 걸릴 수 있다. 그래서 시퀀스 번호를 통해 바이너리 암호화 키를 관리한다.
mysqlbinlog 활용
MySQL 서버에서 트랜잭션의 내용을 추적하거나 백업 복구를 위해 암호화된 바이너리 로그를 복호화해야 할 경우가 자주 발생한다. 하지만 한 번 암호화된 바이너리 로그 파일은 바이너리 로그 암호화 키가 없으면 복호화할 수 없다. 그런데 바이너리 암호화 키는 MySQL 서버만 가지고 있어서 mysqlbinlog 도구로는 복호화가 불가능하다. 유일한 방법은 MySQL 서버를 통해 직접 가져오는 것이다.
linux> mysqlbinlog --read-from-remote-server -u root -p -vvv mysql-bin.000011
'Database' 카테고리의 다른 글
[MySQL] B-Tree 인덱스 - 1 (0) | 2023.05.04 |
---|---|
[MySQL] 인덱스 - 개요 (0) | 2023.04.21 |
[MySQL] 데이터 압축 (0) | 2023.03.24 |
[MySQL] 잠금 (0) | 2023.03.15 |
[MySQL] 트랜잭션 (0) | 2023.03.08 |