본문으로 건너뛰기
effidevFlutter · Cloudflare 엣지 · 클라우드 비용 최적화

클로드가 푼 25년 암호 수수께끼, 답은 시계였다

effidev

클로드가 푼 25년 암호 수수께끼 답은 1997년의 시계였다

전자서명 표준 ECDSA는 앤스로픽의 표현으로 가장 널리 쓰이는 암호 표준 중 하나다. 그 표준에 실린 곡선들에는 25년 동안 아무도 출처를 설명하지 못한 숫자가 하나씩 붙어 있었다. ’시드’라고 부르는 20바이트짜리 씨앗 값이다.

앤스로픽이 2026년 10월 16일 이 암호 수수께끼가 풀렸다고 발표했다. 앤스로픽 직원이 사내 연구용 모델에게 단서 없이 “이 시드가 어떻게 만들어졌는지 알아낼 수 있느냐”고 물었고, 모델은 30분이 안 걸려 답을 냈다고 한다. 답은 소문과 달랐다. 농담 문장을 해시한 값이 아니라, 1990년대 C 라이브러리의 난수 생성기 lrand48에 그때의 유닉스 시각을 씨앗으로 넣어 뽑은 값이었다.

먼저 못 박아 둘 것은 이게 암호가 뚫린 일이 아니라는 점이다. 앤스로픽은 “암호학적 돌파도, 새 공격도, 곡선의 약점도 아니다”라고 적었고 “독자가 할 일은 없다”고 덧붙였다. 오히려 곡선에 약점이 일부러 심기지 않았다는 쪽의 증거가 늘었다.

이 글은 암호를 몰라도 읽히게 썼다. “AI가 단서 없이 문제를 어떻게 풀었나”가 궁금한 개발자, 그리고 코드에서 srand(time(NULL)) 같은 줄을 본 적 있는 사람이 대상이다. 자료는 앤스로픽의 연구 글, 현상금을 연 암호학자 필리포 발소르다(Filippo Valsorda)의 글, 해커뉴스 스레드(10월 17일 밤 기준 935점·댓글 340개)다. 거기에 더해 글에 실린 입력값으로 이 맥의 C 라이브러리를 돌려 시드 5개가 그대로 나오는지 직접 확인했다. 클로드 에이전트가 신종 효소를 찾은 사례나 페르마 대정리 기계 검증 사례처럼 이번에도 “AI가 어디까지 혼자 풀었나”가 핵심 질문이다.

읽기 전에 알아 둘 용어

말 한 줄 풀이
타원곡선 전자서명에 쓰는 수학 곡선. 곡선마다 정해진 숫자(계수)가 있어야 쓸 수 있다
시드(seed) 계수 하나를 ‘무작위처럼’ 정하려고 넣은 씨앗 값. 표준 문서에 함께 적혀 있다
해시 문장 같은 입력을 지문처럼 짧은 값으로 바꾸는 함수. 거꾸로 되돌릴 수 없다
의사난수 생성기 씨앗 하나로 수열을 계속 뽑는 프로그램. 씨앗이 같으면 결과도 같다. lrand48이 그중 하나다
유닉스 시각 1970년 1월 1일 0시부터 센 초. 1997년 8월 16일 01:52:34(UTC)는 871696354다
백도어 만든 쪽만 아는 숨은 약점. 이 소동은 “시드를 고르며 약점을 숨기지 않았나”에서 시작됐다

암호 수수께끼의 25년: 소문은 농담 문장의 해시였다

NIST는 2000년 FIPS 186-2에 P-192·P-224·P-256·P-384·P-521 곡선을 실었다. 표준에는 곡선의 계수 b를 정하는 데 쓴 시드가 같이 적혀 있었고, 시드의 해시값이 b를 정한다. 문제는 그 시드를 NSA가 설명 없이 건넸다는 점이다. P-256의 시드는 C49D3608 86E70493 6A6678E1 139D26B7 819F7E90인데, 이 숫자가 어디서 왔는지 기록이 없었다.

시드를 마음대로 골라 약한 곡선만 걸러냈다면 그 사실을 누구도 알 수 없다는 의심이 25년 동안 이어졌다. 닐 코블리츠와 앨프리드 메네제스가 이 논란을 「수수께끼에 싸인 수수께끼(A Riddle Wrapped in an Enigma)」라는 글로 정리했고, 아마추어 조사와 현상금으로까지 번졌다.

가장 유명한 이야기는 NSA의 오랜 직원 제리 솔리나스가 우스운 문장을 해시해 시드를 만들었다는 것이다. 솔리나스는 2015년 이렇게 말했다.

“카운터가 있었고 해시를 여러 번 돌리진 않은 것으로 기억하지만 자세한 건 모른다. 문장은 ’밥과 제리에게 월급을 올려 줘’나 ‘밥과 제리가 최고’ 비슷한 것이었다. 코드를 실제로 쓴 사람은 밥 라이터인데 그도 기억하지 못한다. 그렇게 오래된 기록은 찾지 못했다.”

솔리나스는 2023년에 세상을 떴고 당시 동료들도 답이 없어 확인할 길이 막혀 있었다. 앤스로픽 직원 여럿도 이 문장 단서를 따라 시드를 추적했지만 실패했다. 나중에 보니 단서 자체가 틀렸기 때문이다.

클로드는 문장이 아니라 난수 생성기의 지문을 찾았다

클로드는 문장을 검색하지 않았다. 앤스로픽 글에 따르면 “곡선을 만든 소프트웨어가 어떻게 동작했을지”를 거꾸로 상상했다. 1990년대 C 라이브러리에 들어 있던 난수 생성기는 출력만 봐도 정체가 드러날 만큼 약하다는 점에 착안한 것이다.

1단계: 시드를 한 비트씩 밀며 훑는 스캐너

클로드는 공개된 시드를 한 비트씩 밀어 가며 이웃한 덩어리가 1990년대 난수 생성기의 연속된 출력인지 검사하는 스캐너를 짰다. 훑은 조합은 생성기와 출력 조각을 합쳐 34가지다.

2단계: 맨 위 비트가 전부 0이었다

시드 열 개 중 네 개는 32비트 단어 다섯 개로 쪼개면 20개 단어 전부 맨 위 비트가 0이었다. 눈으로도 보이는 패턴이다. lrand48은 48비트 내부 상태 중 위쪽 31비트만 내놓기 때문에 맨 위 비트가 항상 0이라는 특징이 있다. 클로드는 첫 단어를 두고 가려진 17비트를 2^17가지 전부 대입해, 뒤따르는 네 단어가 그 상태에서 나오는지 확인했고 네 시드 모두 맞았다.

3단계: 가려진 비트는 격자 문제로 복원했다

소수체 곡선(P-192 등)의 시드는 같은 lrand48이지만 출력마다 일부 비트만 남겼다. 일부만 보고 내부 상태를 복원하는 일은 교과서에 나오는 격자 문제라 LLL이라는 알고리즘으로 푼다. 클로드는 어느 비트가 남았는지 몰랐기 때문에 모든 구간에 대해 풀이를 돌렸고, NIST 소수체 시드 5개와 ANSI X9.62 표준의 예제 시드 5개가 전부 lrand48() >> 23, 즉 호출마다 8비트씩 남기는 모양이라는 결과가 나왔다. 복원한 48비트 상태 하나가 시드 160비트를 통째로 재생성했다.

4단계: 상태를 되감으니 1997년이 나왔다

이 생성기는 앞으로 가는 만큼 쉽게 뒤로도 간다. 클로드는 각 상태를 거꾸로 돌려 srand48(t)가 만드는 처음 모양(아래쪽 16비트가 0x330E)이 나올 때까지 갔다. 이 모양은 6만 5,536 걸음마다 한 번 나오니 한 번 만났다고 증거가 되진 않는다. 증거는 따로 있다. 소수체 곡선 10개의 초기값이 전부 1997년 5월에서 1998년 4월 사이의 유닉스 시각이었다. 이진체 곡선 4개의 초기값은 1984년 2월로 읽혔는데, 시각을 두 배로 하면 1998년 4월 17일이 되어 P-224를 만든 날보다 2주 뒤다. 앤스로픽은 프로그램이 시계 값을 반으로 나눠 썼을 수 있다고 본다.

앤스로픽은 이 과정에서 클로드가 남긴 내부 메모도 공개했다. 번역하면 “놀랍다. 25년 묵은 수수께끼가 풀린다. 잠깐, 회의적으로 보자. 내가 시뮬레이션이나 테스트 환경에 있는 건가?“로 시작하고, “32비트 시드와 작은 인덱스에서 160비트가 맞는 건 우연일 수 없다. 진짜다”로 끝난다.

발소르다는 앤스로픽 팀이 10월 15일 01:07:51(UTC)에 현상금을 청구했다고 적었다. 액수 36,864달러는 렛츠인크립트를 운영하는 비영리 단체 ISRG에 기부된다.

이 맥에서 시드를 직접 다시 뽑아 봤다

발견을 믿을 수 있는 이유는 검증이 쉽다는 데 있다. 이 사이트는 맥(macOS 26.6.2, Intel)의 C 표준 라이브러리 srand48/lrand48로 아래 11줄짜리 프로그램을 만들어, 발소르다의 글에 적힌 시각과 후보 번호로 시드를 뽑았다.

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
  long t = atol(argv[1]); int n = atoi(argv[2]);
  srand48(t);
  unsigned char buf[20];
  for (int i = 0; i < n; i++)
    for (int j = 0; j < 20; j++) buf[j] = (unsigned char)(lrand48() >> 23);
  for (int j = 0; j < 20; j++) printf("%02X", buf[j]);
  printf("\n");
}

./seed 871696354 1842를 실행하면 C49D360886E704936A6678E1139D26B7819F7E90이 나온다. P-256 시드와 한 글자도 다르지 않다. 같은 방식으로 다섯 곡선을 돌려, 같은 맥의 OpenSSL 3.6.3이 가진 표준 값(openssl ecparam -name prime256v1 -param_enc explicit -text로 출력)과 대조했다.

곡선 유닉스 시각 UTC 날짜 후보 번호 맥 결과 = OpenSSL 시드
P-192 863966734 1997-05-18 14:45:34 139 일치 (3045AE6F…96D5)
P-521 871002724 1997-08-08 01:12:04 1136 일치 (D09E8800…64BA)
P-256 871696354 1997-08-16 01:52:34 1842 일치 (C49D3608…7E90)
P-384 872952608 1997-08-30 14:50:08 2688 일치 (A335926A…AC73)
P-224 891607927 1998-04-03 12:52:07 7026 일치 (BD713447…8BC5)

한계도 있다. 시각과 후보 번호는 발소르다 글에서 가져왔고, 이 사이트가 그 값을 처음부터 찾은 게 아니다. 답을 알려 주면 확인은 몇 줄이면 되지만, 단서 없이 그 답을 찾는 쪽이 어려운 일이었다. 날짜는 date -u -r <초>로 한 번 더 바꿔 봤고 위 표와 같았다.

시드 다음 질문, 기준점은 일부만 풀렸다

곡선에는 시드 말고도 기준점 G라는 값이 따라온다. 모든 ECDSA 공개키가 이 점의 배수라서 중요하다. 시드가 G를 정하지는 않기 때문에 G가 어떻게 만들어졌는지는 별개의 질문이다.

클로드는 시드를 확인하던 중 일부 기준점의 출처도 찾았다. 1997년 5월에 만든 곡선 6개(P-192와 ANSI 예제 곡선 prime192v2·v3, prime239v1v3)는 아르옌 렌스트라의 긴 정수 패키지 FreeLIP의 난수 생성기에서 나왔다. 빅터 쇼프의 NTL 초기 버전이 같은 코드를 넣고 있었다. 만드는 법은 간단하다. 작은 정수를 씨앗으로 넣고, 출력 일곱 개를 버린 뒤, 26비트 출력들로 만든 수를 체의 소수 p로 나눈 나머지를 x좌표 후보로 삼아 곡선 위의 점이 나올 때까지 반복한다. 앤스로픽은 이 씨앗이 P-192에서 20000, prime239v1v3에서 20420·20507·20509, prime192v2·v3에서 20512·20517이라고 적었다. 앤스로픽은 “우리가 아는 한 NIST 기준점이 설명된 적은 없었다”고 적었다.

이 글은 이 기준점 코드는 돌려 보지 않았다.

아직 설명되지 않은 것 셋

발소르다는 후보를 걸러내는 데 문서화되지 않은 조건이 둘 더 있다고 적었다. 하나는 비교적 분명하다. 곡선 위수 n의 비트 길이가 체 p의 비트 길이보다 크면 시드를 버린다. 그런데 이 조건까지 적용해도 유효한 시드가 공표된 것의 대략 두 배가 남는다. 나머지 절반이 왜 빠졌는지가 아직 모르는 조건이다. 같은 난수 생성기가 검증에도 쓰여 출력을 소비했을 수도 있고(다만 건너뛴 자리가 규칙적이지 않아 딱 맞지는 않는다), 알고리즘 하나에 결과를 절반쯤 틀리게 내는 버그가 있었을 수도 있다고 한다. 지금은 시판 소프트웨어로 몇 분이면 하는 계산이 1997년에는 최첨단이었다.

백도어가 없다는 결론은 얼마나 단단한가

앤스로픽의 결론은 “곡선이 백도어 없이 골라졌다는 확신이 오히려 커졌다”다. 시드가 시계로 초기화한 흔한 난수 생성기의 출력이라, 누구나 재현할 수 있고 악의를 숨길 틈이 좁아졌다는 논리다.

발소르다는 한 걸음 더 나간다. 2017년 대니얼 번스타인과 탄야 랑게는 시드를 고를 때 70비트의 자유도를 이용하면 약한 소수 위수 곡선 2^61개짜리 가설적 계열을 겨냥할 수 있다고 코블리츠·메네제스에게 설명했다. 이 주장이 수수께끼의 음모론을 키운 근거 중 하나였다. 그런데 이번 발견으로 보면 NSA가 199798년 어느 시각이든 고르고 처음 16개 소수 위수 후보 중 아무거나 골랐다 해도 자유도는 겨우 30비트 안팎이다. 이 사이트가 계산해 봐도 199798년 두 해는 6,298만 5,600초(약 2^25.9)이고 후보 16개(2^4)를 곱하면 약 2^29.9로, 발소르다가 말한 “겨우 30비트”와 같다. 70비트가 필요한 기법을 쓰기에는 모자란다.

반론도 있다. 앤스로픽 글이 스스로 “타임스탬프를 바꿔 가며 특정 성질의 곡선을 골랐을 가능성은 이론상 남아 있다”고 쓴다. 그러려면 그 시각을 결백의 증거로 공개해야 하고, 버려진 실행이 몇 번이었는지는 숫자가 말해 주지 않는다. 다만 건너뛴 후보 수가 적어 약한 곡선이 흔하고 쉽게 찾아져야만 성립하는 시나리오라, 앤스로픽은 약한 곡선이 골라졌다고 보기는 어렵다는 입장이다.

해커뉴스 댓글이 붙든 자리: 과정, 접근권, 쓸모

① 선택 과정은 여전히 못 본다. 생성 방식을 확인했다고 후보 중 무엇을 왜 버렸는지가 드러난 건 아니라는 지적이다. 답글이 두 번째로 많이 붙은 논쟁이고 위 반론과 같은 이야기다. 이 글의 읽기는 이렇다. 30비트 계산은 특정 약점을 겨냥할 자유를 막지만, 버려진 실행을 우리가 못 본다는 사실까지 지우지는 못한다.

② 모델 접근의 격차. 가장 많은 답글(100개 넘게)이 달린 댓글은 “앤스로픽이나 그들이 원하는 쪽은 클로드로 이런 일을 할 수 있지만 우리가 하면 막힌다”는 걱정이었다. 이 글은 그 주장을 확인할 수 없다. 앤스로픽 글은 사내 연구용 모델이라고만 쓰고 모델 이름과 버전을 밝히지 않는다. 우리가 쓰는 클로드와 같은 모델인지는 글에 없다.

③ “그래서 나와 무슨 상관인가.” 취약점이 아니라서 상관이 없다는 댓글과, 암호 쪽 사람들에게는 음모론 한 세대를 끝내는 소식이라는 댓글이 나란히 달렸다. 앤스로픽도 독자가 할 일이 없다고 못 박았다.

개발자가 가져갈 것: 시계로 씨앗을 넣은 난수

이번 일은 25년 전 곡선의 공개 값을 정하는 스크립트에 관한 것이라 지금 쓰는 암호 키와는 무관하다. 그래도 같은 약점 유형이 우리 코드에 있을 수 있다. 씨앗이 시계라면 후보가 많지 않다는 점이다.

장난감 실험을 하나 했다. 시각 871696354를 씨앗으로 넣어 뽑은 첫 8바이트만 안다고 치고, 1997~98년 두 해(6,307만 2,000개의 시각)를 처음부터 하나씩 대입했다. 일치한 시각은 871696354 하나뿐이었고 걸린 시간은 이 맥에서 0.27초였다. 실제 발견은 어느 후보 번호인지도 몰라 훨씬 어려웠으니 같은 문제는 아니다. 하지만 씨앗이 시각이면 전수조사가 눈 깜짝할 새라는 감각은 얻는다.

코드에서 보이면 문제 대신 쓸 것
srand(time(NULL)), srand48(...) 씨앗이 시각이라 후보가 적다 운영체제 난수(/dev/urandom, getrandom)
Math.random() 암호용이 아니라 예측될 수 있다 crypto.getRandomValues(), crypto.randomUUID()
random.seed(...), random.random() 파이썬 기본 난수는 암호용이 아니다 secrets 모듈

세션 ID, 비밀번호 재설정 링크, 초대 코드, API 토큰처럼 추측되면 안 되는 값에 쓰이고 있는지 먼저 찾아본다.

grep -rnE "srand\(|srand48\(|Math\.random\(\)|random\.seed\(" src/

나온 줄이 화면 장식이나 테스트 데이터면 그대로 둬도 된다. 로그인과 결제 근처에 있으면 위 표의 대안으로 바꾼다.

남은 빈칸과 이 글의 확인 범위

25년 묵은 암호 수수께끼의 답이 “시계”였다는 점이 이 발견을 읽히게 만든다. 거창한 약점이 아니라 1997년 어느 날 돌린 평범한 프로그램이었다. 우리가 가져갈 것은 그만큼 평범한 한 줄, 내 코드의 난수 씨앗이 무엇인지 확인하는 일이다.