본 포스트는 Real MySQL 8.0 1권을 읽은 뒤 정리하는 글입니다.

MySQL 서버의 암호화 기능은 위 이미지와 같이 데이터베이스 서버와 디스크 사이의 데이터를 읽고 쓰기 지점에서 암호화 또는 복호화를 수행하는데, MySQL 서버에서 디스크 입출력 이외의 부분에서는 암호화 처리가 필요하지 않아 InnoDB I/O 레이어에서만 데이터의 암호화 및 복호화 과정이 실행된다.
MySQL 서버에서 사용자의 쿼리를 처리하는 과정에서 테이블의 데이터의 암호화 여부와 상관없이 동일한 처리 과정을 거치므로 MySQL 내부와 사용자 입장에서 아무런 차이가 없기 때문에 이러한 암호 방식을 TDE(Transparent Data Encryption) 혹은 Data at Rest Encryption이라고 한다.
MySQL 서버의 TDE에서 암호화 키는 키링(KeyRing) 플러그인에 의해 관리되며 다양한 플러그인이 제공되지만 마스터 키를 관리하는 방법만 다르며 내부적으로 작동하는 방식은 모두 동일하다. MySQL 서버의 키링 플러그인은 2단계(2-Tier) 키 관리 방식을 사용하는데, 아래 이미지는 2단계 키 관리 아키텍처를 보여준다.

MySQL 서버의 데이터 암호화는 마스터 키(master key)와 테이블스페이스 키(tablespace key)라는 두 가지 종류의 키를 가지고 있으며, 테이블스페이스 키는 프라이빗 키(private key)라고도 한다. MySQL 서버는 HashiCorp Vault 같은 외부 키 관리 솔루션 또는 디스크의 파일에서 마스터 키를 가져오고, 암호화된 테이블이 생성될 때마다 해당 테이블을 위한 임의의 테이블스페이스 키를 발급한다. 그리고 MySQL 서버는 테이블스페이스키를 암호화해서 각 테이블의 데이터 파일 헤더에 저장한다. 이렇게 생성된 테이블스페이스 키는 테이블이 삭제되지 않는 이상 절대 변경되지 않지만, 이는 외부로 노출되지 않기 때문에 주기적으로 변경하지 않아도 보안상 취약점이 되지는 않는다.
하지만 마스터 키는 외부의 파일을 이용하기 때문에 노출될 가능성이 있어 주기적으로 변경해야 한다. 이는 다음과 같이 변경할 수 있다.
mysql> ALTER INSTANCE ROTATE INNODB MASTER KEY;
마스터 키를 변경하면 MySQL 서버는 기존의 마스터 키를 이용해 각 테이블의 테이블스페이스 키를 복호화한 다음 새로운 마스터 키로 다시 암호화하며, 변경 과정에서 테이블스페이스 키 자체와 데이터 파일의 데이터는 전혀 변경되지 않는다.
MySQL 서버에서 2단계 암호화 방식을 사용하는 이유는 암호화 키 변경으로 인한 과도한 시스템 부하를 피하기 위해서다. 테이블스페이스 키가 변경된다면 MySQL 서버는 데이터 파일의 모든 데이터를 다시 복호화했다가 다시 암호화해야 한다. 이로 인해 키를 변경할 때마다 엄청난 작업을 해야 하며, 사용자 쿼리를 처리하는 데도 상당한 영향을 미치게 된다.
MySQL 서버의 암호화는 TDE 방식이기 때문에 디스크로부터 한 번 읽은 데이터 페이지는 복호화되어 InnoDB의 버퍼 풀에 적재되므로 암호화되지 않은 테이블과 동일한 성능을 보인다. 하지만 쿼리가 InnoDB 버퍼 풀에 존재하지 않는 데이터 페이지를 읽어야 하는 경우에는 복호화 과정을 거치기 때문에 쿼리 처리가 지연된다. 또한 암호화된 테이블이 변경되면 다시 디스크로 동기화될 때 암호화돼야 하기 때문에 디스크에 저장할 때도 추가로 시간이 걸린다. 하지만 데이터 페이지 저장은 MySQL 서버의 백그라운드 스레드가 수행하기 때문에 실제 사용자 쿼리가 지연되는 것은 아니다.
같은 테이블에 대해 암호화와 압축이 동시에 적용되면 MySQL 서버는 압축을 먼저 실행하고 암호화를 적용하는데, 이유는 다음과 같다.
MySQL 서버에서 기본적으로 모든 노드는 각자의 마스터 키를 할당해야 한다. 데이터베이스의 서버의 마스터 키를 관리하는 경우 로컬 디렉토리든 원격 키 관리 솔루션을 사용하든 소스 서버와 레플리카 서버는 서로 다른 마스터 키를 갖도록 설정해야 한다. 마스터 키 자체가 레플리카로 복제되지 않기 때문에 테이블스페이스 키 또한 복제되지 않아, 소스 서버와 레플리카 서버는 서로 각자의 마스터 키와 테이블스페이스 키를 관리하기 때문에 복제 멤버들의 데이터 파일은 암호화 되기 전 값이 동일하더라도 실제 암호화된 데이터가 저장된 데이터 파일의 내용은 완전히 달라진다.
MySQL 서버의 백업에서 TDE의 키링(Key Ring) 파일을 찾지 못하면 데이터를 복구할 수 없게 된다. 키링 파일을 데이터 백업과 별도로 백업한다면 마스터 키 로테이션 명령으로 TDE의 마스터 키가 언제 변경됐는지까지 기억하고 있어야 한다. 보안을 위해 데이터 파일과 키링 파일을 별도로 보관하는 것을 권장하지만, 마스터 키도 계속 변경될 수 있으므로 복구를 감안하고 백업 방식을 고려해야 한다.
TDE를 이용하는 테이블은 아래와 같이 일반적인 테이블 생성 구문 마지막에 ENCRYPTION='Y' 옵션만 추가로 넣으면 되며, MySQL 서버의 모든 테이블에 대해 암호화를 적용하고자 한다면 default_table_encryption 시스템 변수를 ON으로 설정하면 된다.
mysql> CREATE TABLE tab_encrypted (
id INT,
data VARCHAR(100),
PRIMARY KEY(id)
) ENCRYPTION='Y';
응용 프로그램의 암호화와 MySQL 서버의 암호화 기능 중 선택해야 하는 상황이라면 고민할 필요 없이 MySQL 서버의 암호화 기능을 선택할 것을 권장한다. 물론 둘의 암호화 목적과 용도가 조금 다르나, MySQL 서버의 TDE 기능으로 암호화한다면 실행 중인 MySQL 서버에 로그인만 할 수 있다면 모든 데이터를 평문으로 확인할 수 있으나 응용 프로그램 암호화는 불가하다. 따라서 응용 프로그램에서의 암호화 기능은 서비스 요건 및 성능을 고려해 선택하고, MySQL 서버의 암호화 기능과 혼합해 사용한다면 더 안전한 서비스를 구축할 수 있을 것이다.
테이블을 다른 서버로 복사하거나 특정 테이블 데이터 파일만 백업했다가 복구하는 경우 테이블스페이스 이동(Export & Import) 기능이 레코드를 덤프했다가 복구하는 방식보다 훨씬 효율적이고 빠르다. MySQL 서버에서 다음과 같이 FLUSH TABLES 명령으로 테이블스페이스를 익스포트(Export)할 수 있다.
mysql> FLUSH TABLES source_table FOR EXPORT;
암호화되지 않은 테이블의 테이블스페이스 복사는 위 명령이 실행되면 MySQL source_table의 저장되지 않은 변경 사항을 모두 디스크로 기록하고, 더이상 source_table에 접근할 수 없게 잠금을 걸며 동시에 source_table 구조를 source_table.cfg 파일로 기록해둔다. 그러면 source_table.ibd 파일과 source_table.cfg 파일이 목적지 서버로 복사되며, UNLCOK TABLES 명령을 실행해 source_table을 사용할 수 있게 된다.
반면 TDE로 암호화된 테이블에 대해 위 명령을 실행하면 MySQL 서버는 임시로 사용할 마스터 키를 발급해서 source_table.cfp라는 파일로 기록한다. 그리고 암호화된 테이블의 테이블스페이스 키를 기존 마스터 키로 복호화한 후, 임시로 발급한 마스터 키를 이용해 다시 암호화해서 데이터 파일의 헤더 부분에 저장한다. 그래서 암호화된 테이블의 경우 테이블스페이스 이동 기능을 사용할 때는 반드시 데이터 파일과 임시 마스터 키가 저장된 *.cfp 파일을 함께 복사해야 한다. *.cfg 파일은 단순히 테이블의 구조만 가지고 있기 때문에 파일이 없어져도 경고만 발생하고 테이블스페이스를 복구할 수 있지만, *.cfp 파일이 없어지면 복구가 불가능해진다.
테이블의 암호화를 적용하더라도 디스크로 저장되는 데이터만 함호화되고 MySQL 서버 메모리에 존재하는 데이터는 복호화된 평문으로 관리되며, 이 평문 데이터가 테이블의 데이터 파일 이외의 디스크 파일로 기록되는 경우에는 여전히 평문으로 저장되므로 테이블에 암호화를 적용해도 리두 로그, 언두 로그, 그리고 복제를 위한 바이너리 로그에는 평문으로 저장된다. MySQL 8.016 버전부터는 innodb_undo_log_encrypt 시스템 변수와 innodb_redo_log_encrypt 시스템 변수를 이용해 리두 로그와 언두 로그를 암호화된 상태로 저장할 수 있게 개선됐다.
리두 로그와 언두 로그는 암호화가 적용되어도 해당 테이블의 모든 데이터가 암호화를 적용할 수 없다. 그래서 MySQL 서버는 리두 로그나 언두 로그를 평문으로 저장하다가 암호화가 활성화되면 그때부터 생성되는 리두 로그나 언두 로그만 암호화해서 저장하며, 암호화를 비활성화하는 경우에는 그때부터 저장되는 로그만 평문으로 저장한다. 그래서 리두 로그와 언두 로그는 암호화를 활성화했다가 비활성화한다고 해서 즉시 암호화에 사용된 키가 불필요해지는 것이 아니다.
리두 로그와 언두 로그 데이터 모두 각각의 테이블스페이스 키로 암호화되고, 테이블스페이스 키는 다시 마스터 키로 암호화된다. 리두 로그와 언두 로그 데이터의 암호화에 사용되는 테이블스페이스 키는 실제 테이블의 암호화에 사용된 테이블스페이스 키가 아닌 리두 로그와 언두 로그 파일을 위한 프라이빗 키를 의미한다. 즉 리두 로그와 언두 로그를 위한 각각의 프라이빗 키가 발급되고, 해당 프라이빗 키는 마스터 키로 암호화되어 리두 로그 파일과 언두 로그 파일의 헤더에 저장되는 것이다.
테이블 암호화가 적용돼도 바이너리 로그와 릴레이 파일은 평문을 저장한다. 바이너리 로그는 의도적으로 상상히 긴 시간 동안 보관하는 서비스도 있고 때로는 증분 백업(Incremental Backup)을 위해 바이너리 로그를 보관하므로 바이너리 로그 파일의 암호화는 상황에 따라 중요도가 높아질 수도 있다.
바이너리 로그와 릴레이 로그 파일 암호화 기능은 디스크에 저장된 로그 파일에 대한 암호화만 담당하고, MySQL 서버의 메모리 내부 또는 소스 서버와 레플리카 서버 간의 네트워크 구간에서 로그 데이터를 암호화하지는 않는다. 복제 멤버 간의 네트워크 구간에서도 바이너리 로그를 암호화하고자 한다면 MySQL 복제를 위한 계정이 SSL을 사용하도록 설정하면 된다.

위 이미지는 바이너리 로그 파일의 암호화 방식으로, 바이너리 로그와 릴레이 로그 파일의 데이터는 파일 키로 암호화해서 디스크로 저장하고, 파일 키는 바이너리 로그 암호화 키로 암호화해서 각 바이너리 로그와 릴레이 로그 파일의 헤더에 저장된다. 즉 바이너리 로그 암호화 키는 테이블 암호화의 마스터 키와 동일한 역할을 하며, 파일 키는 바이너리 로그와 릴레이 로그 파일 단위로 자동으로 생성되어 해당 로그 파일의 데이터 암호화에서만 사용된다.
바이너리 로그 암호화 키는 다음과 같이 변경할 수 있다.
mysql> ALTER INSTANCE ROTATE BINLOG MASTER KEY;
바이너리 로그 암호화 키가 변경되면 다음과 같은 과정을 거친다.