데이터 암호화는 MySQL 5.7 버전부터 지원하며, MySQL 8.0 버전 부터는 리두 로그, 언두 로그, 복제를 위한 바이터리 로그 등도 모두 암호화 기능을 지원한다.
MySQL 암호화 기능은 데이터베이스 서버와 디스크 사이의 데이터를 읽고 쓰기 지점에서 암호화 또는 복호화를 수행한다. 즉 InnoDB 스토리지엔진의 IO 레이어에서만 데이터의 암호화 복호화 과정이 실행된다는 뜻이다.
MySQL 서버에서는 사용자의 쿼리를 처리하는 과정에서 테이블의 데이터가 암호화 되어있는지 확인할 필요가없다. 왜냐하면, 스토리지에서 데이터를 쓰기전에 실시간으로 암호화하고 데이터를 읽을때도 실시간으로 복호화를 하기 때문이다. 이러한 암호화 방식을 TDE(Transparent Data Encryption) 라고 한다.
MySQL 서버의 TDE 에서 암호화 키는 키링(KeyRing) 플러그인에 관리된다. MySQL 에서 지원되는 키링 플러그인은 아래의 링크에서 확인할 수 있다.
👉👉👉 https://dev.mysql.com/doc/refman/5.7/en/keyring.html
MySQL 2단계 키 관리 아키텍처는 다음 그림과 같다.
키관리 진행 순서는
1. Vault와 같은 외부 키 관리 솔루션 또는 디스크의 파일 에서 마스터키를 가져온다.
2. 암호화된 테이블이 생성될 때마다 해당 테이블을 위한 임의의 테이블 스페이스키를 발급한다.
3. MySQL서버는 마스터키를 가지고 테이블 스페이스키를 암호화해서 각 테이블의 데이터 파일 헤더에 저장한다.
이렇게 생성된 테이블 스페이스키는 테이블이 삭제되지 않는 한 절대 변경 되지 않는다.
그리고 테이블 스페이스키는 서버내부에 저장되어 있기때문에 주기적으로 변경할 필요가 없고, 보안상 취약점도 없다. 하지만,
마스터키는 외부에 노출되어 있기때문에 주기적으로 변경 해줘야한다.
-- 마스터키 변경을 위한 쿼리 --
mysql> ALTER INSTANCE ROTATE INNODB MASTER KEY;
마스터키를 변경하면 MySQL 서버는 기존의 마스터 키를 사용해 각테이블의 테이블스페이스 키를 복호화 한다음 새로운 마스터키롤 암호화 처리를 한다.
Q. MySQL 에서 2단계 암호화 하는 이유는 무엇인가?
암호화 키 변경으로 인한 과도한 시스템 부하를 피하기 위해서 이다.
Q. TDE에서 지원하는 알고리즘은 무엇이 있는가?
AES-256
MySQL 서버의 암호화는 TDE 방식이기 사용하므로, 디스크에서 한번 읽은 데이터 페이지는 복호화 되어 InnoDB 버퍼 풀에 적재된다. 그래서 페이지가 적재되면 암호화 되지 않은 테이블과 동일한 성능을 보인다.
존재하지 않는 데이터 페이지를 읽어 올 경우 복호화 하는 과정을 거치기 때문에 복호화 시간 동안 쿼리 지연이 발생할 수 있다. 그리고 암호화된 테이블이 변경 되면 다시 디스크로 동기화될 때 암호화돼야 하기 때문에 디스크에 저장할 때도 추가로 시간이 더 지연된다. 하지만, MySQL 서버의 백그라운드 스레드가 수행하기 때문에 실제 사용자 쿼리가 지연되는건 아니다.
SELECT
뿐만 아니라 UPDATE
, DELETE
명령 또한 복호화 지연이 발생할 수 있다.
AES 암호화 알고리즘은 암호화할 대상의 평문 길이가 짧을 경우 암호화 키의 크기에 따라 암호화된 결과의 용량이 더 커질 수도 있지만, 이미 데이터 페이지는 암호화 키 보다 훨씬 크기 때문에 암호화 결과와 평문결과가 동일하다.
암호화 한다고 해서 InnoDB 버퍼 풀의 효율이 달라지거나 메모리 사용효율이 떨어지는 현상은 없다는 이야기 이다.
서버는 압축을 먼저 실행하고 암호화를 적용한다.
1. 암호화된 데이터는 랜덤한 바이트 배열을 가지게 되는데, 압축률을 떨어뜨린다. 그래서 최대한 압축효율을 높이기 위해 사용자의 데이터를 그대로 압축해서 용량을 최소화 해야 한다.
2. 암호화된 테이블의 데이터 페이지는 복호화된 상태로 버퍼풀에 저장되지만, 압축된 데이터 페이지는 압축 도는 해제된 상태로 버퍼풀에 존재할 수 있다. 그래서 암호화가 먼저 실행되고 압축이 적용된다면, MySQL 서버는 버퍼풀에 존재하는 데이터 페이지에 대해서도 매번 암복호화 작업을 수 행해야한다.
MySQL 서버의 복제에서 레플리카 서버는 소스서버의 모든 사용자 데이터를 동기화하기 때문에 실제 데이터 파일도 동일할 것이라 생각이 되지만, 그렇지는 않다. MySQL 서버에서 기본적으로 모든 노드는 각자의 마스터키를 할당한다. 마스터 키 자체가 레플리카로 복제 되지 않기 때문에 테이블 스페이스 키. 또한 레플리카로 복제되지 않는다.
그러므로 평문의 데이터는 동일하더라도 암호화된 데이터의 내용은 다르다.
응용프로그램에서 직접암호화 하는 방식은 해당 데이터가 암호화된 것인지 여부를 MySQL 서버에서는 인지 하지 못한다.
그리고 인덱스를 생성하더라도 기능을 활용하지 못한다.
CREATE TABLE app_user(
id BIGINT,
enc_birth_year VARCHAR(50), // 응용프로그램에서 이미 암호화됨
...
PRIMARY KEY(id)
);
// 응용프로그램에서 이미 암호회된 데이터 조회 쿼리
1. SELECT * FROM app_user WHERE enc_birth_year=#{encryptedYear};
2. SELECT * FROM app_user WHERE enc_birth_year BETWEEN #{encryptedYear} AND #{encryptedMaxYear};
3. SELECT * FROM app_user ORDER BY enc_birth_year LIMIMT 10;
위의 3개의 쿼리로는 조회는 할 수 있다. 그러나 이미 암호화된 값이기에 출생년도의 범위의 사용자를 검색하거나, 출생년도 기준으로 정렬은 불가능하다.
MySQL 서버의 암호화 기능(TDE)를 사용한다면 서버는 인덱스 관련된 작업을 처리후 최종 디스크에 데이터 페이지를 저장할 때만 암호화 하기때문에 제약은 없다.
테이블을 다른 서버로 복사해야 하는 경우 또는 특정 테이블의 데이터 파일만 백업했다가 복구하는 경우라면 테이블 스페이스 이동(Export & Import) 기능이 레코드를 덤프 했다가 복구하는 방식보다 훨씬 효율적이고 빠르다.
아래 명령어로 테이블스페이스를 익스포트(Export) 할 수 있다.
mysql> FLUSH TABLES source_table FOR EXPORT;
위의 명령이 실행되면 ... 다음과 같은 과정을 거친다.
1. MySQL 서버는 source_table 의 저장되지 않은 변경 사항을 모두 디스크로 기록
2. 동시에 source_table 구조를 source_table.cfg 파일로 기록
3. source_table.ibd 파일과 source_table.cfg 파일을 목적지 서버로 복사
4. 복사가 완료되면 UNLOCK TABLES 명령어를 실행하여 source_table 사용할 수 있게 하면 끝난다.
테이블의 암호화를 적용하더라도 디스크로 저장되는 데이터만 암호화되고 MySQL 서버의 메모리에 존재하는 데이터는 복호화된 평문으로 관리 된다. 메모리에 존재하는 복호화된 평문 데이터를 테이블 데이터파일 이외에 저장하는 경우엔 여전히 평문으로 저장된다. 리두, 언두, 바이너리 로그도 마찬가지다.
MySQL 8.0.16 버전부터는 innodb_undo_log_encrypt, innodb_redo_log_encrypt
시스템 변수를 사용하여 리두, 언두 로그를 암호화한 상태로 저장할 수 있게 됐다.
실행중인 서버에서 리두, 언두로그를 활성화한다고 해서 모두 암호화되진 않는다. 활성화 시점부터 생성되는 리두, 언두로그만 암호화해서 저장한다.
반대도 비활성화하는 시점에 생성되는 리두, 언두로그만 복호화 된다.
리두, 언 로그 데이터 모두각각의 테이블스페이스 키로 암호화되고, 테이블스페이스 키(프라이빗 키)는 다시 마스터 키로 암호화된다. 암호화된 테이블스페이스키는 리두 로그파일 과 언두 로그 파일 헤더에 저장된다.
InnoDB 리두 로그가 암호화 됐는지는 다음 명령어로 확인할 수 있다.
mysql> SHOW GLOBAL VARIABLES LIKE 'innodb_redo_log_encrypt';
바이너리 로그 암호화는 리두, 언두로그와는 다르게 상당히 긴 시간 동안 보관하는 서비스도 있고 때로는 증분(Incremental Backup) 을 위해 바이너리 로그를 보관하기도 한다. 이런 이유로 바이너리 로그 파일의 암호화는 상황에 따라 중요도가 높아 질 수 있다.
바이너리 로그와 릴레이 로그 파일 암호화 기능은 디스크에 저장된 로그 파일에 대한 암호화만 담당하고, MySQL 서버의 메모리 내부 또는 소스서버와 레플리카 서버 간의 네트워크 구간에서 로그 데이터를 암호화하지 않는다.
바이너리 로그와 릴레이 로그 파일의 데이터는 파일 키(File Key)로 암호화해서 디스크로 저장하고, 파일 키는 바이너리 로그 암호화 키로 암호화해서 각 바이너리 로그와 릴레이 로그 파일의 헤더에 저장된다.
바이너리 로그 암호화 키는 다음 명령어로 변경할 수 있다.
mysql> ALTER INSTANCE ROTATE BINLOG MASTER KEY;
바이너리 로그 암호화 키가 변경되면 다음의 과정을 거친다.
1. 증가된 시퀀스 번호화 함께 새로운 바이너리 로그 암호화 키 발급 후 키링 파일에 저장
2. 바이너리 로그 파일과 릴레이 로그 파일 스위치(새로운 로그 파일로 로테이션)
3. 새로 생성되는 바이너리 로그와 릴레이 로그 파일의 암호화를 위해 파일 키를 생성, 파일키는 바이너리 로그 파일키로 암호화해서 각 로그파일에 저장
4. 기존 바이너리 로그와 릴레이 로그 파일의 파일 키를 읽어서 새로운 바이너리 로그 파일 키로 암호화해서 다시 저장
5. 모든 바이너리 로그와 릴레이 로그 파일이 새로운 바이너리 로그 암호화 키로 다시 암호화됐다면 기존 바이너리 암호화 키를 키링 파일에서 제거
MySQL 서버의 바이너리 로그 파일이 암호화돼 있는지 여부는 다음 명령어로 확인할 수있다.
mysql> SHOW BINARY LOGS;