1. MySQL 서버의 데이터 암호화
5.7 버전부터: 데이터 암호화 기능 지원, 처음에는 데이터 파일(테이블스페이스)에 대해서만 암호화 기능 제공
8.0 버전 이후: 데이터 파일, 리두 로그, 언두 로그, 복제를 위한 바이너리 로그 등 모두 암호화 기능 제공
데이터베이스 서버와 디스크 사이의 데이터를 읽고 쓰는 지점에서 암호화/복호화
-> 디스크 입출력 ㅣㅇ외의 부분에서는 압호화 처리 필요 X
== MySQL 서버(InnoDB 스토리지 엔진)의 I/O 레이어에서만 데이터 암호화/복호화 과정 실행

사용자의 쿼리를 처리하는 과정에서 테이블의 데이터가 암호화돼 있는지 여부 식별할 필요 X
암호화 O 테이블, 암호화 X 테이블 => 동일한 처리 과정: 데이터 암호화 기능이 활성화 O -> MySQL 내부와 사용자 입장에서 차이 X
=> TDE(Transparent Data Encryption) (주로 사용), Data at Rest Encryption
Data at Rest: 메모리(In-Process)나 네트워크 전송(In-Transit) 단계 X 디스크에 저장(At Rest)된 단계에서만 암호화
1) 2단계 키 관리
TDE에서 암호화 키 -> 키링(KeyRing) 플러그인에 의해 관리
- `keyring_file`: File-Based 플러그인 (커뮤니티 에디션)
- `keyring_encrypted_file`: Keyring 플러그인
- `keyring_okv`: KMIP 플러그인
- `keyring_aws`: Amazaon Web Services Keyring 플러그인
=> 마스크 키를 관리하는 방법만 다르고 서버 내부적으로 작동하는 방식은 동일
마스터 키(master key) / 테이블스페이스 키(tablespace key) == 프라이빗 키(private key)
서버는 HashiCrop Vault 같은 외부 키 관리 솔루션(KMS, Key Management Service)
or 디스크 파일(`keyring_file` or `keyring_encrypted_file` 플러그인 사용)에서 마스터 키 가져옴
암호화된 테이블이 생성될 때마다 해당 테이블을 위한 임의의 테이블스페이스 키 발급
MySQL 서버는 마스터 키 -> 테이블스페이스 키 암호화 -> 각 테이블의 데이터 파일 헤더에 저장
=> 테이블스페이스 키: 테이블 삭제되지 않는 이상 절대 변경 X
/ 마스터 키는 외부의 파일 이용 -> 노출될 가능성 ↑ => 주기적으로 MySQL 서버의 마스터 키 변경
ALTER INSTANCE ROTATE INNODB MASTER KEY;
기존의 마스터 키 -> 각 테이블의 테이블스페이스 키를 복호화, 새로운 마스터 키로 다시 암호화
마스터 키가 변경되는 동안 MySQL 서버의 테이블스페이스 키 자체와 데이터 파일의 데이터 변경 X
2단계 암호화 방식 사용하는 이유: 암호화 키 변경으로 인한 과도한 시스템 부하를 피하기 위해
테이블스페이스 키 변경 -> MySQL 서버는 데이터 파일의 모든 데이터 다시 복화했다가 암호화
=> 키 변경할 때마다 작업량 ↑, 사용자 쿼리를 처리하는 데도 영향 O
TDE에서 지원하는 암호화 알고리즘: AES 256비트
테이블스페이스 키: AES-256 ECB(Electronic CodeBook) 알고리즘 -> 암호화
실제 데이터 파일: AES-256 CBC(Cipher Block Chaining) 알고리즘 -> 암호화
2) 암호화와 성능
TDE(Transparent Data Encryption) 방식
-> 디스크로부터 한 번 읽은 데이터 페이지는 복호화되어 InnoDB의 버퍼 풀에 저재
-> 데이터 페이지가 한 번 메모리에 적재되면 암호화 X 테이블과 동일한 성능
But, 쿼리가 InnoDB 버퍼 풀에 존재 X 데이터 페이지를 읽어야 하는 경우에는 복호화 과정 거침 => 복호화 시간 동안 쿼리 처리 지연
암호화된 테이블 변경 -> 다시 디스크로 동기화될 때 암호화돼야 함 => 디스크 저장할 때 추가로 시간 더 걸림
But, 데이터 페이지 저장: 사용자의 쿼리를 처리하는 스레드 X MySQL 서버의 백그라운드 스레드가 수행 -> 실제 사용자 쿼리 지연 X
`SELECT`, `UPDATE`, `DELETE` 명령: 변경하고자 하는 레코드를 InnoDB 버퍼 풀로 읽어와야 함
=> 새롭게 디스크에서 읽어야 하는 데이터 페이지의 개수에 따라서 복호화 지연 발생
AES(Advanced Encryption Standard) 암호화 알고리즘
암호화하고자 하는 평문의 길이 ↓ -> 암호화 키의 크기에 따라 암호화된 결과의 용량이 더 커질 수 있지만, 이미 데이터 페이지 > 암호화 키
-> 암호화 결과가 평문의 결과와 동일한 크기의 암호문 반환
=> TDE 적용해도 데이터 파일의 크기 == 암호화 X 테이블 크기
== 암호화한다고 해서 InnoDB 버퍼 풀의 효율이 달라지거나 메모리 사용 효율이 떨어지는 현상 X
같은 테이블에 대해 암호화와 압축 동시 적용 -> 압축 먼저 실행하고 암호화 적용
- 암호화된 결과문: 아주 랜덤한 바이트의 배열을 가짐 -> 압축률 ↓
-> 최대한 압축 효율 ↑ 위해 사용자의 데이터를 그대로 압축해서 용량을 최소화한 후 암호화를 적용 - 암호화된 테이블의 데이터 페이지는 복호화된 상태로 InnoDB 버퍼 풀에 저장, 압축된 데이터 페이지는 압축 or 압축 해제의 모든 상태로 InnoDB 버퍼 풀에 존재할 수 있음
-> 암호화가 먼저 실행되고 압축 적용: InnoDB 버퍼 풀에 존재하는 데이터 페이지에 대해서도 매번 암복호화 작업을 수행해야 함
암호화된 테이블의 경우 읽기는 3~5배 정도 느리고, 쓰기는 5~6배 느림
(밀리초 단위이므로 체감되지 않을 수 있음)
3) 암호화와 복제
MySQL 서버의 복제에서 레플리카 서버: 소스 서버의 모든 사용자 데이터를 동기화
-> 실제 데이터 파일도 동일할 것이라고 생각할 수 있음
But, TDE를 이용한 암호화 사용 -> 마스터 키, 테이블스페이스 키 동일 X
서버에서 기본적으로 모든 노드는 각자의 마스터 키를 할당해야 함
데이터베이스 서버의 로컬 디렉터리에 마스터 키를 관리하는 경우: 소스 서버 != 레플리카 서버 키
/ 원격으로 관리하는 경우: 소스 서버 != 레플리카 서버 마스터 키
-> 마스터 키: 레플리카로 복제 X -> 테이블스페이스 키: 레플리카로 복제 X
=> 소스 서버, 레플리카 서버: 각자의 마스터 키, 테이블스페이스 키 관리
-> 복제 멤버들의 데이터 파일은 암호화되기 전의 값이 동일해도 실제 암호화된 데이터가 저장된 데이터 파일 내용은 완전히 달라짐
복제 소스 서버의 마스터 키 변경: `ALTER INSTANCE ROTATE INNODB MASTER KEY` 명령 실행
명령은 레플리카 서버로 복제 But, 실제 소스 서버의 마스터 키 자체가 레플리카 서버로 전달되는 것 X
-> 마스터 키 로테이션 실행: 소스 서버, 레플리카 서버가 각각 서로 다른 마스터 키를 새로 발급 받음
MySQL 서버의 백업에서 TDE의 키링(Key Ring) 파일을 백업하지 X 경우 O
-> 키링 파일 찾지 못하면 데이터 파일 복구 X
키링 파일을 데이터 백업과 별도로 백업 -> 마스터 키 로테이션 명령으로 TDE의 마스터 키가 언제 변경됐는지까지 기억해야 함
보안 -> 키링 파일을 데이터 파일과 별도로 보관하는 것 (권장)
마스터 키도 계속 변경될 수 있기 때문에 백업마다 키링 파일의 백업 고려 필요
2. Keyring_file 플러그인 설치
TDE의 암호화 키 관리: 플러그인 방식을 제공
`keyring_file` 플러그인
- MySQL 커뮤니티 에디션
- 테이블스페이스 키를 암호화하기 위한 마스터 키 -> 디스크의 파일로 관리 (=> 보안 요건 충족 X)
- 마스터 키는 평문으로 디스크에 저장
=> 마스터 키가 저장된 파일이 외부에 노출된다면 데이터 암호화 무용지물
TDE 플러그인은 MySQL 서버가 시작되는 단계에서도 가장 빨리 초기화돼야 함
-> 설정 파일(`my.cnf`)에서 `early-plugin-load` 시스템 변수에 `keyring_file` 플러그인 위한 라이브러리(`keyring_file.so`) 명시
`keyring_file_data` 설정의 경로는 오직 하나의 MySQL 서버만 참조해야 함
하나의 리눅스 서버에 MySQL 서버가 2개 이상 실행 중 -> 각 MySQL 서버가 서로 다른 키링 파일을 사용하도록 설정해야 함
서버의 설정 파일 준비 -> 재시작하면 자동으로 `keyring_file` 플러그인이 초기화
`keyring_file` 플러그인 초기화 여부: `SHOW PLUGINS` 명령으로 확인
SHOW PLUGINS;
`keyring_file` 플러그인 초기화 -> MySQL 서버는 플러그인의 초기화 + `keyring_file_data` 시스템 변수의 경로에 빈 파일 생성
플러그인만 초기화된 상태, 마스터 키 사용 X -> 실제 키링 파일의 내용은 비어 있음
데이터 암호화 기능을 사용하는 테이블을 생성 or 마스터 로테이션 실행 -> 키링 파일의 마스터 키 초기화
3. 테이블 암호화
키링 플러그인: 마스터 키 생성, 관리하는 부분까지만 담당
-> 어떤 키링 플러그인 사용하든 관계 X 암호화된 테이블 생성하고 활용하는 방법은 모두 동일
1) 테이블 생성
TDE 이용하는 테이블 생성
CREATE TABLE tab_encrypted (
id INT,
data VARCHAR(100),
PRIMARY KEY(id)
) ENCRYPTION='Y';
INSERT INTO tab_encrypted VALUES (1, 'test_data');
SELECT * FROM tab_encrypted;
`ENCRYPTION='Y' 옵션: 테이블의 데이터가 디스크에 기록될 때 데이터가 자동으로 암호화돼 저장
다시 디스크에서 메모리로 읽어올 때 복호화
암호화된 테이블만 검색 -> `information_schema`의 TABLES 뷰 이용
SELECT table_schema, table_name, create_options
FROM infromation_schema.tables
WHERE table_name='tab_encrypted';
모든 테이블에 대해 암호화 적용하고자 한다면 `default_table_encryption` 시스템 변수를 ON으로 설정
-> ENCRYPTION 옵션을 별도로 설정하지 않아도 암호화된 테이블로 생성됨
2) 응용 프로그램 암호화와의 비교
직접 암호화해서 저장하는 경우: 저장되는 칼럼의 값이 이미 암호화된 것인지 여부 MySQL 서버 인지 X
-> 응용 프로그램에서 인덱스 생성하더라도 인덱스의 기능 100% 활용 X
app_user 테이블은 암호화 X enc_birth_year 컬럼은 응용 프로그램에서 이미 암호화해서 app_user 테이블에 저장
CREATE TABLE app_user (
id BIGINT,
enc_birth_year VARCHAR(50),
...
PRIMARY KEY (id),
INDEX ix_birthyear (birth_year)
);
SELECT * FROM app_user WHERE enc_birth_year=#{encryptedYear};
SELECT * FROM app_user
WHERE enc_birth_year BETWEEN #{encryptedMinYear} AND #{encryptedMaxYear};
SELECT * FROM app_user ORDER BY enc_birth_year LIMIT 10;
첫 번째 쿼리: 동일 값만 검색하는 쿼리 -> `enc_birth_year=#{encryptedYear}` 조건으로 검색할 수 있음
But, 출생 연도 범위의 사용자 검색 or 출생 연도 기준으로 정렬해서 상위 10개만 가져오는 쿼리 등 사용 X
MySQL 서버는 이미 암호화된 값을 기준으로 정렬 -> 암호화되기 전의 값을 기준으로 정렬 X
응용 프로그램에서 직접 암호화 X 암호화 기능(TDE) 사용 -> 인덱스 작업 모두 처리 후 최종 디스크에 데이터 페이지를 저장할 때만 암호화
=> 응용 프로그램의 암호화 vs. MySQL 서버의 암호화 기능 선택 -> MySQL 서버의 암호화 기능 선택
- 응용 프로그램 암호화: MySQL 서버에 로그인할 수 있어도 평문의 내용 확인할 수 X
- 서비스의 요건과 성능 고려해서 선택해야 함
- MySQL 서버의 TDE 기능으로 암호화: 실행 중인 MySQL 서버에 로그인만 할 수 있다면 모든 데이터를 평문으로 확인할 수 있음
3) 테이블스페이스 이동
테이블을 다른 서버로 복사해야 하는 경우 or 특정 테이블의 데이터 파일만 백업했다가 복구하는 경우
-> 테이블스페이스 이동(Export & Import) 기능이 레코드를 덤프했다가 복구하는 방식보다 훨씬 효율적이고 빠름
TDE가 적용되어 암호화된 테이블의 경우 원본 MySQL 서버 != 목적지 MySQL 서버의 암호화 키(마스터 키) -> 하나 더 신경 써야 함
`FLUSH TABLES` 명령 -> 테이블스페이스 익스포트(Export)
FLUSH TABLES source_table FOR EXPORT;
암호화되지 X 테이블의 테이블스페이스 복사 과정
`source_table`의 저장 X 변경 사항 모두 디스크로 기록, 더이상 `source_table`에 접근할 수 없게 잠금
`source_table`의 구조를 `source_table.cfg` 파일로 기록
-> `source_table.ibd` 파일, `source_table.cfg` 파일을 목적지 서버로 복사
=> 복사 모두 완료: `UNLOCK TABLES` 명령 실행 -> `source_table`을 사용할 수 있게 함
TDE로 암호화된 테이블에 대해 `FLUSH TABLES source_table FOR EXPORT` 명령 실행
-> MySQL 서버는 임시로 사용할 마스터 키 발급해 `source_table.cfp`라는 파일로 기록
암호화된 테이블의 스페이스 키를 마스터 키로 복호화, 임시로 발급한 마스터 키로 다시 암호화해서 데이터 파일의 헤더 부분에 저장
=> 테이블스페이스 이동 기능을 사용할 때는 반드시 데이터 파일, 임시 마스터 키가 저장된 `*.cfp` 파일을 함께 복사해야 함
`*.cfg` 파일: 단순히 테이블의 구조만 가지고 있음 -> 파일이 없어져도 경고만 발생, 테이블스페이스 복구 O
`*.cfp` 파일: 없어지면 복구 불가능
4. 언두 로그 및 리두 로그 암호화
테이블의 암호화를 적용하더라도 디스크로 저장되는 데이터만 암호화
MySQL 서버의 메모리에 존재하는 데이터는 복호화된 평문으로만 관리
평문 데이터가 테이블의 데이터 파일 이외의 디스크 파일로 기록되는 경우에는 여전히 평문으로 저장
-> 테이블 암호화를 적용해도 리두 로그, 언두 로그, 복제를 위한 바이너리 로그에 평문으로 저장
8.0.16 버전부터: `innodb_undo_log_encrypt` 시스템 변수, `innodb_redo_log_encrypt` 시스템 변수 이용
-> InnoDB 스토리지 엔진의 리두/언두 로그를 암호화된 상태로 저장할 수 있게 개선
테이블의 암호화는 테이블 하나에 대해 암호화 적용 -> 해당 테이블의 모든 데이터가 암호화 돼야 함
But, 리두 로그/언두 로그 그렇게 적용할 수 X
== 실행 중인 MySQL 서버에서 언두 로그/리두 로그를 활성화해도 모든 리두 로그/언두 로그의 데이터를 해당 시점에 한 번에 암호화해서 다시 저장할 수 X
-> 서버는 리두/언두 로그를 평문으로 저장하다가 암호화 활성화 -> 그때부터 생성되는 로그만 암호화해서 저장
/ 리두/언두 로그가 암호화되는 상태에서 암호화 비활성화 -> 그때부터 저장되는 로그만 평문으로 저장
InnoDB 리두 로그가 암호화됐는지 확인
SHOW GLOBAL VARIABLES LIKE 'innodb_redo_log_encrypt';
INSERT INTO enc VALUES (2, 'Real-MySQL');
SET GLOBAL innodb_redo_log_encrypt=ON;
INSERT INTO enc VALUES (2, 'Real-MongoDB');
`INSERT`된 레코드 문자열이 InnoDB의 리두 로그에 보이는지만 확인
`grep` 명령 이용한 단순한 검색 결과
- 암호화되기 전에 `INSERT`한 Real-MYSQL 문자열 검색 O
- 암호화 이후 `INSERT`된 Real-MongoDB 문자열은 검색 X
5. 바이너리 로그 암호화
1) 바이너리 로그 암호화 키 관리
바이너리 로그, 릴레이 로그 파일 데이터의 암호화 -> 2단계 암호화 키 관리 방식 사용

- 데이터: 파일 키(File Key)로 암호화 -> 디스크에 저장
- 파일 키: 바이너리 로그 암호화 키로 암호화 -> 각 파일의 헤더에 저장
- 바이너리 로그와 로그 파일 단위로 자동 생성 -> 해당 로그 파일의 데이터 암호화에만 사용
=> 바이너리 로그 암호화 키 == 테이블 암호화의 마스터 키
2) 바이너리 로그 암호화 키 변경
변경(로테이션)
ALTER INSTANCE ROTATE BINLOG MASTER KEY;
- 증가된 시퀀스 번호와 함께 새로운 바이너리 로그 암호화 키 발급 후 키링 파일에 저장
- 바이너리 로그 파일, 릴레이 로그 파일(새로운 로그 파일로 로테이션)
- 새로 생성되는 바이너리 로그, 릴레이 로그 파일의 암호화 -> 파일 키 생성,
파일 키: 바이너리 로그 파일 키(마스터 키)로 암호화해서 각 로그 파일에 저장 - 기존 바이너리 로그, 릴레이 로그 파일의 파일 키 읽어서 새로운 바이너리 로그 파일 키로 암호화해서 다시 저장
(암호화되지 X 로그 파일 무시) - 모든 바이너리 로그, 릴레이 로그 파일이 새로운 바이너리 로그 암호화 키로 다시 암호화
-> 기존 바이너리로 그 암호화 키를 키링 파일에서 제거
4번이 시간이 걸리는 작업
-> 키링 파일에서 바이너리 로그 암호화 키: 내부적으로 버전(시퀀스 번호) 관리 이뤄짐
ex. 바이너리 로그, 릴레이 로그 ↑ MySQL 서버에서 `ALTER INSTANCE ROTATE BINLOG MASTER KEY` 명령 연속 2번
-> 키링 파일에는 순차적인 시퀀스 번호 가지는 3개의 바이너리 로그 암호화 키 존재
모든 각 로그 파일은 최근 순서대로 파일 키 다시 암호화해서 저장하는 작업 수행
모든 각 로그 파일의 파일 키가 새로운 바이너리 로그 암호화 키로 암호화돼 저장
-> 기존 바이너리 로그 암호화 키 필요 X => 키링 파일에서 제거
바이너리 로그 파일이 암호화돼 있는지 여부 확인
SHOW BINARY LOGS:
3) mysqlbinlog 도구 활용
트랜잭션의 내용 추적 or 백업 복구 -> 암호화된 바이너리 로그를 평문으로 복호화할 일 ↑
But, 한 번 바이너리 로그 파일 암호화 -> 바이너리 로그 암호화 키 필요
=> MySQL 서버만 가지고 있어 복호화 불가능
`mysqlbinlog` 도구 이용 -> 암호화된 바이너리 로그 파일의 내용을 SQL로 보면 직접 열어 볼 수 없다는 에러 메시지
=> 다른 서버로 복사하거나 바이너리 로그 파일 백업 소용 X
바이너리 로그 파일의 내용을 볼 수 있는 방법: MySQL 서버 ~> 가져오는 방법이 유일
== (가정) MySQL 서버가 mysql-bin.000011 로그 파일을 가지고 있음
-> mysqlbinlog 도구가 MySQL 서버에 접속해서 바이너리 로그를 가져오는 방법
mysqlbinlog 도구가 직접 파일을 읽는 것 X => 명령을 실행할 때 `--read-from-remote-server` 파라미터와 함께 서버 접속 정보 입력
출처
GitHub - wikibook/realmysql80: 《Real MySQL 8.0》 예제 파일
《Real MySQL 8.0》 예제 파일. Contribute to wikibook/realmysql80 development by creating an account on GitHub.
github.com
'💻 > 데이터베이스' 카테고리의 다른 글
Pessimistic Lock(비관적 락) vs. Optimistic Lock(낙관적 락) (0) | 2025.02.23 |
---|---|
Trigger & Procedure (0) | 2025.02.23 |
[Real MySQL 8.0 1] 0.6. 데이터 압축 (0) | 2025.01.27 |
[Real MySQL 8.0 1] 0.5 트랜잭션과 잠금 (0) | 2025.01.24 |
[Real MySQL 8.0 1] 03. 사용자 및 권한 (0) | 2025.01.23 |
1. MySQL 서버의 데이터 암호화
5.7 버전부터: 데이터 암호화 기능 지원, 처음에는 데이터 파일(테이블스페이스)에 대해서만 암호화 기능 제공
8.0 버전 이후: 데이터 파일, 리두 로그, 언두 로그, 복제를 위한 바이너리 로그 등 모두 암호화 기능 제공
데이터베이스 서버와 디스크 사이의 데이터를 읽고 쓰는 지점에서 암호화/복호화
-> 디스크 입출력 ㅣㅇ외의 부분에서는 압호화 처리 필요 X
== MySQL 서버(InnoDB 스토리지 엔진)의 I/O 레이어에서만 데이터 암호화/복호화 과정 실행

사용자의 쿼리를 처리하는 과정에서 테이블의 데이터가 암호화돼 있는지 여부 식별할 필요 X
암호화 O 테이블, 암호화 X 테이블 => 동일한 처리 과정: 데이터 암호화 기능이 활성화 O -> MySQL 내부와 사용자 입장에서 차이 X
=> TDE(Transparent Data Encryption) (주로 사용), Data at Rest Encryption
Data at Rest: 메모리(In-Process)나 네트워크 전송(In-Transit) 단계 X 디스크에 저장(At Rest)된 단계에서만 암호화
1) 2단계 키 관리
TDE에서 암호화 키 -> 키링(KeyRing) 플러그인에 의해 관리
keyring_file
: File-Based 플러그인 (커뮤니티 에디션)keyring_encrypted_file
: Keyring 플러그인keyring_okv
: KMIP 플러그인keyring_aws
: Amazaon Web Services Keyring 플러그인
=> 마스크 키를 관리하는 방법만 다르고 서버 내부적으로 작동하는 방식은 동일
마스터 키(master key) / 테이블스페이스 키(tablespace key) == 프라이빗 키(private key)
서버는 HashiCrop Vault 같은 외부 키 관리 솔루션(KMS, Key Management Service)
or 디스크 파일(keyring_file
or keyring_encrypted_file
플러그인 사용)에서 마스터 키 가져옴
암호화된 테이블이 생성될 때마다 해당 테이블을 위한 임의의 테이블스페이스 키 발급
MySQL 서버는 마스터 키 -> 테이블스페이스 키 암호화 -> 각 테이블의 데이터 파일 헤더에 저장
=> 테이블스페이스 키: 테이블 삭제되지 않는 이상 절대 변경 X
/ 마스터 키는 외부의 파일 이용 -> 노출될 가능성 ↑ => 주기적으로 MySQL 서버의 마스터 키 변경
ALTER INSTANCE ROTATE INNODB MASTER KEY;
기존의 마스터 키 -> 각 테이블의 테이블스페이스 키를 복호화, 새로운 마스터 키로 다시 암호화
마스터 키가 변경되는 동안 MySQL 서버의 테이블스페이스 키 자체와 데이터 파일의 데이터 변경 X
2단계 암호화 방식 사용하는 이유: 암호화 키 변경으로 인한 과도한 시스템 부하를 피하기 위해
테이블스페이스 키 변경 -> MySQL 서버는 데이터 파일의 모든 데이터 다시 복화했다가 암호화
=> 키 변경할 때마다 작업량 ↑, 사용자 쿼리를 처리하는 데도 영향 O
TDE에서 지원하는 암호화 알고리즘: AES 256비트
테이블스페이스 키: AES-256 ECB(Electronic CodeBook) 알고리즘 -> 암호화
실제 데이터 파일: AES-256 CBC(Cipher Block Chaining) 알고리즘 -> 암호화
2) 암호화와 성능
TDE(Transparent Data Encryption) 방식
-> 디스크로부터 한 번 읽은 데이터 페이지는 복호화되어 InnoDB의 버퍼 풀에 저재
-> 데이터 페이지가 한 번 메모리에 적재되면 암호화 X 테이블과 동일한 성능
But, 쿼리가 InnoDB 버퍼 풀에 존재 X 데이터 페이지를 읽어야 하는 경우에는 복호화 과정 거침 => 복호화 시간 동안 쿼리 처리 지연
암호화된 테이블 변경 -> 다시 디스크로 동기화될 때 암호화돼야 함 => 디스크 저장할 때 추가로 시간 더 걸림
But, 데이터 페이지 저장: 사용자의 쿼리를 처리하는 스레드 X MySQL 서버의 백그라운드 스레드가 수행 -> 실제 사용자 쿼리 지연 X
SELECT
, UPDATE
, DELETE
명령: 변경하고자 하는 레코드를 InnoDB 버퍼 풀로 읽어와야 함
=> 새롭게 디스크에서 읽어야 하는 데이터 페이지의 개수에 따라서 복호화 지연 발생
AES(Advanced Encryption Standard) 암호화 알고리즘
암호화하고자 하는 평문의 길이 ↓ -> 암호화 키의 크기에 따라 암호화된 결과의 용량이 더 커질 수 있지만, 이미 데이터 페이지 > 암호화 키
-> 암호화 결과가 평문의 결과와 동일한 크기의 암호문 반환
=> TDE 적용해도 데이터 파일의 크기 == 암호화 X 테이블 크기
== 암호화한다고 해서 InnoDB 버퍼 풀의 효율이 달라지거나 메모리 사용 효율이 떨어지는 현상 X
같은 테이블에 대해 암호화와 압축 동시 적용 -> 압축 먼저 실행하고 암호화 적용
- 암호화된 결과문: 아주 랜덤한 바이트의 배열을 가짐 -> 압축률 ↓
-> 최대한 압축 효율 ↑ 위해 사용자의 데이터를 그대로 압축해서 용량을 최소화한 후 암호화를 적용 - 암호화된 테이블의 데이터 페이지는 복호화된 상태로 InnoDB 버퍼 풀에 저장, 압축된 데이터 페이지는 압축 or 압축 해제의 모든 상태로 InnoDB 버퍼 풀에 존재할 수 있음
-> 암호화가 먼저 실행되고 압축 적용: InnoDB 버퍼 풀에 존재하는 데이터 페이지에 대해서도 매번 암복호화 작업을 수행해야 함
암호화된 테이블의 경우 읽기는 3~5배 정도 느리고, 쓰기는 5~6배 느림
(밀리초 단위이므로 체감되지 않을 수 있음)
3) 암호화와 복제
MySQL 서버의 복제에서 레플리카 서버: 소스 서버의 모든 사용자 데이터를 동기화
-> 실제 데이터 파일도 동일할 것이라고 생각할 수 있음
But, TDE를 이용한 암호화 사용 -> 마스터 키, 테이블스페이스 키 동일 X
서버에서 기본적으로 모든 노드는 각자의 마스터 키를 할당해야 함
데이터베이스 서버의 로컬 디렉터리에 마스터 키를 관리하는 경우: 소스 서버 != 레플리카 서버 키
/ 원격으로 관리하는 경우: 소스 서버 != 레플리카 서버 마스터 키
-> 마스터 키: 레플리카로 복제 X -> 테이블스페이스 키: 레플리카로 복제 X
=> 소스 서버, 레플리카 서버: 각자의 마스터 키, 테이블스페이스 키 관리
-> 복제 멤버들의 데이터 파일은 암호화되기 전의 값이 동일해도 실제 암호화된 데이터가 저장된 데이터 파일 내용은 완전히 달라짐
복제 소스 서버의 마스터 키 변경: ALTER INSTANCE ROTATE INNODB MASTER KEY
명령 실행
명령은 레플리카 서버로 복제 But, 실제 소스 서버의 마스터 키 자체가 레플리카 서버로 전달되는 것 X
-> 마스터 키 로테이션 실행: 소스 서버, 레플리카 서버가 각각 서로 다른 마스터 키를 새로 발급 받음
MySQL 서버의 백업에서 TDE의 키링(Key Ring) 파일을 백업하지 X 경우 O
-> 키링 파일 찾지 못하면 데이터 파일 복구 X
키링 파일을 데이터 백업과 별도로 백업 -> 마스터 키 로테이션 명령으로 TDE의 마스터 키가 언제 변경됐는지까지 기억해야 함
보안 -> 키링 파일을 데이터 파일과 별도로 보관하는 것 (권장)
마스터 키도 계속 변경될 수 있기 때문에 백업마다 키링 파일의 백업 고려 필요
2. Keyring_file 플러그인 설치
TDE의 암호화 키 관리: 플러그인 방식을 제공
keyring_file
플러그인
- MySQL 커뮤니티 에디션
- 테이블스페이스 키를 암호화하기 위한 마스터 키 -> 디스크의 파일로 관리 (=> 보안 요건 충족 X)
- 마스터 키는 평문으로 디스크에 저장
=> 마스터 키가 저장된 파일이 외부에 노출된다면 데이터 암호화 무용지물
TDE 플러그인은 MySQL 서버가 시작되는 단계에서도 가장 빨리 초기화돼야 함
-> 설정 파일(my.cnf
)에서 early-plugin-load
시스템 변수에 keyring_file
플러그인 위한 라이브러리(keyring_file.so
) 명시
keyring_file_data
설정의 경로는 오직 하나의 MySQL 서버만 참조해야 함
하나의 리눅스 서버에 MySQL 서버가 2개 이상 실행 중 -> 각 MySQL 서버가 서로 다른 키링 파일을 사용하도록 설정해야 함
서버의 설정 파일 준비 -> 재시작하면 자동으로 keyring_file
플러그인이 초기화
keyring_file
플러그인 초기화 여부: SHOW PLUGINS
명령으로 확인
SHOW PLUGINS;
keyring_file
플러그인 초기화 -> MySQL 서버는 플러그인의 초기화 + keyring_file_data
시스템 변수의 경로에 빈 파일 생성
플러그인만 초기화된 상태, 마스터 키 사용 X -> 실제 키링 파일의 내용은 비어 있음
데이터 암호화 기능을 사용하는 테이블을 생성 or 마스터 로테이션 실행 -> 키링 파일의 마스터 키 초기화
3. 테이블 암호화
키링 플러그인: 마스터 키 생성, 관리하는 부분까지만 담당
-> 어떤 키링 플러그인 사용하든 관계 X 암호화된 테이블 생성하고 활용하는 방법은 모두 동일
1) 테이블 생성
TDE 이용하는 테이블 생성
CREATE TABLE tab_encrypted (
id INT,
data VARCHAR(100),
PRIMARY KEY(id)
) ENCRYPTION='Y';
INSERT INTO tab_encrypted VALUES (1, 'test_data');
SELECT * FROM tab_encrypted;
`ENCRYPTION='Y' 옵션: 테이블의 데이터가 디스크에 기록될 때 데이터가 자동으로 암호화돼 저장
다시 디스크에서 메모리로 읽어올 때 복호화
암호화된 테이블만 검색 -> information_schema
의 TABLES 뷰 이용
SELECT table_schema, table_name, create_options
FROM infromation_schema.tables
WHERE table_name='tab_encrypted';
모든 테이블에 대해 암호화 적용하고자 한다면 default_table_encryption
시스템 변수를 ON으로 설정
-> ENCRYPTION 옵션을 별도로 설정하지 않아도 암호화된 테이블로 생성됨
2) 응용 프로그램 암호화와의 비교
직접 암호화해서 저장하는 경우: 저장되는 칼럼의 값이 이미 암호화된 것인지 여부 MySQL 서버 인지 X
-> 응용 프로그램에서 인덱스 생성하더라도 인덱스의 기능 100% 활용 X
app_user 테이블은 암호화 X enc_birth_year 컬럼은 응용 프로그램에서 이미 암호화해서 app_user 테이블에 저장
CREATE TABLE app_user (
id BIGINT,
enc_birth_year VARCHAR(50),
...
PRIMARY KEY (id),
INDEX ix_birthyear (birth_year)
);
SELECT * FROM app_user WHERE enc_birth_year=#{encryptedYear};
SELECT * FROM app_user
WHERE enc_birth_year BETWEEN #{encryptedMinYear} AND #{encryptedMaxYear};
SELECT * FROM app_user ORDER BY enc_birth_year LIMIT 10;
첫 번째 쿼리: 동일 값만 검색하는 쿼리 ->enc_birth_year=#{encryptedYear}
조건으로 검색할 수 있음
But, 출생 연도 범위의 사용자 검색 or 출생 연도 기준으로 정렬해서 상위 10개만 가져오는 쿼리 등 사용 X
MySQL 서버는 이미 암호화된 값을 기준으로 정렬 -> 암호화되기 전의 값을 기준으로 정렬 X
응용 프로그램에서 직접 암호화 X 암호화 기능(TDE) 사용 -> 인덱스 작업 모두 처리 후 최종 디스크에 데이터 페이지를 저장할 때만 암호화
=> 응용 프로그램의 암호화 vs. MySQL 서버의 암호화 기능 선택 -> MySQL 서버의 암호화 기능 선택
- 응용 프로그램 암호화: MySQL 서버에 로그인할 수 있어도 평문의 내용 확인할 수 X
- 서비스의 요건과 성능 고려해서 선택해야 함
- MySQL 서버의 TDE 기능으로 암호화: 실행 중인 MySQL 서버에 로그인만 할 수 있다면 모든 데이터를 평문으로 확인할 수 있음
3) 테이블스페이스 이동
테이블을 다른 서버로 복사해야 하는 경우 or 특정 테이블의 데이터 파일만 백업했다가 복구하는 경우
-> 테이블스페이스 이동(Export & Import) 기능이 레코드를 덤프했다가 복구하는 방식보다 훨씬 효율적이고 빠름
TDE가 적용되어 암호화된 테이블의 경우 원본 MySQL 서버 != 목적지 MySQL 서버의 암호화 키(마스터 키) -> 하나 더 신경 써야 함
FLUSH TABLES
명령 -> 테이블스페이스 익스포트(Export)
FLUSH TABLES source_table FOR EXPORT;
암호화되지 X 테이블의 테이블스페이스 복사 과정
source_table
의 저장 X 변경 사항 모두 디스크로 기록, 더이상 source_table
에 접근할 수 없게 잠금
source_table
의 구조를 source_table.cfg
파일로 기록
-> source_table.ibd
파일, source_table.cfg
파일을 목적지 서버로 복사
=> 복사 모두 완료: UNLOCK TABLES
명령 실행 -> source_table
을 사용할 수 있게 함
TDE로 암호화된 테이블에 대해 FLUSH TABLES source_table FOR EXPORT
명령 실행
-> MySQL 서버는 임시로 사용할 마스터 키 발급해 source_table.cfp
라는 파일로 기록
암호화된 테이블의 스페이스 키를 마스터 키로 복호화, 임시로 발급한 마스터 키로 다시 암호화해서 데이터 파일의 헤더 부분에 저장
=> 테이블스페이스 이동 기능을 사용할 때는 반드시 데이터 파일, 임시 마스터 키가 저장된 *.cfp
파일을 함께 복사해야 함
*.cfg
파일: 단순히 테이블의 구조만 가지고 있음 -> 파일이 없어져도 경고만 발생, 테이블스페이스 복구 O
*.cfp
파일: 없어지면 복구 불가능
4. 언두 로그 및 리두 로그 암호화
테이블의 암호화를 적용하더라도 디스크로 저장되는 데이터만 암호화
MySQL 서버의 메모리에 존재하는 데이터는 복호화된 평문으로만 관리
평문 데이터가 테이블의 데이터 파일 이외의 디스크 파일로 기록되는 경우에는 여전히 평문으로 저장
-> 테이블 암호화를 적용해도 리두 로그, 언두 로그, 복제를 위한 바이너리 로그에 평문으로 저장
8.0.16 버전부터: innodb_undo_log_encrypt
시스템 변수, innodb_redo_log_encrypt
시스템 변수 이용
-> InnoDB 스토리지 엔진의 리두/언두 로그를 암호화된 상태로 저장할 수 있게 개선
테이블의 암호화는 테이블 하나에 대해 암호화 적용 -> 해당 테이블의 모든 데이터가 암호화 돼야 함
But, 리두 로그/언두 로그 그렇게 적용할 수 X
== 실행 중인 MySQL 서버에서 언두 로그/리두 로그를 활성화해도 모든 리두 로그/언두 로그의 데이터를 해당 시점에 한 번에 암호화해서 다시 저장할 수 X
-> 서버는 리두/언두 로그를 평문으로 저장하다가 암호화 활성화 -> 그때부터 생성되는 로그만 암호화해서 저장
/ 리두/언두 로그가 암호화되는 상태에서 암호화 비활성화 -> 그때부터 저장되는 로그만 평문으로 저장
InnoDB 리두 로그가 암호화됐는지 확인
SHOW GLOBAL VARIABLES LIKE 'innodb_redo_log_encrypt';
INSERT INTO enc VALUES (2, 'Real-MySQL');
SET GLOBAL innodb_redo_log_encrypt=ON;
INSERT INTO enc VALUES (2, 'Real-MongoDB');
INSERT
된 레코드 문자열이 InnoDB의 리두 로그에 보이는지만 확인
grep
명령 이용한 단순한 검색 결과
- 암호화되기 전에
INSERT
한 Real-MYSQL 문자열 검색 O - 암호화 이후
INSERT
된 Real-MongoDB 문자열은 검색 X
5. 바이너리 로그 암호화
1) 바이너리 로그 암호화 키 관리
바이너리 로그, 릴레이 로그 파일 데이터의 암호화 -> 2단계 암호화 키 관리 방식 사용

- 데이터: 파일 키(File Key)로 암호화 -> 디스크에 저장
- 파일 키: 바이너리 로그 암호화 키로 암호화 -> 각 파일의 헤더에 저장
- 바이너리 로그와 로그 파일 단위로 자동 생성 -> 해당 로그 파일의 데이터 암호화에만 사용
=> 바이너리 로그 암호화 키 == 테이블 암호화의 마스터 키
2) 바이너리 로그 암호화 키 변경
변경(로테이션)
ALTER INSTANCE ROTATE BINLOG MASTER KEY;
- 증가된 시퀀스 번호와 함께 새로운 바이너리 로그 암호화 키 발급 후 키링 파일에 저장
- 바이너리 로그 파일, 릴레이 로그 파일(새로운 로그 파일로 로테이션)
- 새로 생성되는 바이너리 로그, 릴레이 로그 파일의 암호화 -> 파일 키 생성,
파일 키: 바이너리 로그 파일 키(마스터 키)로 암호화해서 각 로그 파일에 저장 - 기존 바이너리 로그, 릴레이 로그 파일의 파일 키 읽어서 새로운 바이너리 로그 파일 키로 암호화해서 다시 저장
(암호화되지 X 로그 파일 무시) - 모든 바이너리 로그, 릴레이 로그 파일이 새로운 바이너리 로그 암호화 키로 다시 암호화
-> 기존 바이너리로 그 암호화 키를 키링 파일에서 제거
4번이 시간이 걸리는 작업
-> 키링 파일에서 바이너리 로그 암호화 키: 내부적으로 버전(시퀀스 번호) 관리 이뤄짐
ex. 바이너리 로그, 릴레이 로그 ↑ MySQL 서버에서ALTER INSTANCE ROTATE BINLOG MASTER KEY
명령 연속 2번
-> 키링 파일에는 순차적인 시퀀스 번호 가지는 3개의 바이너리 로그 암호화 키 존재
모든 각 로그 파일은 최근 순서대로 파일 키 다시 암호화해서 저장하는 작업 수행
모든 각 로그 파일의 파일 키가 새로운 바이너리 로그 암호화 키로 암호화돼 저장
-> 기존 바이너리 로그 암호화 키 필요 X => 키링 파일에서 제거
바이너리 로그 파일이 암호화돼 있는지 여부 확인
SHOW BINARY LOGS:
3) mysqlbinlog 도구 활용
트랜잭션의 내용 추적 or 백업 복구 -> 암호화된 바이너리 로그를 평문으로 복호화할 일 ↑
But, 한 번 바이너리 로그 파일 암호화 -> 바이너리 로그 암호화 키 필요
=> MySQL 서버만 가지고 있어 복호화 불가능
mysqlbinlog
도구 이용 -> 암호화된 바이너리 로그 파일의 내용을 SQL로 보면 직접 열어 볼 수 없다는 에러 메시지
=> 다른 서버로 복사하거나 바이너리 로그 파일 백업 소용 X
바이너리 로그 파일의 내용을 볼 수 있는 방법: MySQL 서버 ~> 가져오는 방법이 유일
== (가정) MySQL 서버가 mysql-bin.000011 로그 파일을 가지고 있음
-> mysqlbinlog 도구가 MySQL 서버에 접속해서 바이너리 로그를 가져오는 방법
mysqlbinlog 도구가 직접 파일을 읽는 것 X => 명령을 실행할 때 --read-from-remote-server
파라미터와 함께 서버 접속 정보 입력
출처
GitHub - wikibook/realmysql80: 《Real MySQL 8.0》 예제 파일
《Real MySQL 8.0》 예제 파일. Contribute to wikibook/realmysql80 development by creating an account on GitHub.
github.com
'💻 > 데이터베이스' 카테고리의 다른 글
Pessimistic Lock(비관적 락) vs. Optimistic Lock(낙관적 락) (0) | 2025.02.23 |
---|---|
Trigger & Procedure (0) | 2025.02.23 |
[Real MySQL 8.0 1] 0.6. 데이터 압축 (0) | 2025.01.27 |
[Real MySQL 8.0 1] 0.5 트랜잭션과 잠금 (0) | 2025.01.24 |
[Real MySQL 8.0 1] 03. 사용자 및 권한 (0) | 2025.01.23 |