웹사이트 인증서와 패스키에 널리 쓰이는 타원곡선 P-256에는 출처를 알 수 없던 숫자가 하나 있었어요 미국 국가안보국(NSA)이 제공한 20바이트짜리 시드(seed)입니다
Anthropic은 10월 16일 이 숫자가 1997년 시각으로 초기화한 난수였다고 밝혔어요 공개 재현 코드의 출력값은 표준 문서의 시드 값과 일치했습니다
FIPS 186-4 부록의 Curve P-256 파라미터와 SEED 값, 하이라이트 — 출처: NIST
시드가 의심받은 이유
정체를 알 수 없는 숫자 씨앗을 돋보기로 들여다보는 장면을 표현한 개념 컷 — 출처: 개념 컷 · agy 자가 생성
타원곡선은 y² = x³ + ax + b 꼴의 식으로 정해져요 P-256에서 계수 b는 시드에 SHA-1 해시 함수를 적용해 얻은 값으로 만들어집니다
해시값에서 원래 입력값을 역산하기는 어려워요 그래서 시드가 정해지면 b를 임의로 지정하기 어렵습니다
출처를 보여 조작이 없음을 입증하는 숫자를 nothing-up-my-sleeve 숫자라고 해요
문제는 그 시드 자체를 어떻게 정했는지 설명하는 공개 자료가 없었다는 점이에요
NIST는 2000년 FIPS 186-2에 P-192~P-521 곡선을 넣었어요 NSA가 준 시드는 설명 없이 그대로 실렸습니다
시드를 반복 변경하며 해시를 계산하면 특정 성질을 가진 곡선을 고를 수 있어요 2013년 NSA의 Dual_EC_DRBG 백도어 의혹이 불거진 뒤 이 의심이 커졌어요
NSA 수학자 Jerry Solinas는 2015년 시드가 농담 문구의 해시라고 말한 것으로 전해져요 그가 2023년 세상을 떠나면서 당사자에게 직접 확인하기 어려워졌어요
시드를 찾는 사람에게는 현상금도 제시됐습니다
lrand48이라는 난수 생성기
결론은 해시가 아니었어요 1990년대 C 라이브러리에서 제공하던 난수 함수 lrand48의 출력이었습니다
lrand48은 선형 합동 생성기(LCG, linear congruential generator)예요 48비트 상태 값 하나를 아래 식으로 계속 갱신합니다
Xₙ₊₁ = (25,214,903,917 × Xₙ + 11) mod 2⁴⁸
시작 상태는 srand48(t)가 정해요 시각 t(1970년 1월 1일부터 센 초)를 16비트 왼쪽으로 밀고 하위 16비트를 0x330E로 채웁니다
🔢 P-256 시드가 나온 과정
시작 값은 1997년 8월 16일 01시 52분 34초(UTC)예요 한국 시간으로는 오전 10시 52분 34초입니다
그 시각을 초로 바꾼 871,696,354가 t입니다
상태를 한 번 갱신할 때마다 상위 8비트를 추출해 1바이트로 써요 20바이트를 모으면 시드 후보 하나가 됩니다
P-256 시드는 이 실행의 1,842번째 후보였고, 그때까지 lrand48을 36,840회 실행했습니다
NIST 소수체 곡선 다섯 개의 공개 시드가 각 실행에서 몇 번째 후보였는지 비교한 막대 차트 — 출처: Anthropic 재현 코드 실행 결과 기반 자가 렌더
소수체 곡선 시드 10개는 모두 1997년 5월부터 1998년 4월 사이 시각으로 초기화됐어요 P-224는 1998년 4월 3일 실행의 7,026번째 후보였습니다
Claude가 찾아낸 방법
Anthropic의 한 직원이 사내 연구용 모델에 농담 문구 단서를 주지 않고 시드의 출처를 물었어요 Claude는 30분이 안 돼 NIST 소수체 곡선 시드 다섯 개의 출처를 찾았어요
🔍 상위 비트가 전부 0
Claude는 먼저 1990년대 C 라이브러리 난수 생성기의 출력 흔적을 찾는 스캐너를 짰어요 생성기와 출력 자르는 방식의 조합 34가지를 시드의 비트를 1비트씩 이동하며 대조했습니다
시드를 32비트 단어 다섯 개로 나누면, 10개 중 4개는 20개 단어 모두 맨 앞 비트가 0이었어요 우연히 이럴 확률은 5,810분의 1입니다
lrand48은 48비트 상태 중 상위 31비트만 돌려주므로 32비트 단어의 맨 앞이 늘 0이에요
알 수 없던 17비트의 경우의 수 2¹⁷ = 131,072개를 모두 넣어 봤어요 그러자 다음 단어들이 생성기 출력과 일치했습니다
🧮 LLL 격자 알고리즘
소수체 곡선 시드는 출력의 일부 비트만 남겨 써서 위 방법이 통하지 않았어요 잘린 출력에서 LCG의 내부 상태를 되찾는 문제는 격자(lattice) 문제로 풀 수 있습니다
Claude는 LLL(Lenstra–Lenstra–Lovász) 기저 축소 알고리즘으로 상태를 복구했어요 NIST 시드 5개와 ANSI X9.62 시드 5개가 모두 lrand48() » 23 꼴이었습니다
LCG는 이전 상태를 역산할 수 있어서, 하위 16비트가 0x330E인 지점까지 되짚었어요 그 지점의 위쪽 32비트를 날짜로 읽으면 모두 1997~1998년 시각이 나왔습니다
시드 10개가 모두 같은 생성기와 그럴듯한 날짜로 맞아떨어진 것은 우연으로 설명하기 어렵습니다
직접 돌려 본 재현
Anthropic 공개 코드를 실행해 NIST 소수체 곡선 다섯 개의 시드를 다시 만든 터미널 화면 — 출처: Anthropic 공개 코드 직접 실행
Anthropic은 재현 코드를 CC0(퍼블릭 도메인)로 공개했어요 Python 3.14에서 실행하니 0.2초 안에 끝났습니다
출력한 시드를 NIST 표준 문서 FIPS 186-4 부록의 값과 한 글자씩 대조했어요
| 곡선 | FIPS 186-4 시드와 대조 |
|---|---|
| P-192·224·256·384·521 | 5개 모두 일치 |
| B-233·283·409·571 | 4개 모두 일치 |
| B-163 | 코드에 없음 |
이진체 곡선 B-233부터 B-571까지는 시각을 2로 나눈 값으로 초기화한 경우와 일치해요 그대로 읽으면 1984년 2월이 나오고, 두 배로 늘리면 1998년 4월 17일입니다
시드에서 곡선 계수 b가 나오는지도 FIPS 186-4 부록 절차대로 계산해 봤어요 P-256 시드를 SHA-1에 넣어 만든 값 r이 r × b² ≡ −27 (mod p)를 만족했습니다
백도어 의혹과 남은 질문
Anthropic은 이번 결과가 ECDSA의 약점이나 새 공격이 아니라고 밝혔어요 시각으로 초기화한 흔한 난수 생성기로 시드를 만들었다면 곡선을 골라 숨길 여지가 줄어든다는 설명입니다
- 건너뛴 후보: P-256 실행의 876번째 후보도 조건에 맞는 곡선이었지만 쓰이지 않음
- B-163 시드: 어떤 방법으로도 재현되지 않음
- 기저점: P-224·256·384·521의 기저점 G를 만든 방법은 미확인
- 실행 횟수: 시각을 바꿔 몇 회 실행했는지는 시드값에서 확인할 수 없음
P-192와 ANSI X9.62 예제 곡선 다섯 개의 기저점은 출처가 확인됐어요 Arjen Lenstra의 FreeLIP 난수 생성기에서 나온 값입니다
시드 현상금 약 5,161만 원(36,864달러)은 ISRG에 기부돼요 ISRG는 무료 인증서 Let’s Encrypt를 운영하는 비영리 단체입니다
국내에서 P-256을 쓰는 곳
openssl로 www.naver.com 인증서의 공개키가 prime256v1인지 확인한 터미널 화면 — 출처: 직접 실행
P-256은 표준마다 secp256r1, prime256v1로도 불리는 같은 곡선이에요 패스키(WebAuthn)의 기본 서명 방식 ES256도 P-256 ECDSA입니다
2026년 10월 19일 국내 주요 사이트 인증서를 openssl로 확인했어요
| 사이트 | 인증서 공개키 |
|---|---|
| www.naver.com | P-256 (EC) |
| www.google.com | P-256 (EC) |
| www.daum.net | RSA 2048 |
| www.kakaocorp.com | RSA 2048 |
| www.gov.kr | RSA 2048 |
| www.kbstar.com | RSA 2048 |
| toss.im·www.coupang.com | RSA 2048 |
네이버 서버는 접속 과정의 서명에 P-256 ECDSA를 쓰고 있어요 키 교환은 네이버·Google·쿠팡 모두 P-256이 아니라 X25519MLKEM768로 협상됐습니다
Anthropic은 독자가 바꿀 것은 없다고 밝혔어요 이번 결과는 P-256을 쓰는 서비스의 안전성을 낮추는 내용이 아닙니다