본문 바로가기
보안2026년 10월 16일17분 읽기

앤트로픽 Claude, 25년 묵은 NIST P-256 시드 미스터리 풀어 — 해시된 문장 아닌 1997년 타임스탬프 난수였다

YS
김영삼
조회 16
앤트로픽 Claude, 25년 묵은 NIST P-256 시드 미스터리 풀어 — 해시된 문장 아닌 1997년 타임스탬프 난수였다

앤트로픽(Anthropic)은 10월 16일(현지시간) 연구 글 "Claude reveals ECDSA curve origins"에서, 25년 넘게 출처가 수수께끼였던 NIST P-256 등 ECDSA 표준 곡선의 시드(seed) 값이 해시된 비밀 문장이 아니라 1990년대 C 라이브러리 난수 함수 lrand48에 유닉스 타임스탬프를 넣어 뽑은 값이었다고 밝혔다.

앤트로픽 직원 한 명이 사내 연구용 Claude 모델에게 힌트 없이 물었고, 모델은 30분이 채 안 돼 P-192, P-224, P-256, P-384, P-521 다섯 곡선의 시드 생성 방식을 찾아냈다. 앤트로픽은 이것이 암호 해독이나 새 공격이 아니며, 오히려 곡선에 백도어가 있을 가능성을 크게 낮추는 결과라고 강조했다. 걸려 있던 현상금 36,864달러는 Let's Encrypt 운영 단체인 ISRG에 기부된다.

3줄 요약
  • NIST P-256 시드 C49D3608…819F7E90은 1997년 8월 16일 01:52:34(UTC) 타임스탬프로 초기화한 lrand48의 1,842번째 20바이트 후보였다.
  • Claude는 오래된 난수 생성기의 흔적을 찾는 스캐너와 LLL 격자 축소로 내부 상태를 복원했고, NIST 소수체 곡선 5개와 ANSI X9.62 예제 곡선 5개, 이진체 곡선 4개의 시드를 재현했다.
  • 앤트로픽은 "취할 조치는 없다"고 했다. 다만 일부 후보를 건너뛴 이유, B-163 시드, P-256 등의 기준점(G) 생성 방식은 여전히 풀리지 않았다.
30분 미만
Claude가 다섯 NIST 소수체 곡선 시드의 기원을 찾는 데 걸린 시간
1,842번째
P-256 표준 시드가 나온 후보 순번 (후보 1개 = 20바이트)
1997.5~1998.4
복원된 srand48 초기화 타임스탬프의 범위 (소수체 곡선 10개)
$36,864
시드 현상금, 전액 ISRG에 기부

무슨 일이"그냥 타임스탬프였다" — 25년 묵은 시드 미스터리

ECDSA는 가장 널리 쓰이는 타원곡선 서명 표준이다. NIST는 2000년 FIPS 186-2 디지털 서명 표준에 크기별 표준 곡선을 실었는데, 소수체와 이진체 곡선에는 "시드"라는 20바이트 값이 함께 공개됐다. 이 시드를 SHA-1로 해시한 값에서 곡선 계수 b를 만든다. 이른바 "검증 가능한 무작위(verifiably at random)" 방식이다. 문제는 그 시드 자체가 어디서 왔는지 아무도 설명하지 않았다는 점이다.

시드는 NSA가 제공했다. 스티븐 와이스(Stephen Weis)가 2023년에 정리한 글에 따르면, ANSI X9.62 표준의 주 저자였던 알프레드 메네제스(Alfred Menezes) 교수는 1997년 가을 NSA 대표로 표준 회의에 참석하던 제리 솔리나스(Jerry Solinas)가 시드를 건넸다고 확인해 줬다. 솔리나스는 생전에 "SHA-1으로 유머러스한 문장을 해시했다. 'Give Bob and Jerry a raise' 같은 것이었는데 정확한 문장은 기억나지 않는다"는 취지로 말했고, 그는 2023년 세상을 떠났다.

Dual_EC_DRBG 사건 이후 NSA가 관여한 표준을 믿기 어렵다는 분위기가 커지면서, 출처를 알 수 없는 시드는 "NSA가 약한 곡선을 골라 놓았을지 모른다"는 의심의 단골 소재가 됐다. 2023년 10월 필리포 발소르다(Filippo Valsorda)는 다섯 시드의 원래 입력을 찾는 사람에게 12,288달러, 자선단체에 기부하면 세 배인 36,864달러를 주는 현상금을 걸었다. 비밀번호 크래커들이 "Jerry"가 들어간 영어 문장을 대량으로 돌렸지만 아무도 맞히지 못했다.

앤트로픽 글에 따르면 사내에서도 몇몇 직원이 이 "문장 해시" 단서를 따라가다 실패했다. 그러다 2026년 10월, 한 직원이 단서를 주지 않고 내부 연구 모델에게 시드가 어떻게 만들어졌는지 알아낼 수 있느냐고 물었다. 결과는 단서와 정반대였다. 시드는 어떤 해시의 출력도 아니었고, C 표준 라이브러리의 lrand48 난수 생성기에 유닉스 타임스탬프를 넣어 초기화한 뒤 20바이트씩 뽑은 후보 중 하나였다.

어떻게 찾았나문장을 찾지 않고, 1990년대 프로그램을 상상했다

Claude의 접근은 "그 시절 곡선 생성 프로그램이라면 어떻게 짰을까"에서 출발했다. 1990년대 C 라이브러리에 딸려 오던 의사난수 생성기(PRNG)는 출력만 보고도 정체를 알아챌 수 있을 만큼 약하다. 어떤 생성기를 썼는지 알면 초기 상태까지 되짚을 수 있다.

1단계: 오래된 생성기의 지문 찾기

Claude는 공개된 시드를 한 비트씩 밀어 가며, 인접한 조각이 1990년대 생성기의 연속 출력처럼 보이는지 검사하는 스캐너를 작성했다. 생성기 종류와 출력 구간 조합 34가지를 훑었다. 그러다 일부 시드에서 패턴이 보였다. 시드를 32비트 단어 5개로 쪼개면, 시드 10개 중 4개에서 모든 단어의 최상위 비트가 0이었다. 우연히 그럴 확률은 5,810분의 1이다. 48비트 상태 중 상위 31비트만 돌려주는 선형 합동 생성기(LCG) lrand48의 특징이다. 앤트로픽은 "알고 보면 눈으로도 보이는 패턴"이라고 적었다.

상태 복원은 단순했다. 첫 단어를 기준으로 숨겨진 17비트의 217가지 경우를 모두 넣어 보고, 다음 네 단어가 맞아떨어지는지 확인했다. 이 방식이 해당 시드 4개 모두에 통했다.

2단계: 잘린 출력에서 상태 복원 — LLL 격자 축소

소수체 곡선 시드는 같은 lrand48에서 나왔지만 출력마다 남긴 비트 수가 더 적었다. 잘린 LCG 출력에서 내부 상태를 복원하는 일은 교과서적인 격자 문제이고, Lenstra–Lenstra–Lovász(LLL) 기저 축소로 풀린다. 어느 비트 구간이 시드에 드러났는지 몰랐기 때문에 Claude는 모든 구간에 대해 솔버를 돌렸다. NIST 소수체 곡선 시드 5개와 ANSI X9.62 예제 시드 5개가 모두 같은 패턴, lrand48() >> 23을 보였다. 복원한 48비트 상태 하나가 시드 160비트 전체를 다시 만들어 내는데, 이건 우연으로 일어날 수 없다.

3단계: 시계를 거꾸로 돌려 srand48 시점 찾기

LCG는 앞으로 돌리는 것만큼 뒤로 돌리기도 쉽다. Claude는 각 시드의 상태를 거꾸로 되돌려 srand48이 만드는 형태, 즉 하위 16비트가 0x330E인 상태에 닿을 때까지 거슬러 올라갔다. 이런 형태는 65,536걸음마다 한 번은 나오므로 그것만으로는 증거가 되지 않는다. 결정적이었던 건 그 초기값을 시간으로 읽었을 때다. 소수체 곡선 시드 10개의 초기값이 모두 1997년 5월에서 1998년 4월 사이의 유닉스 타임스탬프였다.

이진체 곡선 4개의 초기값은 1984년 2월로 읽혔다. 값을 두 배로 하면 1998년 4월 17일, P-224를 만든 날로부터 2주 뒤가 된다. 앤트로픽은 프로그램이 시계 값을 절반으로 나눠 썼을 수 있다고 봤다. 이 부분은 "그렇게 읽는 게 맞다면"이라는 단서가 붙어 있다.

앤트로픽이 공개한 재현 코드의 입력값. B-163 시드는 아직 설명되지 않았다.
곡선srand48 타임스탬프(UTC)후보 순번비고
P-192 (prime192v1)1997-05-18 14:45:34139ANSI X9.62 예제 prime192v2·v3와 같은 실행
P-5211997-08-08 01:12:041,136현상금 대상 5개 중 하나
P-2561997-08-16 01:52:341,842876번째 후보도 조건을 만족했지만 건너뜀
P-3841997-08-30 14:50:082,688현상금 대상
P-2241998-04-03 12:52:077,026현상금 대상, 실행 중 유효 곡선 3개를 건너뜀
B-233·B-283·B-409·B-5711998-04-17 15:24~15:32 (시각÷2로 초기화 추정)155 / 32 / 60 / 73이진체, 4분 남짓 사이에 연속 생성

앤트로픽은 Claude의 당시 사고 과정도 공개했다. 자신이 테스트 환경에 있는 건 아닌지 의심하다가, "10개 × 160비트 일치는 우연일 수 없다. 진짜다"라고 결론 내리는 대목이다.

직접 돌려 보기P-256 시드 재현 코드

앤트로픽은 시드와 일부 기준점을 재현하는 파이썬 코드를 CC0로 공개했다. 아래는 그 코드에서 P-256 한 줄만 남긴 것이다. 내 노트북에서 돌려 보니 FIPS 186-2에 실린 시드와 정확히 같은 값이 나왔다.

from datetime import datetime, timezone

def unix(when):
    return int(datetime.fromisoformat(when).replace(tzinfo=timezone.utc).timestamp())

def candidate(t, n, per, shift, width):
    x, out = (t << 16) | 0x330E, ""          # srand48(t)가 만드는 상태
    for i in range(per * n):
        x = (0x5DEECE66D * x + 0xB) % 2**48  # lrand48 한 걸음
        if i >= per * (n - 1):
            out += "%0*X" % (width, x >> shift)
    return out

# P-256: 1997-08-16 01:52:34 UTC, 1842번째 후보, lrand48() >> 23
print(candidate(unix("1997-08-16 01:52:34"), 1842, 20, 40, 2))
# C49D360886E704936A6678E1139D26B7819F7E90

0x5DEECE66D와 0xB는 lrand48 계열이 쓰는 표준 LCG 상수다. 출력 한 번에 상위 8비트만 남기고(48비트 상태에서 40비트를 오른쪽으로 민 값) 그걸 20번 이어 붙여 20바이트 후보 하나를 만든다. 숨길 공간이 거의 없는 열 줄짜리 코드다.

덤기준점 G 일부도 설명됐다

곡선에는 시드 말고도 기준점 G가 있다. 모든 ECDSA 공개키는 G의 배수다. 시드는 G를 정하지 않기 때문에 G의 출처도 별도의 질문이었다. Claude는 시드를 확인하던 중 1997년 5월에 만들어진 곡선 6개(P-192와 ANSI X9.62 예제 prime192v2·v3, prime239v1~v3)의 기준점이 아르옌 렌스트라(Arjen Lenstra)의 다정밀 정수 연산 패키지 FreeLIP의 난수 생성기에서 나왔다는 것도 찾아냈다. 초기 NTL 라이브러리에도 같은 코드가 들어 있었다고 한다.

방법은 작은 정수로 생성기를 초기화하고, 출력 일곱 개를 버린 뒤, 26비트 출력으로 x 좌표 후보를 만들어 곡선 위에 올라갈 때까지 반복하는 식이다. P-192의 생성기 시드는 20000이었다. 앤트로픽은 NIST 기준점이 설명된 건 이번이 처음인 것으로 안다고 했다.

왜 중요한가깨진 게 아니라, 의심이 줄었다

먼저 오해부터 정리하자. 이번 발견은 암호 해독도, 새 공격도, ECDSA 곡선의 약점도 아니다. 앤트로픽 글 첫머리부터 "취할 조치나 바꿔야 할 것은 없다. 대부분의 연구자는 이미 이 곡선을 신뢰했다"고 못 박는다. TLS 인증서, SSH 키, 각종 서명에서 P-256을 쓰고 있다면 그대로 쓰면 된다.

의미는 신뢰 쪽에 있다. 백도어 의혹의 핵심은 "누군가 시드를 마음대로 골랐을 수 있다"는 것이었다. 이제 시드는 시계로 초기화한 평범한 난수 생성기 출력이고, 누구나 재현할 수 있다. 앤트로픽은 "조작할 여지가 훨씬 줄었다"고 평가했다. 발소르다도 2023년 현상금 공지에서, 시드를 완전히 통제할 수 있었다 해도 학계가 25년간 못 찾을 만큼 거대한 약한 곡선 집합을 NSA가 알고 있었어야 하므로 의혹은 그럴듯하지 않다고 썼다. 이번 결과는 그 논리에 구체적인 근거를 더한다.

다만 남은 질문도 앤트로픽이 직접 적어 뒀다. 모든 20바이트 후보가 쓸 만한 곡선을 만드는 건 아닌데, 쓸 만한 후보를 건너뛴 흔적이 있다. P-256은 1,842번째 후보가 채택됐지만 같은 실행의 876번째 후보도 같은 조건의 소수 위수 곡선을 만든다. P-224 실행은 그런 곡선을 3개 건너뛰었고, ANSI X9.62 예제 곡선을 만든 두 실행은 7개를 더 건너뛰었다. 어떤 기준이었는지, 의도치 않은 결과인지는 모른다.

앤트로픽은 건너뛴 수가 적기 때문에 약한 곡선을 골라냈을 가능성은 낮다고 본다. 그러려면 약한 곡선이 흔하고 찾기 쉬워야 하기 때문이다. 숫자만으로는 실행이 몇 번 있었는지 알 수 없다는 점도 인정했다.

아직 풀리지 않은 것
B-163 이진체 곡선의 시드는 Claude가 시도한 어떤 방식에도 맞지 않았다. P-224·P-256·P-384·P-521과 NIST 이진체 곡선의 기준점 G 생성 방식도 미지수다. 채택 전에 유효 후보를 건너뛴 이유 역시 설명되지 않았다.
영삼
영삼의 한마디
솔직히 처음 제목만 보고는 "AI가 암호를 깼다"는 식의 기사가 쏟아질까 걱정했다. 원문은 그 반대다. 25년간 사람들이 영어 문장을 해시하며 찾던 답이 사실은 srand48(time(NULL))였다는 건, 보안 업계에 오래 있던 사람일수록 허탈하게 웃을 이야기다. 나도 예전에 테스트 데이터를 만들 때 시간으로 시드를 박아 넣은 적이 있으니 남 얘기 같지 않다. AI 쪽에서 눈여겨볼 대목은 따로 있다. 잘못된 단서를 주지 않았더니 오히려 풀었다는 것. 사람이 붙여 준 가설이 탐색 범위를 좁혀 버리는 일은 내 디버깅에서도 자주 겪는다.

앞으로개발자가 할 일은 없다, 읽을거리는 있다

실무 조치는 필요 없다. P-256·P-384 기반 인증서나 키를 교체할 이유는 생기지 않았다. 오히려 NIST 곡선을 피하려고 일부러 다른 곡선을 고르던 근거 하나가 약해졌다고 보는 게 맞다. 곡선 선택은 이제 성능, 구현 안전성, 라이브러리 지원 같은 공학적 기준으로 판단하면 된다.

연구자들에게는 남은 숙제가 분명하다. B-163 시드, 나머지 기준점, 건너뛴 후보의 이유. 앤트로픽은 재현 코드를 공개했으니 다른 사람이 이어받아 검증하고 확장할 수 있다. 해커뉴스 토론은 1,065포인트, 댓글 390개를 넘겼다.

연구는 앤트로픽 프런티어 레드팀이 진행했고, 저자 중 한 명인 스티브 와이스의 2023년 글 맨 위에는 이제 "2026년 업데이트: 타임스탬프였다!"라는 한 줄이 붙었다.

자주 묻는 질문

NIST P-256 시드는 어떻게 만들어졌나?

앤트로픽 연구에 따르면 NIST P-256 시드는 C 라이브러리의 lrand48 난수 생성기를 1997년 8월 16일 01:52:34(UTC) 유닉스 타임스탬프로 초기화한 뒤 20바이트씩 뽑은 1,842번째 후보다. 그동안 알려졌던 "유머러스한 문장을 SHA-1로 해시했다"는 이야기와는 달랐다. 앤트로픽이 공개한 코드로 누구나 재현할 수 있다.

이번 발견으로 ECDSA나 P-256이 위험해졌나?

아니다. 앤트로픽은 이것이 암호 해독이나 새 공격, 곡선의 약점이 아니라고 밝혔다. 오히려 시드가 평범한 난수 생성기에서 나왔다는 게 확인돼 백도어 가능성이 낮아졌다는 평가이며, 사용자가 취할 조치는 없다.

Claude는 ECDSA 시드를 어떻게 찾았나?

Claude는 문장을 추측하는 대신 1990년대 C 라이브러리 난수 생성기의 출력 패턴을 찾는 스캐너를 만들었다. 최상위 비트가 모두 0인 패턴에서 lrand48을 알아냈고, 잘린 출력은 LLL 격자 축소로 내부 상태를 복원한 뒤 상태를 거꾸로 돌려 srand48 초기화 시점을 찾았다. 앤트로픽은 이 과정이 30분이 안 걸렸다고 밝혔다.

NIST 곡선 시드 현상금은 어떻게 되나?

2023년 필리포 발소르다가 조직한 현상금은 기부 시 세 배가 되는 구조였고, 총 36,864달러는 Let's Encrypt를 운영하는 ISRG(Internet Security Research Group)에 기부된다. 앤트로픽 연구 글에 이 내용이 명시돼 있다.

ECDSA 곡선에서 아직 풀리지 않은 것은?

이진체 곡선 B-163의 시드, P-224·P-256·P-384·P-521과 이진체 곡선의 기준점 G 생성 방식은 아직 설명되지 않았다. 또 P-256 생성 과정에서 876번째 후보처럼 조건을 만족한 곡선을 건너뛴 이유도 밝혀지지 않았다. 앤트로픽은 건너뛴 수가 적어 약한 곡선을 골랐을 가능성은 낮다고 봤다.

출처

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록