← 실계산 조각
이걸로 돈을 버는 두 회사● 시세 불러오는 중

기준일을 100으로 놓고 그린 선입니다. 위 화면의 계산과는 무관하고, “누가 이걸로 버는가”를 보여 주려고 붙였습니다.

★깃 자체는 누구나 공짜로 씁니다. 돈은 남 대신 안전하게 붙들고 있어 주는 데서 납니다. ★깃허브 단독 매출은 1차 공시에 없어 비워 두었습니다.
실계산 조각 · 깃 해시

깃이 지키는 건 파일이 아니라
이름이다

깃 공식 문서는 자기를 “내용으로 주소를 매기는 파일 시스템”이라고 부릅니다. 파일 하나, 폴더 하나, 커밋 하나가 전부 내용을 해시한 값으로 불립니다. 이 설계에서 진짜 사고는 디스크가 죽는 게 아니라 서로 다른 내용이 같은 이름을 받는 것입니다. 그러면 저장소는 조용히 한쪽을 잃습니다. 그래서 ‘몇 자리로 할까’는 취향이 아니라 확률 계산입니다.

② 자릿수 하나의 값충돌 50%까지 필요한 개수 · 로그 눈금 · 위 판의 식 그대로

손잡이

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). ★실측에서 나눈 파생값입니다.

눌러서 맞춰 보기

★두 번째 계기가 이 조각의 급소입니다. 짧은 해시는 이미 겹치고 있습니다. 깃은 그때마다 그 객체만 한 자리씩 늘려 보여 줍니다.

전체 자릿수가 겹칠 확률
보여 주는 자릿수가 겹칠 확률
깃이 스스로 고르는 자릿수
2017년 실제 공격 대비

객체가 두 배면 위험은 네 배다

사람들이 여기서 자주 틀립니다. 충돌은 객체 하나하나에 일어나는 사고가 아니라 객체 ‘쌍’에 일어나는 사고입니다. 그래서 세는 것이 n이 아니라 n(n−1)/2입니다.

쌍의 수 = n(n−1) ÷ 2  ★n이 두 배가 되면 여기가 네 배가 된다
N = 2b  이름이 가리킬 수 있는 서로 다른 값의 개수
λ = 쌍의 수 ÷ N  기대 충돌 쌍 수
P(한 쌍이라도 겹침) = 1 − e−λ
n50 = √(2·ln2·N) = 1.1774 · 2b/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

방금 위에서 돌린 생일 계산과 글자 그대로 같은 식입니다. 코드로 옮기면 이렇습니다.

bits = ⌊log2 n⌋ + 1  객체 수를 비트로
d = ⌈ bits ÷ 2 ⌉  ← 2로 나누고 올림 · 위 주석 그대로
d = max(d, 7)  밑바닥은 기본값 7
n* = 22d  자릿수가 한 칸 더 늘어나는 객체 수

검산을 해 봤습니다. 이 PC의 richmap 저장소는 객체가 38,838개입니다. 위 식에 넣으면 8자리가 나옵니다. 그리고 같은 저장소에서 git log --oneline이 실제로 찍는 짧은 해시는 2adbdb68 — 8자리입니다. 맞습니다.

우연이 아닙니다. 깃이 같은 생일 계산을 하고 있기 때문입니다. 손잡이의 객체 수를 밀어 보세요 — 7→8→9→10으로 계단식으로 올라갑니다.

이름은 이렇게 지어진다

깃이 객체 이름을 짓는 규칙은 한 줄입니다. 내용만 해시하는 게 아니라, 타입과 바이트 수를 앞에 붙이고 널 바이트로 끊은 다음 해시합니다.

이름 = H( “<타입> <바이트수>” + 0x00 + 내용 )
예: H(“blob 5\0hello”)

$ printf 'hello' | git hash-object --stdin
b6fc4c620b67d95f953a5c1c1230aaab5db5a1b0
>>> hashlib.sha1(b'blob 5\x00hello').hexdigest()
b6fc4c620b67d95f953a5c1c1230aaab5db5a1b0  ← 같다

둘 다 이 PC에서 방금 돌린 것입니다. 깃은 마법을 쓰지 않습니다 — 파이썬 한 줄로 같은 이름이 나옵니다. 그리고 여기서 이 조각의 문제가 정확히 드러납니다. 이름이 곧 내용의 정체성이라면, 같은 이름을 받은 두 내용은 깃에게 같은 물건입니다.

2017년의 충돌을 이 페이지에서 다시 돌린다

2017년 2월, CWI 암스테르담과 구글이 SHA-1의 첫 충돌을 만들어 발표했습니다. 아래는 그 논문 3쪽 Table 1에 인쇄된 진짜 충돌 블록 두 벌입니다. 각각 128바이트이고, 가 서로 다릅니다. 다른 자리를 붉게 칠했습니다.

파일 A · 128바이트

파일 B · 128바이트

버튼을 누르면 이 페이지가 SHA-1 압축함수를 직접 네 번 돌립니다 (외부 라이브러리 없이 80스텝 루프 그대로). 결과를 논문이 인쇄한 값과 그 자리에서 대조하고, 안 맞으면 화면이 스스로 “안 맞는다”고 적습니다.

첫 블록을 지나면 두 파일의 상태는 서로 다릅니다. 그런데 둘째 블록을 지나는 순간 같은 값에 착지합니다. 거기서부터 뒤에 무엇을 이어 붙이든 두 파일의 SHA-1은 영원히 같습니다. 이게 ‘조용히 한쪽을 잃는다’가 실제로 일어나는 방식입니다.

★논문은 최종 PDF 두 개의 파일 해시를 인쇄하지 않았습니다. 그래서 여기에도 쓰지 않습니다. 재현한 것은 압축함수 체이닝값이고, 그것이 곧 충돌입니다 — 그것만으로 충분합니다.

10만 배 싸게 끝났다

넷째 계기가 재는 것이 이것입니다. 아무 요령 없이 충돌을 찾으려면 해시를 몇 번 돌려야 하는가 — 이 식도 제가 고른 게 아니라 논문이 인쇄한 것입니다.

“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비트까지 벌어졌다가 되접혀 들어옵니다 — ‘충돌을 찾았다’는 말이 실제로 무엇을 한 것인지가 이 그림입니다.

풀고 있는 식

— 확률 층
λ = n(n−1) ÷ (2 · 2b)   b = 전체 비트수, 또는 자른 자릿수 × 4
P = 1 − e−λ
n50 = 1.1774 · 2b/2
★2160은 배정밀도로 못 센다. 전부 로그 공간에서 계산하고 마지막에만 편다.

— 깃 자릿수 층 (odb.c 규칙 그대로)
d = max(7, ⌈(⌊log2 n⌋ + 1) ÷ 2⌉)
n* = 22d  ·  남은 날 = (n* − n) ÷ 하루 증가분

— 계산량 층 (논문 인쇄 식)
G = √(π/2) · 2b/2  ·  배수 = G ÷ 263.1

이 이야기가 실제로 있었던 자리

이 조각의 진짜 ‘실물’은 위 화면에서 방금 돌린 충돌 블록입니다. 사진은 그 계산이 어디서 시작되고 어디서 돌았는지를 보태는 정도입니다.

암스테르담 CWI 연구소 정문

① 40자리를 40자리로 놔둘 수 없게 만든 자리

암스테르담의 CWI(Centrum Wiskunde & Informatica) 정문입니다. 벽돌 건물에 붉은 CWI 명판이 붙어 있습니다.

위에서 재현한 충돌 논문의 저자 소속 첫 줄이 CWI 암스테르담입니다. SHA-1을 실제로 깨뜨린 계산이 여기서 설계됐습니다.

사진 Robvanroo · CC BY-SA 4.0 · 위키미디어 커먼즈
구글 오리건 더댈러스 데이터센터 외관

② 6,500 CPU-년이 나오는 물건

구글의 오리건주 더댈러스 데이터센터입니다. 냉각탑이 열 지어 서 있고 뒤로 민둥산이 보입니다.

논문이 적은 자원은 약 6,500 CPU-년 + 100 GPU-년입니다. 사람이 셀 수 있는 시간이 아니라 이런 건물을 몇 달 돌린 시간입니다.

이 계산이 이 건물에서 돌았다는 근거는 없습니다. 논문은 “여러 데이터센터에 분산된 대규모 계산”이라고만 적었고 어느 센터인지는 밝히지 않았습니다. 규모 감각용으로만 보십시오.

사진 Tony Webster · CC BY 2.0 · 위키미디어 커먼즈
2014년 LinuxCon Europe 무대의 두 사람

③ ‘40자리로 하자’고 정한 사람

2014년 뒤셀도르프 LinuxCon Europe 무대입니다. 커먼즈 설명문에 Dirk Hohndel과 Linus Torvalds가 이야기하는 장면이라고 적혀 있습니다. (★설명문에 좌우가 명시돼 있지 않아 여기서도 특정하지 않습니다.)

토르발스가 2005년에 깃을 만들면서 정한 것이 “이름은 SHA-1 40자리”였습니다. 위 화면의 모든 계산이 그 한 결정에서 나옵니다.

사진 Krd · CC BY-SA 4.0 · 위키미디어 커먼즈

자료구조는 공짜인데, 붙들어 주는 건 75억 달러다

여기까지가 계산이고, 여기서부터는 장부입니다. 깃 자체는 누구나 공짜로 씁니다. 돈은 그걸 남 대신 안전하게 붙들고 있어 주는 데서 납니다.

마이크로소프트는 2018년 10월 깃허브를 75억 달러에 샀습니다. 10-K에 인쇄된 매입가 배분표가 흥미롭습니다 (단위 백만 달러).

항목배분액비중내용
영업권5,49779.4%장부에 물건으로 안 잡히는 것
무형자산1,26718.3%고객관련 648 · 기술기반 447 · 마케팅 170 · 계약 2
현금·단기투자2343.4%
기타자산 − 기타부채−74−1.1%
합계6,924100%

기술에 붙은 값은 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차 공시에 없습니다. 그래서 여기서도 비워 둡니다.