기준일을 100으로 놓고 그린 선입니다. 위 화면의 계산과는 무관하고, “누가 이걸로 버는가”를 보여 주려고 붙였습니다.
깃 공식 문서는 자기를 “내용으로 주소를 매기는 파일 시스템”이라고 부릅니다. 파일 하나, 폴더 하나, 커밋 하나가 전부 내용을 해시한 값으로 불립니다. 이 설계에서 진짜 사고는 디스크가 죽는 게 아니라 서로 다른 내용이 같은 이름을 받는 것입니다. 그러면 저장소는 조용히 한쪽을 잃습니다. 그래서 ‘몇 자리로 할까’는 취향이 아니라 확률 계산입니다.
160 = SHA-1, 지금 대부분의 깃 저장소. 256 = SHA-256, 깃이 2018년 말에 고른 후계.
기본값은 이 글을 쓴 PC의 richmap 저장소를 실제로 센 값입니다
(git cat-file --batch-all-objects, 2026-08-26).
★상단 100억은 근거 있는 눈금이 아니라 가정입니다.
기본 7은 깃 소스의
#define FALLBACK_DEFAULT_ABBREV 7,
최소 4는 minimum_abbrev = 4와 맞춰 두었습니다.
위 줄은 진짜 커밋 해시입니다.
38,838 ÷ 69일(첫 커밋 2026-06-08 ~ 최신 2026-08-16). ★실측에서 나눈 파생값입니다.
★두 번째 계기가 이 조각의 급소입니다. 짧은 해시는 이미 겹치고 있습니다. 깃은 그때마다 그 객체만 한 자리씩 늘려 보여 줍니다.
사람들이 여기서 자주 틀립니다. 충돌은 객체 하나하나에 일어나는 사고가 아니라 객체 ‘쌍’에 일어나는 사고입니다. 그래서 세는 것이 n이 아니라 n(n−1)/2입니다.
기본값에서 40자리 전체가 겹칠 확률은 5.16 × 10⁻⁴⁰입니다. 그 확률이 절반이 되려면 이 저장소에 객체가 1.42 × 10²⁴개 있어야 합니다. 실질적으로 0이라는 뜻입니다.
그런데 손잡이의 자릿수를 7로 자르면 같은 저장소에서 93.98%가 됩니다. 40자리와 7자리 사이의 이 간격이 이 조각의 요점입니다. 자릿수 하나를 자를 때마다 확률이 16배가 됩니다 — hex 한 자리가 4비트니까요.
짧은 해시가 겹치기 시작하면 어떻게 될까요. 깃은 저장소가 커지면 자릿수를 스스로 늘립니다. 그 규칙이 깃 소스에 이렇게 적혀 있습니다.
“We now know we have on the order of 2^len objects, which
expects a collision at 2^(len/2). But we also care about hex chars, not bits,
and there are 4 bits per hex. So all together we need to divide by 2 and round up.”
— 깃 소스 odb.c, 함수 odb_find_abbrev_len
방금 위에서 돌린 생일 계산과 글자 그대로 같은 식입니다. 코드로 옮기면 이렇습니다.
검산을 해 봤습니다.
이 PC의 richmap 저장소는 객체가 38,838개입니다.
위 식에 넣으면 8자리가 나옵니다.
그리고 같은 저장소에서 git log --oneline이 실제로 찍는 짧은 해시는
2adbdb68 — 8자리입니다. 맞습니다.
우연이 아닙니다. 깃이 같은 생일 계산을 하고 있기 때문입니다. 손잡이의 객체 수를 밀어 보세요 — 7→8→9→10으로 계단식으로 올라갑니다.
깃이 객체 이름을 짓는 규칙은 한 줄입니다. 내용만 해시하는 게 아니라, 타입과 바이트 수를 앞에 붙이고 널 바이트로 끊은 다음 해시합니다.
둘 다 이 PC에서 방금 돌린 것입니다. 깃은 마법을 쓰지 않습니다 — 파이썬 한 줄로 같은 이름이 나옵니다. 그리고 여기서 이 조각의 문제가 정확히 드러납니다. 이름이 곧 내용의 정체성이라면, 같은 이름을 받은 두 내용은 깃에게 같은 물건입니다.
2017년 2월, CWI 암스테르담과 구글이 SHA-1의 첫 충돌을 만들어 발표했습니다. 아래는 그 논문 3쪽 Table 1에 인쇄된 진짜 충돌 블록 두 벌입니다. 각각 128바이트이고, —가 서로 다릅니다. 다른 자리를 붉게 칠했습니다.
버튼을 누르면 이 페이지가 SHA-1 압축함수를 직접 네 번 돌립니다 (외부 라이브러리 없이 80스텝 루프 그대로). 결과를 논문이 인쇄한 값과 그 자리에서 대조하고, 안 맞으면 화면이 스스로 “안 맞는다”고 적습니다.
첫 블록을 지나면 두 파일의 상태는 서로 다릅니다. 그런데 둘째 블록을 지나는 순간 같은 값에 착지합니다. 거기서부터 뒤에 무엇을 이어 붙이든 두 파일의 SHA-1은 영원히 같습니다. 이게 ‘조용히 한쪽을 잃는다’가 실제로 일어나는 방식입니다.
★논문은 최종 PDF 두 개의 파일 해시를 인쇄하지 않았습니다. 그래서 여기에도 쓰지 않습니다. 재현한 것은 압축함수 체이닝값이고, 그것이 곧 충돌입니다 — 그것만으로 충분합니다.
넷째 계기가 재는 것이 이것입니다. 아무 요령 없이 충돌을 찾으려면 해시를 몇 번 돌려야 하는가 — 이 식도 제가 고른 게 아니라 논문이 인쇄한 것입니다.
“the expected number of calls to H … to find a collision using an
optimal generic algorithm is √(π/2)·2n/2”
— Stevens 외, “The first collision for full SHA-1”, IACR ePrint 2017/190
| 항목 | 값 | 출처 |
|---|---|---|
| SHA-1 일반 충돌 탐색 기대 계산량 | 1.52 × 10²⁴회 (≈1.25·2⁸⁰) | 위 식으로 화면이 계산 |
| 2017년 실제 공격 | 263.1회 | 논문 초록 인쇄값 |
| 구글이 발표한 총 계산 횟수 | 9,223,372,036,854,775,808 | 구글 보안 블로그 — 정확히 2⁶³ |
| 들어간 자원 | 약 6,500 CPU-년 + 100 GPU-년 | 논문 초록 |
| (같은 사건, 다른 숫자) | 6,500 CPU-년 + 110 GPU-년 | 구글 블로그 — ★논문과 어긋난다 |
기본값에서 넷째 계기가 15만 배를 가리킵니다. 논문 초록이 적은 표현은 “무차별 대입보다 10만 배 이상 빠르다” — 방향과 자릿수가 맞습니다.
손잡이의 해시 길이를 256비트로 올려 보세요. 같은 계기가 4.3 × 10¹⁹배로 튀어오릅니다. 깃이 후계 해시로 SHA-256을 고른 이유가 이 한 칸에 들어 있습니다. 깃 공식 문서는 그 선정 기준을 “흔한 보안 관행에 맞을 만큼 길되, 성능과 디스크를 해칠 만큼 지나치게 길지는 않은 256비트”라고 적었습니다.
그리고 깃은 그 전에 이미 방어를 넣었습니다 — v2.13.0부터 hardened SHA-1을 기본으로 씁니다. 같은 문서의 문장은 이렇습니다: 그 구현은 “SHAttered 공격에 취약하지 않지만, SHA-1은 여전히 약하다”.
충돌은 ‘운 좋게 같은 값이 나왔다’가 아닙니다. 두 파일의 내부 상태를 일부러 벌려 놨다가 되접어 같은 자리로 데려오는 것입니다. 위에서 돌린 압축함수의 매 스텝 상태를 받아 적어, 두 파일이 몇 비트 다른지를 그려 보겠습니다.
가로는 내부 상태 160비트(a·b·c·d·e 다섯 워드), 세로는 스텝입니다. 칠해진 칸은 두 파일이 그 비트에서 다르다는 뜻입니다 — 비트 값 자체는 그리지 않습니다. 여기서 물어야 할 것은 값이 무엇이냐가 아니라 둘이 언제 갈리고 언제 만나느냐이기 때문입니다. 오른쪽 막대는 그 줄에서 다른 비트가 몇 개인지입니다.
★블록이 둘인 것이 전부입니다. 블록 1은 같은 CV0에서 출발해 서로 다른 값으로 끝나고 (두 체이닝값이 14비트 다릅니다), 블록 2는 그 다른 두 값에서 출발해 같은 값으로 끝납니다.
★0이 되는 자리가 스텝이 아닙니다. 마지막 160번째 스텝에서도 15비트가 다릅니다. 차이가 사라지는 건 압축함수 끝의 덧셈(H + 작업상태)에서입니다 — 그래서 스텝 80줄마다 덧셈 한 줄을 따로 세웠습니다(파란 줄). 들어온 체이닝값이 14비트 다르고 마지막 작업 상태가 15비트 다른데, 더하면 0입니다. 그렇게 되는 두 번째 블록을 찾아낸 것이 이 공격입니다. 0이 안 되면 화면이 스스로 어긋났다고 적습니다 — 그러라고 대조하는 것입니다.
★첫 블록만 보면 ‘안 됐다’로 보입니다. 블록 1의 덧셈 줄에서 두 체이닝값은 여전히 다릅니다. 공격이 성립하는 건 두 번째 블록의 덧셈까지 다 하고 나서입니다. 중간에 차이가 최대 58비트까지 벌어졌다가 되접혀 들어옵니다 — ‘충돌을 찾았다’는 말이 실제로 무엇을 한 것인지가 이 그림입니다.
이 조각의 진짜 ‘실물’은 위 화면에서 방금 돌린 충돌 블록입니다. 사진은 그 계산이 어디서 시작되고 어디서 돌았는지를 보태는 정도입니다.
암스테르담의 CWI(Centrum Wiskunde & Informatica) 정문입니다. 벽돌 건물에 붉은 CWI 명판이 붙어 있습니다.
위에서 재현한 충돌 논문의 저자 소속 첫 줄이 CWI 암스테르담입니다. SHA-1을 실제로 깨뜨린 계산이 여기서 설계됐습니다.
구글의 오리건주 더댈러스 데이터센터입니다. 냉각탑이 열 지어 서 있고 뒤로 민둥산이 보입니다.
논문이 적은 자원은 약 6,500 CPU-년 + 100 GPU-년입니다. 사람이 셀 수 있는 시간이 아니라 이런 건물을 몇 달 돌린 시간입니다.
★이 계산이 이 건물에서 돌았다는 근거는 없습니다. 논문은 “여러 데이터센터에 분산된 대규모 계산”이라고만 적었고 어느 센터인지는 밝히지 않았습니다. 규모 감각용으로만 보십시오.
2014년 뒤셀도르프 LinuxCon Europe 무대입니다. 커먼즈 설명문에 Dirk Hohndel과 Linus Torvalds가 이야기하는 장면이라고 적혀 있습니다. (★설명문에 좌우가 명시돼 있지 않아 여기서도 특정하지 않습니다.)
토르발스가 2005년에 깃을 만들면서 정한 것이 “이름은 SHA-1 40자리”였습니다. 위 화면의 모든 계산이 그 한 결정에서 나옵니다.
여기까지가 계산이고, 여기서부터는 장부입니다. 깃 자체는 누구나 공짜로 씁니다. 돈은 그걸 남 대신 안전하게 붙들고 있어 주는 데서 납니다.
마이크로소프트는 2018년 10월 깃허브를 75억 달러에 샀습니다. 10-K에 인쇄된 매입가 배분표가 흥미롭습니다 (단위 백만 달러).
| 항목 | 배분액 | 비중 | 내용 |
|---|---|---|---|
| 영업권 | 5,497 | 79.4% | 장부에 물건으로 안 잡히는 것 |
| 무형자산 | 1,267 | 18.3% | 고객관련 648 · 기술기반 447 · 마케팅 170 · 계약 2 |
| 현금·단기투자 | 234 | 3.4% | |
| 기타자산 − 기타부채 | −74 | −1.1% | |
| 합계 | 6,924 | 100% |
기술에 붙은 값은 447 — 배분액의 6.5%입니다. 나머지는 영업권입니다. 10-K의 표현은 “미래 성장에서 기대되는 시너지에 주로 기인”이고, 그 영업권은 Intelligent Cloud 세그먼트에 배정됐습니다.
그럼 지금은 얼마를 벌까요. 알 수 없습니다. FY2026 10-K 전문에서 ‘GitHub’은 딱 다섯 번 나오는데 전부 서술이고, 숫자가 붙은 적이 한 번도 없습니다. 깃허브는 이런 줄 안에 이름만 남아 있습니다 — “Server products and cloud services”, FY2026 매출 1,294억 달러.
대조군을 하나 놓으면 크기가 잡힙니다. 깃랩은 같은 일을 회사 통째로 합니다. FY2026 총매출 9.55억 달러(전년 7.59억, +26%), 영업손실 7,048만 달러 — 아직 적자입니다. 마이크로소프트가 깃허브를 묻어 둔 그 한 줄이 깃랩 전체 매출의 135배입니다.
★깃허브의 매출·이용자 수는 1차 공시에 없습니다. 그래서 여기서도 비워 둡니다.