데이터 암호화 여부는 매우 중요한 부분이다. 주로 응용 프로그램에서는 중요 정보를 가진 컬럼 단위로 암호화를 수행하고, DB 수준에서는 테이블 단위로 암호화를 적용한다.
MySQL 서버는 DB 서버와 디스크 사이의 데이터 읽기/쓰기 시점에서 암호화 또는 복호화를 수행한다.
따라서, MySQL 서버에서 사용자의 쿼리를 처리하는 과정은 테이블의 데이터의 암호화 유무를 판단할 필요가 전혀 없다. 쿼리를 처리하는 과정은 MySQL 서버 내에서 작동하기 때문이다. 사용자 입장에선 테이블 암호화 여부가 아무런 영향을 주지 않는다.
이런 암호화 방식을 TDE(Transparent Data Encryption) 이라 한다.
MySQL 서버의 TDE에서 암호화 키는 키링(KeyRing) 플러그인에 의해 관리된다. 다양한 플러그인이 제공되지만 마스터 키를 관리하는 방법만 다를 뿐 MySQL 서버 내부적으로 작동하는 방식은 모두 동일하다.
MySQL 서버의 데이터 암호화에 사용되는 키는 다음 두 가지 키가 있다.
MySQL 서버는 마스터 키를 이용해 테이블스페이스 키를 암호화해서 테이블의 데이터 파일 헤더에 저장한다. 이렇게 생성된 테이블스페이스 키는 테이블을 삭제하지 않는한 절대로 사라지지 않는다.
이렇게 2단계 암호화 방식을 채택한 이유는 암호화 키 변경으로 인한 과도한 시스템 부하를 막기 위해서이다.
MySQL 서버의 암호화는 TDE 방식이므로 디스크로부터 한 번 읽어온 데이터 페이지는 복호화되어 InnoDB의 버퍼 풀에 저장한다.
암호화는 디스크에서 데이터를 꺼내올 때만 지연이 발생하므로 이렇게 메모리에 적재되면 암호화되지 않은 테이블과 동일한 성능을 보인다. 디스크로 동기화되야 하는 경우에는 암/복호화 지연이 발생한다.
만약, 같은 테이블에 대해 암호화와 압축이 동시에 적용되면 MySQL 서버는 압축을 먼저 실행하고 암호화를 적용한다. 이유는 다음과 같다.
레플리카 서버와 소스 서버는 모든 사용자 데이터를 동기화하므로 실제 데이터 파일도 동일하다고 생각할 수 있다. 그러나 TDE를 사용한 암호화를 사용하면 그렇지 않다.
MySQL 서버에서는 기본적으로 모든 노드는 각자 다른 마스터 키를 할당해야 하므로, 원격으로 키를 관리하는 경우에도 소스 서버와 레플리카 서버는 서로 다른 마스터 키를 가지고 있어야 한다.
마스터 키가 서로 다르게 관리되므로 테이블스페이스 키 또한 다르게 관리 된다.
결국 소스 서버와 레플리카 서버의 마스터 키와 테이블스페이스 키가 다르므로 암호화 이전 데이터 파일은 같아도 암호화 된 데이터 파일은 완전히 다르다.
ALTER INSTANCE ROTATE INNODB MASTER KEY 명령을 통해 복제 소스 서버의 마스터 키를 변경할 수 있는데, 이 때 명령 자체는 레플리카 서버로 복제되지만 소스 서버의 마스터 키 자체가 레플리카 서버로 전달되는 것은 아니다.
그래서, 마스터 키 로테이션을 실행하면서 소스 서버와 레플리카 서버는 서로 다른 마스터 키를 발급받는다. 이 때문에, 백업 시 TDE의 키링 파일을 백업하지 않는 경우엔 키링 파일을 찾지 못하여 데이터 복구를 할 수 없게 된다.
MySQL 서버의 데이터 암호화 기능인 TDE의 암호화 키 관리는 플러그인 방식으로 제공된다. 대표적인 플러그인 keyring_file 은 테이블스페이스 키를 암호화 하기위한 마스터 키를 디스크에 평문으로 저장하여 관리한다. 따라서, 마스터 키가 저장된 파일이 외부로 노출된다면 데이터 암호화는 무용지물이 되므로 주의하자.
키링 플러그인은 마스터 키를 생성하고 관리하는 부분만 담당한다.
TDE 플러그인의 경우 MySQL 서버가 시작되는 단계에서 가장 빨리 초기화 되어야 한다. 그래서, 다음과 같이 MySQL의 설정 파일인 my.cnf 에서 별도의 시스템 변수를 설정하면 된다.
early-plugin-load=keyring_file.so
keyring_file_data=/very/secure/directory/tde_master.key
참고로, keyring_file_data의 설정 경로는 오직 하나의 MySQL 서버만 참조해야 한다.
MySQL 서버의 설정 파일이 준비되어 MySQL 서버를 재시작하면 자동으로 keyring_file 플러그인이 초기화되는데, 이는 SHOW PLUGIN 명령으로 확인이 가능하다.
keyring_file 플러그인이 초기화되면서 MySQL 서버는 플러그인의 초기화와 동시에 keyring_file_data 시스템 변수의 경로에 빈 파일을 생성한다. 아직 마스터 키를 사용하지 않았으므로 실제 키링 파일의 내용은 비어 있다. 실제 키링 파일은 데이터 암호화 기능을 사용하면 초기화된다.
앞서 언급했듯 키링 플러그인은 마스터키의 생성 및 관리 부분만 담당하므로 어떤 키링 플러그인을 사용하던 간에 암호화된 테이블을 생성하고 활용하는 방법은 모두 동일하다.
TDE를 사용하는 테이블은 다음과 같이 생성할 수 있다.
mysql> CREATE TABLE tab_encrypted
id INT,
data VARCHAR(100),
PRIMARY KEY(id)
) ENCRYPTION='Y';
일반적인 테이블 생성과 동일한데 마지막에 ENCRYPTION='Y'; 옵션만 추가로 넣으면 된다.
만약 MySQL 서버의 모든 테이블에 암호화를 적용하고자 한다면 default_table_encryption 시스템 변수를 ON으로 설정하면 된다.
MySQL이 아닌 응용 프로그램에서 암호화시 MySQL 서버는 이를 인지하지 못한다. 따라서, 응용 프로그램의 암호화와 MySQL 서버의 암호화 중 선택을 하는 상황에선 반드시 MySQL 서버의 암호화 기능을 사용하자.
다음 테이블의 인덱스를 생각해보자.
mysql> CREATE TABLE app_user (
id BIGINT,
enc_birth_year VARCHAR(50), /* 응용 프로그램에서 미리 암호화해서 저장된 컬럼*/
...
PRIMARY KEY(id),
INDEX ix_birthyear (birth_year)
);
app_user 테이블은 암호화되지 않았지만 enc_birth_year 컬럼은 응용 프로그램에 의해 암호화가 된 상태로 테이블에 저장되었다.위 테이블에서 다음 2개의 쿼리 문장을 생각해보자.
1.
mysql> SELECT * FROM app_user WHERE enc_birth_year=#{encryptedYear};
2.
mysql> SELECT * FROM app_user
WHERE enc_birth_year BETWEEN #{encryptedYear} AND #{encryptedMaxYear};
3.
mysql> SELECT * FROM app_user ORDER BY enc_birth_year LIMIT 10;
enc_birth_year 컬럼은 암호화를 해제할 수 없으므로 암호화가 되기 전 상태로 정렬이 불가능하다.따라서, 범위 탐색(레인지 스캔)은 불가능하다.만약, 위의 enc_birth_year 컬럼이 TDE를 통해 암호화를 했다면 MySQL 서버는 인덱스 관련 작업을 모두 처리한 후 최종 디스크 데이터 페이지를 저장할 때만 암호화를 하기 때문에 위같은 제약이 없다.
(오직 디스크와 MySQL 서버에서 데이터를 주고받을 때만 암호화가 고려되고 MySQL 서버 내에서 처리되는 모든 작업들은 암호화가 전혀 고려되지 않는다는 것을 알면 이는 이해하기 쉽다.)
MySQL 서버의 TDE 기능으로 암호화를 한다면 실행중인 MySQL 서버에 로그인할 수 있다면 모든 데이터를 평문으로 조회할 수 있으나 응용 프로그램 암호화를 사용하면 MySQL 서버에 로그인을 했다 하더라도 데이터를 평문으로 조회할 수 없다.
MySQL 서버의 DB 관리자면 테이블스페이스만 이동하는 기능을 자주 사용하게 된다.
참고
테이블스페이스 : 데이터베이스 오브젝트 내 실제 데이터를 저장하는 공간이다. 이것은 데이터베이스의 물리적인 부분이며, 세그먼트로 관리되는 모든 DBMS에 대해 저장소를 할당한다.
테이블을 다른 서버로 복사해야 하는 경우나 특정 테이블의 데이터 파일만 백업했다 복구하는 경우라면 테이블스페이스 이동(Export & Import) 기능이 레코드를 덤프했다 복구하는 방식보다 훨씬 효율적이고 빠르다.
만약, 테이블에 TDE 암호화가 적용되있다면 원본 MySQL 서버와 목적이 MySQL 서버의 마스터 키가 다르기 때문에 신경써야 할 부분이 있다.
MySQL 서버에서는 다음과 같이 FLUSH TABLES 명령으로 테이블스페이스를 Export할 수 있다.
mysql> FLUSH TABLE source_table FOR EXPORT;
이 명령이 실행되면 MySQL 서버가 테이블의 암호화 여부에 따라 어떤식으로 다른 서버로 테이블을 이동시키는지 확인해보자.
암호화 되지 않은 테이블을 이동
source_table 의 저장되지 않은 변경 사항을 모두 디스크로 기록하고, 더 이상 source_table에 접근하지 못하도록 잠금을 건다.source_table 구조를 source_table.cfg로 기록한 후, source_table.ibd 파일과 source_table.cfg 파일을 목적지 서버로 복사한다.UNLOCK TABLES 명령을 통해 source_table을 사용할 수 있게 한다.암호화된 테이블을 이동
source_table.cfp라는 파일에 기록한다.핵심은 암호화된 테이블의 경우 임시 마스터 키가 저장된 *.cfg 파일을 함께 복사해야 한다는 것이다. 이 파일이 없으면 복구가 불가능해진다.
테이블의 암호화는 디스크로 저장되는 데이터만 암호화된다는 사실을 다시 생각해보자. 그러면 MySQL 서버의 메모리에만 존재하는 데이터는 복호화된 상태의 평문으로만 존재하게 된다.
중요한 것은 이 평문 데이터가 디스크로 기록되는 경우에도 여전히 평문을 기록된다. 그래서, 테이블 암호화를 적용해도 리두/언두/바이너리 로그는 평문으로 저장된다.
리두/언두 로그를 암호화한 상태로 저장하기 위해서는 각각 innodb_redo_log_encrypt, innodb_undo_log_encrypt 시스템 변수를 이용하면 된다.
테이블 암호화는 일단 하나의 테이블에 대해 암호화가 적용되면 해당 테이블의 모든 데이터가 암호화되어야 한다. 그러나 리두/언두 로그는 그렇게 할 수 없다.
즉, 실행 중인 MySQL 서버에서 언두/리두 로그를 활성화한다 하더라도 모든 리두/언두 로그는 해당 시점에 한 번에 암호화해서 다시 저장할 수 없다. 그래서, MySQL 서버는 이들을 평문으로 저장하다가 암호화가 활성화 되는 순간부터 생성되는 리두/언두 로그만 암호화해서 저장한다.
특히, 이렇게 암호화를 했다가 다시 비활성화한 이후부터 저장되는 언두 로그는 평문으로 저장되지만 기존의 언두 로그는 여전히 암호화된 상태로 남아있기에 암호화 키가 필요할 수 있다.
리두/언두 로그 데이터 모두 각각의 테이블스페이스 키로 암호화되고 테이블 스페이스 키는 다시 마스터 키로 암호화된다.
이 때, 사용되는 테이블스페이스 키는 실제 테이블의 암호화에 사용된 테이블스페이스 키가 아닌 리두/언두 로그파일을 위한 별도의 프라이빗 키를 의미한다.
바이너리 로그의 경우 언두/리두 로그에 비해 의도적으로 상당히 긴 시간 동안 보관하는 서비스도 있고 때로는 증분 백업(Incremental Backup)을 위해 장시간 보관하기도 한다. 이런 이유로 인해 바이너리 로그 파일의 암호화는 상황에 다라 매우 중요해질 수 있다.
참고
증분 백업 : 연속되는 데이터 사본들에 이전의 백업 복사본이 만들어진 이래로 변경된 부분만을 담은 것이다. 전체 복구가 필요하면 복원 프로세스는 마지막 풀 백업 + 복원 시점까지의 모든 증분 백업이 필요하다바이너리 로그는 데이터의 변화가 발생할 경우 해당 이벤트들을 기록하는 로그 파일이기에 위의 증분 백업에서 사용된다.
바이너리 로그와 릴레이 로그 파일 암호화 기능은 디스크에 저장된 로그 파일에 대한 암호화만 담당하고, MySQL 서버의 메모리 내부 또는 소스 서버와 레플리카 서버 간의 네트워크 구간에서 로그 데이터를 암호화 하진 않는다.
바이너리 로그와 릴레이 로그 파일도 마찬가지로 2단계 암호화 키 방식을 사용한다.
MySQL 서버의 키링 플러그인이 I/O 핸들러를 통해 MySQL 서버와 디스크 간 읽기/쓰기에서 암호화를 적용하듯 이 경우도 바이너리 로그 I/O 핸들러를 통해 암호화를 구현한다.
디스크에 테이블스페이스 키가 저장됬듯 바이너리 로그의 경우엔 바이너리 로그 파일 키가 저장되며 키링 플러그인 을 통해 외부에 노출되는 바이너리 로그 암호화 키는 마스터 키와 동일한 역할을 한다. 이를 요약하면 다음과 같다.
바이너리 로그 암호화 키는 다음과 같이 변경할 수 있다.
mysql> ALTER INSTANCE ROTATE BINLOG MASTER KEY;
바이너리 로그 암호화 키가 변경되면 다음 과정을 거친다.
이 과정에서 4번 과정은 사당한 시간이 걸릴 수 있는데, 키링 파일에서 "바이너리 로그 암호화 키"는 내부적으로 버전(시퀀스 번호) 관리가 이루어진다.
MySQL 서버의 바이너리 로그 파일의 암호화 여부는 다음과 같이 확인할 수 있다.
mysql> SHOW BINARY LOGS;