Featured image of post 7. 데이터 암호화

7. 데이터 암호화

Real MySQL 8.0

데이터 암호화는 MySQL 5.7 버전부터 지원되기 시작했으며, 처음에는 데이터 파일(테이블스페이스)에서만 암호화 기능이 제공 되었으나 MySQL 8.0으로 업그레이드 리두 로그나 언두 로그, 복제를 위한 바이너리 로그 등도 모두 암호화 기능을 지원하기 시작했다.

데이터 암호화 여부는 보안 감사에서 필수적으로 언급되는 부분이며, 핀테크 서비스처럼 중요한 정보를 저장하는 서비스에서는 응용 프로그램에서 암호화한 데이터를 데이터베이스 서버에서 다시 암호화하는 이중 암호화 방법을 선택하기도 한다.

응용 프로그램의 암호화는 주로 중요 정보를 가진 칼럼 단위로 암호화를 수행하며, 데이터베이스 수준에서는 테입르 단위로 암호화를 적용한다.

MySQL 서버의 데이터 암호화

MySQL 서버의 암호화 기능은 데이터베이스 서버와 디스크 사이의 데이터 읽고 쓰기 지점에서 암호화 또는 복호화를 수행한다. 즉 MySQL 서버(InnoDB 스토리지 엔진)의 I/O 레이어에서만 데이터의 암호화 및 복호화 과정이 실행되므로디스크 입출력 이외의 부분에서는 암호화 처리가 전혀 필요치 않다.

MySQL 서버가 사용자의 쿼리를 처리하는 과정에서 테이블의 데이터가 암호화돼 있는지 여부를 식별할 필요가 없으며, 암호화된 테이블도 그렇지 않은 테이블과 동일한 처리 과정을 거친다.

데이터 암호화 기능이 활성화돼 있다고 하더라도 MySQL 내부와 사용자의 입장에서는 아무런 차이가 없기 때문에 이러한 암호화 방식을 가리켜 TDE(Transparent Data Encryption)이라고 한다.

2단계 키 관리

MySQL 서버의 TDE에서 암호화 키는 키링(KeyRing) 플러그인에 의해 관리되며, MySQL 8.0 버전에서 지왼되는 키링 플러그인은 다음과 같다.

  • keyring_file File-Based 플러그인
  • keyring_encrypted_file Keyring 플러그인
  • keyring_okv KMIP 플러그인
  • keyring_aws Amazon Web Services keyring 플러그인

MySQL커뮤니티 에디션에서는 keyring_file 플러그인만 사용 가능하고, 나머지 플러그인은 모두 엔터프라이즈 에디션에서만 사용 가능하다.

다양한 플러그인이 제공되지만 마스터 키를 관리하는 방법만 다를 뿐 MySQL 서버 내부적으로 작동하는 방식은 모두 동일하다. MySQL 서버의 키링 플러그인은 2단계(2-Tier) 키 관리 방식을 사용한다.

MySQL 서버의 데이터 암호화는 마스터 키(master key)와 테이블스페이스 키(tablespace key)라는 두 가지 종류의 키를 가지고 있는데, 테이블스페이스 키는 프라이빗 키(private key)라고도 한다.

  • Hasicorp Vault 같은 외부 키 관리 솔루션(KMS, Key Management Service) 또는 디스크의 파일(Keyring_file 또는 keyring_encrypted_file 플러그인 사용시)에서 마스터 키를 가져오고, 암호화된 테이블이 생성될 때마다 해당 테이블을 위한 임의의 테이블스페이스 키를 발급한다.
  • 마스터 키를 이용해 테이블 스페이스키를 암호화해서 각 테이블의 데이터 파일 헤더에 저장한다.

이렇게 생성된 테이블스프에스 키는 테이블이 삭제되지 않는 이상 절대 변경되지 않지만, 테이블 스페이스키는 절대 MySQL 서버 외부로 노출되지 않기 때문에 테이블스페이스 키를 주기적으로 변경하지 않아도 보안상 취약점이 되지는 않는다.

하지만 마스터 키는 외부의 파일을 이용하기 때문에 노출될 가능성이 있어 주기적으로 변경해야 한다.

1
ALTER INSTANCE ROTATE INNODB MASTER KEY;

마스터키를 변경하면 MySQL서버는 기존의 마스터 키를 이용해 각 테이블의 테이블스페이스 키를 복호화한 다음 새로운 마스터 키로 다시 암호화화한다. 마스터 키가 변경되는 동안 MySQL 서버의 테이블스페이스 키 자체와 데이터 파일의 데이터는 전혀 변경되지 않는다.

MySQL 서버에서 이렇게 2단계 암호화 방식을 사용하는 이유는 암호화 키 변경으로 인한 과도한 시스템 부하를 피하기 위해서다.

테이블스페이스 키가 변경된다면 MySQL 서버는 데이터 파일의 모든 데이터를 다시 복호화했다가 다시 암호화해야 하므로, 키를 변경할 때마다 매우 큰 작업을 수행해야 하며, 이에따라 사용자 쿼리를 처리하는 데도 상당한 영향을 미치게 된다.

MySQL 서버의 TDE에서 지원되는 암호화 알고리즘은 AES 256bit이며, 이외의 알고리즘은 지원되지 않는다.

  • 테이블스페이스 키는 AES-256(Electronic CodeBook) 알고리즘을 이용해 암호화 되고, 실제 데이터 파일은 AES-256 CBC(Cipher Block Chaining) 알고리즘을 이용해 암호화 된다.

암호화 성능

MySQL 서버의 암호화는 TDE 방식이기 때문에 디스크로부터 한 번 읽은 데이터 페이지는 복호화되어 InnoDB 버퍼풀에 적재된다. 따라서 데이터 페이지가 한 번 메모리에 적재되면 암호화되지 않은 테이블과 동일한 성능을 보인다.

  • 쿼리가 InnoDB 버퍼풀에 존재하지 않는 데이터 페이지를 읽어야 하는 경우에는 복호화 과정을 거치기 때문에 복호화 시간동안 쿼리 처리가 지연될 수 있다.
  • 암호화된 테이블이 변경되면 다시 디스크로 동기화될 때 암호화돼야 하기 때문에 디스크에 저장할 때도 추가로 시간이 더 걸린다.
    • 데이터 페이지 저장은 사용자의 쿼리를 처리하는 스레드가 아는 MySQL 서버으 백그라운드 스레드가 수행하기 때문에 실제 사용자 쿼리가 지연되는 것은 아니다.
  • UPDATE, DELETE 명령 또한 변경하고자 하는 레코드를 InnoDB 버퍼풀로 읽어와야 하기 대문에 새롭게 디스크에서 읽어야 하는 데이터 페이지의 개수에 따라서 복호화 지연이 발생할 수 있다.

AES 암호화 알고리즘은 암호화하고자 하는 평문의 길이가 짧은 경우 암호화 키의 크기에 따라 암호화된 결과의 용량이 더 커질수도 있지만, 이미 데이터 페이지는 암호화 키보다 훨씬 크기 때문에 암호화 결과가 평문의 결과와 동일한 크기의 암호문을 반환한다. 따라서 TDE를 적용한다고 해도 데이터 파일의 크기는 암호화되지 않은 테입르과 동일한 크기를 가진다. 즉 암호화한다고 해서 InnoDB 버퍼풀의 효율이 달라지거나 메모리 사용 효율이 떨어지는 현상은 발생하지 않는다.

같은 테이블에 대해 암호화와 압축이 동시에 적용되면 MySQL 서버는 압축을 먼저 실행하고 암호화를 적용한다.

  • 일반적으로 암호화된 결과문은 아주 랜덤한 바이트의 배열을 가지게 되는데, 이는 암축률을 상당히 떨어뜨린다.
  • 암호화된 테이블의 데이터 페이지는 복호화된 상태로 InnoDB 버퍼풀에 저장되지만, 압축된 데이터 페이지는 압축 또는 압축 해제의 모든 상태로 InnoDB 버퍼풀에 전재할 수 있다.

암호화와 복제

MySQL 서버의 복제에서 레플리카 서버는 소스 서버의 모든 사용자 데이터를 동기화할 때 TDE를 이용한 암호화 사용 시 마스터 키와 테이블스페이스 키는 제외된다.

MySQL 서버에서 기본적으로 모든 노드는 각자의 마스터 키를 할당해야 한다. 데이터베이스 서버의 로컬 디렉터리에 마스터 키를 관리하는 경우에는 소스 서버와 레플리카 서버는 서로 다른 마스터 키를 갖도록 설정해야 한다. 마스터 키 자체가 레플리카로 복제되지 않기 때문에 테이블스페이스 키 또한 레플리카로 복제되지 않는다.

결국 소스 서버와 레플리카 서버는 서로 각자의 마스터 키와 테이블 스페이스 키를 관리하기 때문에 복제 멤버들의 데이터 파일은 암호화 되기 전의 값이 동일하더라도 실제 암호화된 데이터가 저장된 데이터 파일의 내용은 완전히 달라진다.

복제 소스 서버의 마스터 키를 변경할 때는 ALERT INSTANCE ROTATE INNODB MASTER KEY 명령을 실행하는데, 이때 명령 자체는 레플리카 서버로 복제되지만 실제 소스 서버의 마스터 키 자체가 레플리카 서버로 전다로디는 것은 아니다. 그래서 마스터 키 로테이션을 실행하면 소스 서버와 레플리카 서버가 각각 서로 다른 마스터 키를 새로 발급받는다.

MySQL 서버의 백업에서 TDE의 키링(Keyr Ring)파일을 백업하지 않는 경우가 있는데, 이 경우 키링 파일을 찾지 못하면 데이터를 복구할 수 없게 된다. 키링 파일을 데이터 백업과 별도로 백업한다면 마스터 키 로테이션 명령으로 TDE의 마스터 키가 엊네 변경됐는지까지 기억하고 있어야 한다.

Keyring_file 플러그인 설치

MySQL 서버의 데이터 암호화 기능인 TDE의 암호화 키 관리는 플러그인 방식을 제공된다.

Keyring_file플러그인은 테이블스페이스 키를 암호화하기 위한 마스터 키를 디스크의 파일로 관리하는데, 이때 마스터 키는 평문으로 디스크에 저장된다. 즉 마스터키가 저장된 파일이 외부에 노출된다면 데이터 암호화는 무용지물이 된다.

keyring_file플러그인은 마스터 키를 암호화하지 않은 상태의 평문으로 로컬 디스크에 저장하기 때문에 보안 요건을 충족시켜주지 않을 수 있다. 그럼에도 keyring_file 플러그인을 사용하고자 한다면 MySQL 서버가 시작될 때만 키링 파일을 다른 서버로부터 다운로드해서 로컬 디스크에 저장한 후 MySQL 서버를 시작하는 방법을 고려할 수 있다. MySQL 서버가 시작되면 마스터 키를 메모리에 캐시하기 때문에 로컬 디스크의 키링 파일을 삭제해도 문제는 전혀 없다. Percona Server는 HashiCorp Vault를 연동하는 키 관리 플러그인을 오픈소스로 제공하므로 함께 검토해보는 것을 권장한다.

TDE 플러그인의 경우 MySQL 서버가 시작되는 단계에서도 가장 빨리 초기화돼야 한다.

1
2
early-plugin-load = keyring_file.so
keyring_file_data = /very/secure/directory/tde_master.key

그래서 다음과 같이 MySQL 서버의 설정 파일(my.cnf)에서 early-plugin-load 시스템 변수에 keyring_file 플러그인을 위한 라이브러리를 명시하면 된다. 그리고 keyring_file 플러그인이 마스터 키를 저장할 키링 파일의 경로를 keyring_file_data 설정에 명시하면 된다.

설정 파일이 준비되면 MySQL 서버 시작시 자동으로 keyring_file플러그인이 초기화된다.

1
SHOW PLUGINS;

초기화와 동시에 지정한 경로에 빈 파일을 생성한다. 데이터 암호화 기능을 사용하는 테이블을 생성하거나 마스터 로테이션을 실행하면 키링 파일의 마스터 키가 초기화된다.

테이블 암호화

키링 플러그인은 마스터 키를 생성하고 관리하는 부분까지만 담당하기 때문에 어떤 키링 플러그인을 사용하든 관계 없이 암호화된 테이블을 생성하고 활용하는 방법은 모두 동일하다.

테이블 생성

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
CREATE TABLE tab_encrypted (
    id INT,
    data VARCHAR(100),
    PRIMARY KEY (id)
) ENCRYPTION='Y';

INSERT INTO tab_encrypted VALUES (1, 'test');

SELECT * FROM tab_encrypted;

/**
+--+----+
|id|data|
+--+----+
| 1|test|
+--+----+
*/

MySQL 서버에서 암호화된 테이블만 검색할 때는 information_schemaTABLES 뷰를 이용한다.

1
2
3
4
5
SELECT table_schema
    ,table_name
    ,create_options
FROM information_schema.tables
WHERE table_name='tab_encrypted';

테이블을 생성할 때마다 옵션을 설정하면 실수로 암호화 적용을 잊어버릴 수도 있으므로 MySQL 서버의 모든 테이블에 암호화를 적용하고자 한다면 default_table_encryption 시스템 변수를 ON으로 설정하면 ENCRYPTION 옵션을 별도로 설정하지 않아도 암호화된 테이블로 생성된다.

응용 프로그램 암호화와의 비교

응용 프로그램에서 직접 암호화해서 MySQL 서버에 저장하는 경우도 있는데, 이 경우 저장되는 칼럼의 값이 이미 암호화된 것인지 여부를 MySQL 서버는 인지하지 못한다. 그래서 응용 프로그램에서 암호화된 컬럼은 인덱스를 생성하더라도 인덱스의 기능을 100% 활용할 수 없다.

응용 프로그램에서 직접 암호화하지 않고 MySQL 서버의 암호화 기능(TDE)을 사용한다면 MySQL 서버는 인덱스 관련된 작업을 모두 처리한 후 최종 디스크에 데이터 페이지를 저장할 때만 암호화 하기 때문에 제약이 줄어든다.

응용 프로그램에서의 암호화 기능은 서비스의 요건과 성능을 고려해서 선택해야 하고, MySQL 서버의 암호화 기능과 혼합해서 사용한다면 더 안전한 서비스를 구축할 수 있을 것이다.

테이블스페이스 이동

MySQL 서버의 데이터베이스 관리자라면 테이블스페이스만 이동하는 기능을 자주 사용하게 되는데, 테이블을 다른 서버로 복사해야 하는 경우 또는 특정 테이블의 데이터 파일만 백업했다가 복구하는 경우라면 테이블스페이스 이동(Export & Import) 기능이 레코드르 덤프했다가 복구하는 방식보다 훨씬 효율적이고 빠르다.

그런데 TDE가 적용되어 암호화된 테이블의 원본 MySQL 서버와 목적지 MySQL 서버의 암호화 키(마스터 키)가 다르기 때문에 FLUSH TABLES 명령으로 테입르스페이스를 익스포트 할 수 있다.

1
FLUSH TABLES souce_table FOR EXPORT;
  1. MySQL 서버는 source_table의 저장되지 않은 변경을 모드 디스크로 기록하고, 더이상 접근할 수 없게 잠금을 건다. 그와 동시에 source_table의 구조를 source_table.cfg 파일로 기록한다.
  2. 암호화된 테이블의 테이블스페이스 키를 기존 마스터 키로 복호화한 후, 임시로 발급한 마스터 키를 이용해 다시 암호화해서 데이터 파일의 헤더 부분에 저장한다.

따라서 암호화된 테이블의 경우 테이블스페이스 이동 기능을 사용할 때는 반드시 데이터 파일과 임시 마스터 키가 저장된 *.cfp 파일을 함께 복사해야 한다. *.cfg파일은 단순히 테이블의 구조만 가지고 있기 때문에 파일이 없어져도 경고만 발생하지만, *.cfp 파일이 없어지면 복구가 불가능해진다.

언두 로그 및 리두 로그 암호화

테이블의 암호화를 적용하더라도 디스크로 저장되는 데이터만 암호화되고 MySQL 서버의 메모리에 존재하는 데이터는 복호화된 평문으로 관리되며, 이 평문 데이터가 테이블의 데이터 파일 이외의 디스크 파일로 기록되는 경우에는 여전히 평문으로 저장된다.

그래서 테이블 암호화를 적용해도 리두 로그나 언두 로그, 그리고 복제를 위한 바이너리 로그에는 평문으로 저장되는 것이다. MySQL 8.0.16 버전 부터는 innodb_undo_log_encrypt, innodb_redo_log_encrypt 시스템 변수를 이용해 언두 로그와 리두 로그를를 암호화 된 상태로 저장할 수 있게 개선되었다.

MySQL 서버는 리두 로그나 언두 로그를 평문으로 저장하다가 암호화가 활성화되면 그때부터 생성되는 리두 로그와 언두 로그만 암호화해서 저장한다. 반대로 리두 로그와 언두 로그가 암호화되는 상태에서 암호화를 비활성화하면 그때부터 저장되는 로그만 평문으로 저장한다.

따라서 리두 로그와 언두 로그는 암호화를 활성화 했다가 비활성화 한다고 해서 즉시 암호화에 사용된 키가 불필요해지는 것이 아니다. 특히 언두 로그의 경우 암호화를 비활성화 한다고 하더라도 새로 생성되는 언두 로그는 평문으로 저장되겠지만 기존 언두 로그는 여전히 암호화된 상태로 남아있어 상황에 따라 계속해서 암호화키가 필요할 수 있다.

바이너리 로그 암호화

테이블 암호화가 적용돼도 바이너리 로그와 릴레이 로그 파일 또한 리두 로그나 언두 로그처럼 평문을 저장한다. 일반적으로 언두 로그와 리두 로그는 기맂 않은 시간동안의 데이터만 가지기 때문에 보안에 민감하지 않을 수 있지만 바이너리 로그 파일의 암호화는 상황에 따라 중요도가 높아질 수 있다.

  • 바이너리 로그는 의도적으로 상당히 긴 시간동안 보관할 수도 있다.
  • 증분 백업(Incremental Backup)을 위해 바이너리 로그를 보관하기도 한다.

바이너리 로그와 릴레이 로그 파일 암호화 기능은 디스크에 저장된 로그 파일에 대한 암호화만 담당하고, MySQL 서버의 메모리 내부 또는 소스 서버와 레플리카 서버 간의 네트워크 구간에서 로그 데이터를 암호화하지는 않는다. 복제 멤버 간의 네트워크 구간에서도 바이너리 로그를 암호화하고자 한다면 MySQL 복제를 위한 계정이 SSL을 사용하도록 설정한다.

바이너리 로그 암호화 키 관리

바이너리 로그와 릴레이 로그 파일 데이터의 암호화를 위해서도 MySQL 서버는 2단계 키 관리 방식을 사용한다.

바이너리 로그와릴레이 로그 파일의 데이터는 파일 키(File Key)로 암호홰해서 디스크로 저장하고, 파일 키는 “바이너리 로그 암호화 키"로 암호화 해서 각 바이너리 로그와 릴레이 로그 파일의 헤더에 저장된다.

즉 “바이너리 로그 암호화 키"는 테이블 암호화의 마스터 키와 동일한 역할을 하며, 파일 키는 바이너리 로그와 릴레이 로그 파일 단위로 자동으로 생성되어 해당 로그 파일의 데이터 암호화에만 사용된다.

바이너리 로그 암호화 키 변경

1
ALTER INSTANCE ROTATE BINLOG MASTER KEY;

바이너리 로그 암호화 키가 변경되면 다음 과정을 거친다.

  1. 증가된 시퀀스 번호와 함께 새로운 바이너리 로그 암호화 키 발금 후 키링 파일에 저장
  2. 바이너리 로그 파일과 릴레이 로그 파일 스위치(새로운 로그 파일로 로테이션)
  3. 새로 생성되는 바이너리 로그와 릴레이 로그 파일의 암호화를 위해 파일 키를 생성하고, 파일 키는 바이너리 로그 파일 키로 암호화해서 각 로그 파일에 저장
  4. 기조 ㄴ바이너리 로그와 릴레이 로그 파일의 파일 키를 읽어서 새로운 바이너리 로그 파일 키로 암호화해서 다시 저장(암호화되지 않은 로그 파일은 무시)
  5. 모든 바이너리 로그와 릴레이 로그 파일리 새로운 바이너리 로그 암호화 키로 다시 암호화됐다면 기조 ㄴ바이너리 로그 암호화 키를 키링 파일에서 제거

4번 과정은 상당히 시간이 걸릴 수 있는데, 이를 위해 키링 파일에서 “바이너리 로그 암호화 키"는 내부적으로 버전 관리가 이뤄진다.

mysqlbinlog 도구 활용

MySQL 서버에서는 트랜잭션의 내용을 추적하거나 백업 복구를 위해 암호화된 바이너리 로그를 평문으로 복호화할 일이 자주 발생한다. 하지만 한 번 바이너리 로그 파일이 암호화되면 바이너리 로그 암호화 키가 없으면 복호화할 수 없다.

그런데 바이너리 로그 암호화 키는 MySQL 서버만 가지고 있어서 복호화가 불가능하다. mysqlbinlog 도구를 이용해 암호화된 바이너리 로그 파일을 직접 열어볼 수 없다는 에러 메시지를 출력하게 된다.

바이너리 로그 암호화 키는 그 바이너리 로그나 릴레이 로그 파일을 생성한 MySQL 서버만 가지고 있기 때문에 MySQL 서버와 관게없이 mysqlbinlog 도구만으로는 복호화할 방법이 없다. 그래서 예전처럼 다른 서버로 복사하거나 바이너리 로그 파일을 백업하는 것은 소용없어졌다.

바이너리 로그 파일의 내용을 확인할 방법은 MySQL 서버를 통해 가져오는 방법이 유일하다.

1
mysqlbinlog --read-from-remote-server -uroot -p -vvv mysql-bin.000011