Shamir Secret Sharing으로 AWS 루트 키 5명 분산 3명 복원하기

목차

Shamir Secret Sharing은 비밀 하나를 N조각으로 쪼개는 알고리즘이다

실제로, Shamir Secret Sharing(SSS)은 1979년 Adi Shamir가 발표한 (k, n) 임계값 기반 비밀 분산 알고리즘이다. 비밀 하나를 n개 조각(share)으로 쪼개되, k개만 모이면 원본을 복원할 수 있다. k-1개 이하로는 어떤 정보도 새지 않는다는 게 수학적으로 증명된다.

AWS 환경에서 이게 왜 필요한가. IAM과 KMS가 있어도 루트 계정의 access key, MFA 백업 코드, KMS의 외부 키 자료(EKM) 같은 최상위 비밀은 어딘가에 평문으로 보관해야 한다. 보안 감사 때 "이 비밀은 누가 들고 있냐"는 질문이 항상 나온다. 한 사람이 들고 있으면 단일 실패점이자 사회공학 공격의 표적이 된다.

한편, 이 글은 AWS 루트 access key를 5명에게 분산하고 3명이 모이면 복원하도록 만든 과정의 기록이다. 처음에 쓴 ssss CLI는 운영이 무너졌다. SLIP-39 기반으로 갈아탄 뒤에야 분기 시뮬레이션을 통과할 수 있었다.

처음 시도: ssss-split CLI로 hex 분산했더니 다들 어이없어 했다

물론, 가장 유명한 SSS 구현은 ssss다. apt 패키지로 깔리고 명령 한 줄로 끝난다.

# Ubuntu/Debian
sudo apt install ssss

# 5명 중 3명이면 복원 가능한 분산
echo -n "AKIAIOSFODNN7EXAMPLE" | ssss-split -t 3 -n 5

그러나, 결과는 이런 식이다.

1-2c8a7f4d9e3b1a05f6c8e7d2b9a4f1e3
2-7e3b9a05c8d7f4e21a6b3c9d8e7f5a02
3-...

1-, 2- 같은 인덱스가 붙은 hex 문자열 5개다. 받는 사람들한테 보냈더니 첫 반응이 "이거 그냥 텍스트로 갖고 있으면 돼?"였다. 이게 문제의 본질이었다.

종이 백업이 사실상 불가능

hex 32자를 종이에 옮겨 적다 한 글자만 틀려도 복원이 깨진다. 무결성 검증 메커니즘이 없어서 어느 share가 잘못됐는지도 모른다. 3명이 share를 모았는데 원본이 안 나오면 한 명씩 빼면서 재시도해야 한다. 사실상 모든 조합을 시도해야 한다는 뜻이다.

QR 코드로 만들려다 실패

또한, 종이 백업이 어려우니 QR 코드로 인쇄해서 금고에 넣자는 아이디어를 시도했다. ssss share 길이가 짧아 QR 자체는 만들어졌다. 문제는 다른 데서 터졌다. 5명 중 한 명이 PDF 파일을 잃어버렸다고 신고했다. 그때 깨달은 게 있다. "잃어버려도 3명만 있으면 복원 가능" → "맞다, 그런데 그 share가 진짜 share인지 검증할 방법이 없다."

ssss의 한계 정리

  • 인코딩: hex만 지원. 단어/숫자/체크섬 없음.
  • 무결성: share에 체크섬이 없어 변조나 오타 감지 불가.
  • 메타데이터: threshold(k)가 share에 안 들어감. 별도로 기억해야 함.
  • 표준: ssss는 사실상 단일 도구. 다른 구현과 호환 안 됨.

SLIP-39로 갈아탄 이유: 단어와 체크섬

그런데, 여기서 살펴본 게 SLIP-39다. Trezor 하드웨어 지갑 회사 SatoshiLabs가 2018년에 제안한 표준으로, BIP-39(시드 단어 12/24개)를 확장해서 Shamir 분산까지 지원하도록 만든 사양이다(SLIP-39 명세).

핵심은 share를 hex가 아니라 1024개 영어 단어 풀에서 뽑은 20개(또는 33개) 단어 시퀀스로 표현한다는 점이다. 비트코인 시드와 같은 방식이다. 각 share는 자체 체크섬을 포함하므로 한 단어를 틀리면 즉시 감지된다.

항목 ssss SLIP-39
인코딩 hex 32자 영어 단어 20~33개
체크섬 없음 RS1024 (오류 감지)
threshold 메타데이터 별도 보관 share에 인코딩
종이 백업 어려움 단어라 가능
그룹화(2단계) 불가 가능 (그룹별 threshold)
표준화 수준 단일 구현 Trezor·여러 지갑 채택

그런데, 체크섬과 단어 표현, 이 두 가지가 운영의 차이를 만들었다.

5명 분산 3명 복원, 실제 구현

그래서, Python에서 SLIP-39를 다루는 라이브러리는 shamir-mnemonic이다. Trezor 공식 구현(GitHub)이고 PyPI에 올라가 있다. 작성 시점(2026-05 기준)으로는 0.3.0 버전을 쓰고 있다.

pip install shamir-mnemonic

반면, 분산 코드는 단순하다.

from shamir_mnemonic import generate_mnemonics

# 분산할 비밀 (실제로는 stdin·KMS 등으로 더 안전하게 입력)
secret_bytes = b"AKIAIOSFODNN7EXAMPLE+secretAccessKey32bytes....."

# (3, 5) 임계값: 5개 share 생성, 3개 모이면 복원
groups = [(3, 5)]
mnemonics = generate_mnemonics(
    group_threshold=1,
    groups=groups,
    master_secret=secret_bytes,
    passphrase=b"",   # 추가 패스프레이즈 (선택)
)

for group in mnemonics:
    for share in group:
        print(share)
        print("---")

한편, 출력은 이런 식으로 나온다.

academic acid acrobat romp academic ...   # 단어 20개
academic acid beard romp academic ...
academic acid ceramic romp academic ...
academic acid decision romp academic ...
academic acid emerald romp academic ...

복원도 단순하다.

from shamir_mnemonic import combine_mnemonics

shares = [
    "academic acid acrobat romp academic ...",
    "academic acid beard romp academic ...",
    "academic acid ceramic romp academic ...",
]

secret = combine_mnemonics(shares)
print(secret)   # 원본 바이트가 돌아온다

그런데, 3개가 모이면 원본이 나온다. 2개면 MnemonicError가 뜬다. 한 단어가 틀리면 체크섬 검증에서 즉시 걸린다. 운영에서 결정적인 차이다.

분산 절차를 어떻게 운영했나

코드보다 중요한 게 절차다. 키를 만들고 나눠주고 보관하는 과정에서 평문이 어디 흘러가는지를 통제해야 한다.

분산용 머신을 격리한다

예를 들어, 분산 작업은 인터넷이 차단된 노트북에서 수행했다. 키가 생성되고 share가 만들어지는 순간 메모리에 평문이 잠깐 존재한다. 그 컨텍스트가 네트워크에 연결돼 있으면 의미가 없다. Tails OS를 USB로 부팅해서 작업했다. (개인적으로 이게 가장 번거로웠지만 가장 본질적인 단계였다.)

반면, 작업 흐름은 이렇다.

import getpass

# 키보드로만 입력. 클립보드·파일 경유 금지
secret_hex = getpass.getpass("Enter AWS access key (hex): ")
secret_bytes = bytes.fromhex(secret_hex)

# 분산 → 인쇄 → 셸 히스토리·스왑 제거 → 전원 종료

share 전달 방식

5명에게 share를 어떻게 전달할 것인가. 처음엔 카톡이나 슬랙으로 보낼 뻔했다. 그러면 분산의 의미가 사라진다. 결국 이렇게 결정했다.

  • 단어 20개를 A4 한 장에 인쇄
  • 비밀 봉투에 봉인
  • 5명에게 대면 전달
  • 보관 책임은 각자

물론, 받은 사람은 본인이 정한 방식대로 보관한다. 누구는 집 금고, 누구는 1Password Secure Note에 입력, 누구는 metal seed plate에 각인. 보관 위치는 본인만 안다. 회사가 모은 위치 리스트를 들고 있으면 그것 자체가 표적이 된다.

복원 시 절차

물론, 복원이 필요한 시점은 거의 없을 거다. 정작 있을 때는 사고 상황(키 분실, 담당자 부재 등)이다. 시뮬레이션을 분기마다 한 번씩 돌렸다.

  • 5명 중 3명을 임의로 호출
  • 격리된 머신에 모여 각자 share를 입력
  • 복원된 키로 임시 IAM 사용자 만들고 즉시 키 로테이션
  • 새 키로 다시 분산 절차 반복

운영하면서 부딪힌 진짜 문제들

한 명이 퇴사하면?

5명 중 한 명이 퇴사했다. 그 사람이 share를 반납했다고 해도 복사본이 어디에 있는지는 모른다. 안전한 선택은 전체 재분산이다. 새 임시 키를 만들고, 5명을 다시 정해서, share 5개를 새로 분산하고, 기존 share를 모두 폐기 처리한다.

이 비용이 의외로 컸다. 5명 모두 시간을 맞춰 대면 절차를 다시 밟아야 한다. 그래서 임계값 설계를 (3, 5)에서 한 발 더 가서 그룹 단위로 가는 것도 고려할 만하다.

그룹화: (1, [(2,3), (2,3)]) 같은 구조

그러나, SLIP-39는 단일 그룹뿐 아니라 그룹의 그룹도 지원한다. 예를 들어 그룹 A(개발팀 3명, threshold 2)와 그룹 B(보안팀 3명, threshold 2)로 나누고, 두 그룹 모두에서 threshold가 충족돼야 복원되도록 만들 수 있다.

groups = [(2, 3), (2, 3)]   # 두 그룹, 각각 3명 중 2명
mnemonics = generate_mnemonics(
    group_threshold=2,       # 두 그룹 모두 충족 필요
    groups=groups,
    master_secret=secret_bytes,
    passphrase=b"",
)

실제로, 이렇게 하면 한 부서만으로는 복원이 불가능해진다. 부서 단위 결탁(collusion) 위협을 막는 구조다. 운영 복잡도가 올라가니까 조직 규모와 위협 모델을 보고 결정하면 된다.

share를 잃어버렸다는 신고가 들어왔을 때

실제로 한 번 있었다. PDF를 노트북 파일 시스템 어딘가에 저장해뒀는데 노트북을 바꾸면서 분실. 이런 경우 (3, 5) 구조면 아직 4명이 가지고 있으므로 복원 자체는 가능하다. 그러나 그 share가 제3자에게 노출됐다고 봐야 한다(노트북 폐기·중고 매각 가능성). 보존된 4명으로 복원해서 키 로테이션 → 재분산으로 가야 한다.

실제로, ::: tip 잃어버린 사람이 "복사본이 정말 없다"고 단언해도 그 말을 믿고 가만히 두면 안 된다. 분산 체계의 의미는 "최악을 가정하고 절차로 막는다"는 데 있다. 사람의 기억이 아니라 절차로 안전성을 확보한다. :::

직접 운영하며 깨달은 것들

이 체계가 모든 시크릿에 필요한 건 아니다. 평소 쓰는 IAM 액세스 키는 KMS·Secrets Manager·IAM Identity Center로 충분히 관리된다. Shamir 분산은 정말 최상위 비밀(루트 access key, 외부 KMS 키 자료, 백업 암호화 마스터 키 등)에만 적용하는 게 맞다.

따라서, 체감으로 정리하면 ssss로 시도했을 때는 한 달도 안 돼 운영이 무너졌다. SLIP-39로 갈아탄 뒤로는 분기 시뮬레이션을 두 번 무사히 통과했다. 차이를 만든 건 알고리즘이 아니라 단어 표현과 체크섬, 그리고 표준화된 도구라는 점이었다.

당장 적용할 수 있는 액션 세 가지를 남긴다.

  1. 회사에 평문으로 보관 중인 최상위 비밀 1개를 골라 (3, 5) 구조로 시범 분산해본다.
  2. share 전달은 대면 + 인쇄 봉투로 한정한다. 채널 메신저 금지.
  3. 분기마다 복원 시뮬레이션을 잡고, 끝나면 키 로테이션 → 재분산까지 한 번에 돌린다.

또한, 다음엔 AWS Nitro Enclave 안에서 SLIP-39 분산·복원을 수행해서 호스트 OS조차 평문을 보지 못하게 만드는 구조를 실험해볼 생각이다.

관련 글