Git 3.0의 SHA-256 기본값 — 해시를 바꿀 것인가, 신뢰를 다시 설계할 것인가
Git 3.0의 SHA-256 기본값 — 해시를 바꿀 것인가, 신뢰를 다시 설계할 것인가
SHA-1의 약점은 해시 알고리즘을 교체해서 고칠 문제인가, 아니면 해시에 신뢰를 걸지 않는 구조로 우회할 문제인가?
도입
2026년 10월 1일(UTC) 해커뉴스 1면에 「Git 3.0’s upcoming SHA-256 default will be a costly mistake」라는 글이 올라와 583점, 500개가 넘는 댓글을 모았다. 필자는 GitHub와 GitButler의 공동 창업자이자 『Pro Git』의 저자인 Scott Chacon이다. 그는 첫 문단부터 수위를 높인다. Git 3.0이 SHA-256을 새 기본 해시로 삼는 일은 「상상하기 어려울 만큼 비싸고, 궁극적으로 가치가 없으며, 피할 수 있었던 전 지구적 악몽」이 될 것이라고 쓴다.
Git 프로젝트 쪽 문서는 정반대의 어조다. Git의 「Upcoming breaking changes」 문서는 Git 3.0의 첫 번째 변경 항목으로 새 저장소의 기본 해시 함수를 sha1에서 sha256으로 바꾼다고 적고, 그 근거로 NIST가 2011년에 SHA-1을 폐기(deprecate)했다는 점, FIPS 140-2 같은 인증에서 권장되지 않는다는 점, 그리고 2015년부터 2020년까지 이어진 일련의 실용적 공격을 나열한다. 「SHA-1이 완전히 깨졌다고 간주되는 것은 시간문제」이므로 미리 대비하겠다는 논리다.
두 문서를 나란히 놓으면 쟁점은 「SHA-1이 약한가」가 아니다. 그 점에는 양쪽 모두 동의한다. 쟁점은 그 약점에 대응하는 수단으로 「새 저장소의 기본값을 바꾼다」는 레버가 적절한가, 그리고 그 비용을 누가 치르는가다. 이 글은 Chacon의 주장, Git 공식 설계 문서, 해커뉴스의 반론을 차례로 대조해 그 질문을 따져 본다.
무엇이 바뀌고, 무엇이 깨지는가
먼저 사실관계부터 정리하자. Git은 내용 주소 지정(content-addressable) 데이터베이스다. 파일, 디렉터리(트리), 커밋 모두 내용의 해시값을 이름으로 갖는다. 커밋은 이전 커밋의 해시를 품고 있으므로, 최신 커밋 하나의 해시가 그 이전의 모든 이력을 연쇄적으로 고정한다. 2005년 Linus Torvalds가 이 해시로 SHA-1을 골랐고, 그 선택이 20년간 유지됐다.
Git 공식 문서 「hash-function-transition」은 2017년 2월 23일 SHAttered 공격이 실용적인 SHA-1 충돌을 시연했고, Git v2.13.0 이후로는 SHAttered에 취약하지 않은 「강화된(hardened) SHA-1」 구현을 기본으로 쓰고 있다고 밝힌다. 그러면서도 「SHA-1은 여전히 약하다」고 덧붙인다. 후속 해시로는 2018년 말 SHA-256이 선택됐다. 후보에는 SHA-512/256, SHA-256x16, K12, BLAKE2bp-256 등이 있었다.
Git 3.0에서 바뀌는 것은 정확히 한 가지다. git init으로 새로 만드는 저장소의 기본 object format이 sha256이 된다. BreakingChanges 문서는 「현시점에서 sha1 object format을 폐기할 계획은 없다」고 명시하고, 이 변경의 중요한 전제 조건으로 「인기 있는 Git 라이브러리, 애플리케이션, 포지(forge)를 포함한 생태계가 sha256을 지원할 준비가 되는 것」을 든다. 3.0의 출시 일정은 아직 정해지지 않았다.
Chacon이 지적하는 마찰은 이 전제 조건이 현실에서 얼마나 무거운지에 관한 것이다. 그는 지금도 다음 명령으로 SHA-256 저장소를 직접 만들어 볼 수 있다고 보여 준다.
git init --object-format=sha256 /tmp/twofiddy
그리고 그가 꼽는 문제들은 이렇다.
첫째, 포지 쪽의 선택이다. 글에 따르면 이렇게 만든 저장소는 현재 GitHub에 푸시할 수 없다. 그는 3.0 출시 전까지는 거의 확실히 고쳐질 것이고, 오히려 그것이 3.0을 지연시키는 주된 요인일 것이라고 추측한다. 문제는 고쳐진 이후다. 서버 쪽 저장소를 만들 때 sha1인지 sha256인지 맞춰야 하고, 두 형식은 섞이지 않는다. 그는 다음과 같은 오류를 많이 보게 될 것이라고 예고한다.
fatal: the receiving end does not support this repository's hash algorithm
둘째, 서브모듈이다. 같은 형식의 프로젝트끼리만 서브모듈로 묶을 수 있으므로, 라이브러리는 기존 프로젝트와 새 프로젝트 양쪽에서 쓰이려면 두 벌을 유지해야 한다는 주장이다.
셋째, 기존 저장소를 변환할 때의 비용이다. 모든 객체를 다시 해시해야 하고, 기존 서명이 깨지며, Slack이나 이메일, 이슈 트래커에 남은 SHA-1 해시 링크가 모두 죽는다. 해시를 40자로 가정하는 내부 도구도 깨진다.
넷째, 라이브러리 생태계다. Chacon은 Git 본체가 재진입 불가능하고 GPL로 묶인, 링크할 수 없는 라이브러리 형태로 개발돼 왔기 때문에 생태계의 대부분이 처음부터 다시 구현한 라이브러리를 쓰고 있으며, 이들의 SHA-256 지원은 없거나 부분적이라고 쓴다. git 바이너리를 직접 fork-exec하지 않는 스크립트와 도구는 어느 정도 깨질 것이라는 얘기다.
그는 또 Google의 Emily Shaffer가 Google이 이 문제에 어떻게 대비하는지 발표했으며, Google이 사내 전역 설정으로 새 프로젝트를 계속 SHA-1로 만들게 할 수도 있다고 언급했다고 전한다. 이 발표 내용은 글이 전하는 것으로, 필자가 발표 원본을 확인하지는 못했다.
충돌 공격은 정말 이론에 불과한가
Chacon 논증의 핵심은 비용 쪽이 아니라 편익 쪽에 있다. 그는 SHA-256 전환이 얻는 보안상 이득이 거의 없다고 본다. 그의 논리를 세 단계로 정리하면 이렇다.
첫째, 충돌 공격과 제2 역상 공격(second-preimage)을 구별해야 한다. 이미 존재하는 남의 파일과 같은 해시를 갖는 악성 파일을 만드는 것이 제2 역상 공격인데, 이것은 MD5조차 사실상 불가능하다고 그는 쓴다. 지구상의 약 30억 대 GPU를 모두 RTX 5090으로 바꿔 MD5만 계산시켜도 특정 역상을 찾는 데 기대값으로 약 160억 년이 걸린다는 계산을 제시한다. 남는 것은 충돌 공격, 즉 공격자가 처음부터 무해한 파일과 악성 파일을 한 쌍으로 만들어 무해한 쪽을 먼저 받아들이게 한 뒤 바꿔치기하는 시나리오다.
둘째, 해시는 SCM 세계에서 신뢰의 메커니즘이 아니다. 그는 Linus Torvalds가 2005년에 남긴 「SHA-1을 보안으로 여기지 말라. 진짜 보안은 배포(distribution)에 있다」는 말을 인용한다. 우리가 rust-lang의 저장소를 믿는 것은 최신 커밋의 서명 때문이 아니라 GitHub의 인증이 제대로 돌아간다고 믿기 때문이라는 것이다.
셋째, 실제 공격은 해시 충돌이 아니라 사회공학으로 일어난다. 수십만 달러어치 GPU를 돌리느니, 지친 오픈소스 메인테이너에게 4만 달러를 주고 프로젝트를 넘겨받는 편이 훨씬 싸고 확실하다는 것이다. 해커뉴스 이용자 JaumeGar는 xz 백도어가 바로 그 사례라고 짚었다(직접 인용은 아니고, 코멘트의 요지를 요약). 해시 충돌이 아니라 메인테이너 신뢰가 무너진 사건이었다는 얘기다.
이 논증에는 강한 반론이 붙었다. 이용자 kpcyrd는 글에 오류가 많다고 반박했다(직접 인용은 아니고, 코멘트의 요지를 요약). SHAttered는 이론이 아니라 실용적인 개념 증명이었고, Git이 영향을 받지 않은 것은 공격자들이 Git blob 접두사에 맞춰 무차별 대입을 하지 않았기 때문일 뿐이라는 것, 두 저장소가 전체 커밋 해시로 검증된 같은 커밋에 있으면서 체크아웃 내용이 다를 수 있다면 충돌 공격만으로도 코드 밀반입이 성립한다는 것, 그리고 Linus의 발언은 오히려 「내용 주소를 신뢰 수단으로 쓰지 말라」는 맥락이었다는 것이다.
이용자 Strilanc의 반론은 더 실무적이다(직접 인용은 아니고, 코멘트의 요지를 요약). 「낯선 사람이 만든 파일을 어떻게 받게 하겠느냐」는 질문은 GitHub 시대에는 성립하지 않는다. 모르는 사람이 PR을 보내고, 리뷰어가 그것을 git fetch해서 로컬에서 테스트하는 것은 일상이다. 「가져오면 끝장」이라는 것은 보안 경계로 받아들일 수 없다는 것이다. 그는 WebKit이 SHA-1 충돌 PDF를 테스트용으로 SVN 저장소에 넣었다가 저장소가 망가진 사건을 예로 들며, 소프트웨어는 흔히 충돌이 일어나지 않는다고 가정하고 만들어지기 때문에 충돌 자체가 예상치 못한 버그를 낳는다고 지적했다. 이용자 computerfriend는 우리가 커밋 해시에 고정(pin)하면서 그것이 불변의 내용을 가리킨다고 기대하고, 커밋과 태그 서명이 그 해시 위에 걸리므로 해시의 성질은 곧 Git의 보안 성질이라고 썼다(직접 인용은 아니고, 코멘트의 요지를 요약).
여기서 Git 공식 문서의 서술이 흥미롭다. hash-function-transition 문서는 해시 기반 주소 지정의 장점으로 무결성 검사와 빠른 조회를 꼽은 뒤, 암호학적으로 안전한 해시가 주는 추가 이점으로 「객체 이름에 서명할 수 있고, 제3자가 그 해시가 서명된 객체와 그것이 참조하는 모든 객체를 가리킨다고 신뢰할 수 있다」는 점을 명시한다. 즉 Git 프로젝트 자신은 해시를 신뢰 사슬의 일부로 설계 문서에 적어 두었다. Chacon이 말하는 「해시로 신뢰하지 말라」는 원칙과, Git이 실제로 제공하는 서명 기능의 의미론은 서로 어긋나 있다. 서명된 태그가 SHA-1 해시 위에 서명하는 한, 서명의 강도는 SHA-1의 충돌 저항성에 묶인다.
Chacon의 대안 제안은 바로 이 어긋남을 겨눈다. 그는 「독립 트리 해시 헤더」라는 방안을 내놓는다. 커밋이나 태그에 서명할 때 트리 전체 내용을 SHA-256(또는 BLAKE3 등)으로 따로 해시해 새 헤더로 넣고, 그 헤더까지 포함해 서명하자는 것이다. SHA-1은 내용 검색용 키로 남기고, 신뢰 검증은 별도 해시가 맡는다. 이것은 새로운 아이디어가 아니며 Colin Walters의 git-evtag가 2015년부터 거의 같은 일을 해 왔다고 그는 밝힌다. 그의 개념 증명은 서브모듈을 재귀적으로 포함한 Chromium(작업 트리 35GB, 210만 파일)을 M5 Mac에서 5초에, Linux 트리(1.5GB)를 257밀리초에, Git 프로젝트를 17밀리초에 체크섬했다고 한다. 수치는 필자의 자체 측정이다.
이 제안에는 분명한 한계도 있다. Chacon 스스로 인정하듯 모든 커밋이 아니라 헤더가 들어 있고 서명된 객체만 신뢰할 수 있다. 서명의 신뢰가 이력 전체로 전파되지 않는다. 또 평범한 미러나 클론 사이에서 같은 SHA-1 이름이 다른 내용을 가리킬 가능성 자체는 그대로 남는다. 이용자 SmasherEpilepti는 이 제안이 글에서 가장 흥미로운 부분인데도 가장 적게 논의되고 있다고 아쉬워하면서도, 미러에서의 충돌 문제는 여전히 남는다고 짚었다(직접 인용은 아니고, 코멘트의 요지를 요약).
기본값이라는 레버
토론에서 가장 생산적인 지점은 「Chacon이 묘사한 고통 중 무엇이 설계상 불가피하고 무엇이 아직 구현되지 않았을 뿐인가」를 가르는 대목이었다.
이용자 GrantMoyer와 gorgoiler는 Git의 hash-function-transition 문서를 근거로 반박했다(직접 인용은 아니고, 코멘트의 요지를 요약). 문서의 목표(Goals) 항목에는 SHA-256으로의 전환을 「저장소 하나씩, 다른 당사자의 조치 없이」 할 수 있어야 하고, SHA-256 저장소가 SHA-1 서버와 push/fetch로 통신할 수 있어야 하며, 사용자가 SHA-1과 SHA-256 식별자를 섞어 쓸 수 있어야 한다고 적혀 있다. SHA-256 저장소는 두 이름 사이의 양방향 매핑을 로컬에 저장하고, 서명도 SHA-1 쪽, SHA-256 쪽, 양쪽 모두로 할 수 있게 설계돼 있다. 설계대로 구현된다면 오래된 링크나 서명은 상당 부분 살아남는다.
그러나 같은 문서는 한계도 분명히 적는다. 비목표(Non-Goals)에는 「Git 프로토콜에 SHA-256 지원 추가」, 「한 저장소 안에서 여러 해시 함수의 객체를 섞기」, 「기존 SHA-1 서명 객체의 보안을 투명하게 개선하기」가 들어 있다. 주의 사항(Caveats)에는 이 설계가 모든 참조 객체를 로컬 변환 테이블에 요구하므로 얕은 클론(shallow clone)이나 가져오지 않은 서브모듈을 지원하지 않는다고 적혀 있고, Git 프로토콜이 SHA-256을 지원하기 전까지는 공개 서버에서 SHA-256 저장소를 쓰는 것을 「강하게 권장하지 않는다」고까지 쓴다. 서브모듈 포인터를 변환하려면 변환된 서브모듈 저장소가 이미 있어야 한다. 이용자 pavon과 thunderfork가 「서브모듈 호환성과 도구의 폭」이야말로 가장 어려운 부분이라고 강조한 것은 이 문서 내용과 맞아떨어진다. 이 설계 문서의 각 단계가 현재 어디까지 구현됐는지는 필자가 이번에 확인하지 못했다.
그렇다면 질문은 「기본값을 바꾸는 것」이 올바른 레버인가로 좁혀진다. 양쪽의 논리는 모두 일리가 있다.
기본값 전환을 지지하는 쪽의 논리는 이용자 ltbarcly3가 가장 간명하게 정리했다(직접 인용은 아니고, 코멘트의 요지를 요약). 기본값을 그대로 두면 아무도 SHA-256으로 옮기지 않고, GitHub도 구현할 이유가 없다. 그러다 10년 뒤 SHA-1이 정말로 깨지면 모두가 같은 날 한꺼번에 옮겨야 한다. 기본값은 생태계가 미리 준비하도록 만드는 강제 장치라는 것이다. 이용자 nicoburns가 SHA-256이 「또 하나의 IPv6」가 될 조짐을 보인다고 우려한 것(직접 인용은 아니고, 코멘트의 요지를 요약)은, 역설적으로 강제 장치가 없을 때 무슨 일이 벌어지는지를 보여 주는 비유이기도 하다.
반대쪽의 논리는 Chacon이 댓글에서 직접 밝혔다(직접 인용은 아니고, 코멘트의 요지를 요약). SHA-256을 옵션으로 두는 것은 괜찮다. 문제는 기본값이다. 기본값이 바뀌면 그 의미를 모르는 사람들이 다른 저장소, 도구, 라이브러리, 서버와 호환되지 않는 저장소를 대량으로 만들게 된다. 신경 쓰고 싶지 않은 사람에게 이해하기 어려운 문제를 떠넘기는 것이 기본값 변경이라는 주장이다.
그 사이에 놓인 중간안들도 나왔다. 이용자 kazinator는 git init이 형식을 명시하지 않으면 진단 메시지와 함께 실패하게 하자고 했고, kccqzy와 plorkyeran은 포지가 저장소 생성 시점이 아니라 첫 푸시 시점에 형식을 감지하면 Chacon이 그린 UX 문제는 대부분 사라진다고 반박했다(모두 직접 인용은 아니고, 코멘트의 요지를 요약). 이용자 gandreani는 Fossil이 SHAttered 발표 6일 뒤인 2017년 3월 1일에 SHA3-256을 대안으로 추가했고, 지금은 새 저장소와 체크인의 기본값이 SHA3-256이면서도 한 저장소 안에 SHA1과 SHA3-256 해시가 공존할 수 있어 저장소 재구축도 링크 파손도 없었다는 Fossil 문서를 인용했다. Fossil 문서는 이어서, Git 저장소는 9년이 지나도록 한 번에 하나의 해시 알고리즘만 지원한다고 덧붙인다.
Fossil의 사례가 보여 주는 것은 비용의 크기가 해시 알고리즘이 아니라 「혼재를 허용하느냐」라는 설계 선택에서 나온다는 점이다. Git은 SHA-1 이름과 SHA-256 이름의 일대일 대응(양방향 변환)을 유지하는 대가로 저장소 단위의 분리를 택했다. 그 선택이 서브모듈과 얕은 클론의 제약으로 이어진다. 이용자 amluto가 제안한 방식, 즉 특정 시점까지 존재한 SHA-1 객체 목록을 SHA-256으로 봉인하고 그 이후에는 새 SHA-1 객체를 받지 않는 방식도 같은 문제의식에서 나왔다.
규제 차원도 빠뜨릴 수 없다. BreakingChanges 문서가 NIST와 FIPS 140-2를 근거로 든 것을 두고, 이용자 0x00cl은 이 변경이 보안보다 인증 요건의 정치에 가깝다고 봤다(직접 인용은 아니고, 코멘트의 요지를 요약). Chacon은 글 말미에서 NIST의 2030년 SHA-1 기한은 「암호학적 보호를 적용하는 데」 SHA-1을 쓰는 것에 관한 것이지 스택 어딘가에 SHA-1이 존재하는 것 자체를 금지하는 것이 아니며, 모든 서명이 SHA-256 내용 해시를 덮는다면 SHA-1은 아무것도 보호하지 않는 단순한 키가 된다고 주장한다. 이 해석이 실제 인증 심사에서 받아들여질지는 별개의 문제이고, 필자는 NIST 원문으로 이를 검증하지 않았다.
결론
이 논쟁을 「SHA-1은 안전한가」의 문제로 읽으면 결론이 나지 않는다. 양쪽 모두 SHA-1이 약하다는 데 동의하고, 양쪽 모두 사회공학이 해시 충돌보다 흔한 공격 경로라는 데도 크게 이의가 없다. 갈리는 것은 두 가지다. 하나는 Git의 해시가 신뢰 사슬의 일부인가 아닌가이고, 다른 하나는 생태계를 움직이는 수단으로 기본값을 쓸 것인가다.
첫 번째 질문에 대해서는 Git 공식 문서 자신이 답을 적어 두었다. 서명이 해시 위에 걸리는 한 해시는 신뢰의 일부다. Chacon의 독립 트리 해시 헤더 제안은 이 점을 부정한다기보다 신뢰를 해시 알고리즘 교체와 분리하자는 제안으로 읽는 편이 정확하다. 그 방향은 앞으로 또 해시를 바꿔야 할 때의 비용을 줄인다는 점에서 진지하게 검토할 가치가 있다. 다만 미러 간 충돌 문제를 남긴다는 한계도 함께 따져야 한다.
두 번째 질문은 결국 순서의 문제다. BreakingChanges 문서는 스스로 「생태계가 준비되는 것」을 이 변경의 중요한 전제로 적었다. 그렇다면 기본값 전환은 생태계를 준비시키는 레버가 아니라, 준비가 끝났음을 선언하는 마지막 단계여야 한다. 서브모듈, 얕은 클론, 프로토콜 지원이 설계 문서의 비목표와 주의 사항으로 남아 있는 동안 기본값을 먼저 뒤집으면, 그 공백을 메우는 비용은 의미를 모르는 사용자에게 돌아간다.
당신의 팀에서 커밋 해시를 40자 문자열로 가정하는 스크립트, 서브모듈 의존성, 커밋 해시로 고정된 SBOM이나 출처 증명 데이터가 몇 군데에 있는지 지금 세어 볼 수 있는가. 그 숫자가 Git 3.0이 여러분에게 청구할 실제 비용이다.
출처: