---
title: "Git 3.0's SHA-256 Default: Migration Costs and Tradeoffs"
description: "Git 3.0 plans to make SHA-256 the default object format. Here is what that means for performance, storage, forges, tooling, and the trust model behind Git's hashes."
slug: "git-3-0-s-sha-256-default-migration-costs-and-tradeoffs"
published: true
read_time: 10
created_at: "2026-10-02 14:04:08.074 +0000 UTC"
updated_at: "2026-10-02 14:04:08.079 +0000 UTC"
author: "Typen"
author_url: "https://typen.blog/@typen"
tags:
  - "Featured"
  - "DevOps"
  - "Open Source"
  - "Security"
---

# Git 3.0's SHA-256 Default: Migration Costs and Tradeoffs

Git 3.0 plans to make SHA-256 the default object format. Here is what that means for performance, storage, forges, tooling, and the trust model behind Git's hashes.

![https://media.typen.blog/img/id/01a0fced-f27c-78a8-a788-b27b85dab8be](trendbot-366719586.webp)

Git is a content-addressable database: it hashes the contents of every file, tree, and commit and uses that hash as the key in an object store. Since 2005 that hash has been SHA-1. Git 3.0 is planning to change the default to SHA-256, and the migration is not a simple flag flip. It touches every repository, forge, library, and script in the ecosystem.

This article looks at what is actually changing, why the change is being made, and what the practical costs and tradeoffs are for teams that have to live through it.

## Why SHA-1 Is Considered "Broken"

SHA-1 is not broken in the sense that two ordinary files will accidentally hash to the same value. For a 160-bit hash, the birthday bound means you would need on the order of 1.4 septillion random files in a single project before an accidental collision becomes likely. In the entire history of Git, this has not happened.

What changed is that SHA-1 is now considered semi-broken because published collision attacks exist. The SHAttered work in 2017 and the "SHA-1 is a Shambles" work in 2020 showed that, for certain content shapes, an attacker with enough GPU capacity can deliberately manufacture two different inputs that hash to the same value. The cost is on the order of tens of thousands of dollars today, which is well within reach of a motivated attacker.

That is the threat model that motivated the SHA-256 migration. SHA-256 does not have the same structural weakness, so the same style of attack is not currently practical against it.

## Collision Attacks vs. Second-Preimage Attacks

It helps to separate two different attacks that are often conflated.

A **collision attack** is when an attacker generates two files with the same hash up front: one benign, one malicious. They publish the benign one, gain trust, and later swap it for the malicious one. Because the hashes match, Git cannot tell the difference. Signed tags or commits on trees containing the benign file can even be made to appear to sign the malicious version.

A **second-preimage attack** is when an attacker sees a file they want to replace and tries to construct a different file with the same hash. This is a much harder problem, and importantly, nearly no widely used hash function has ever been susceptible to it. Git could be using MD5 and still be effectively immune to second-preimage attacks. The math is stark: if every GPU on Earth were replaced with the fastest available consumer card and spent 100% of its time brute-forcing a single MD5 preimage, the expected time would still be on the order of the age of the universe.

So the realistic attack surface is collision attacks, not preimage attacks.

## What a Real Attack Looks Like

Even granting that SHA-1 collisions are cheap to produce, getting a malicious collision into a codebase is not the easiest path for an attacker. To exploit a collision, you have to:

1. Get a file with the malicious content fetched by people who do not know you and have never pulled a previous version of it.
2. Get them to run it in a way that is useful to you.

Both steps are hard. Meanwhile, the actual attacks that happen in the wild look completely different: someone socially engineers their way into write access to a popular npm package, or convinces a tired maintainer to hand over an unloved but widely used project. That attack is cheaper, faster, and far more likely to succeed than engineering a hash collision and getting it into a trusted fetch path.

This is the core of the argument against treating the hash as the trust mechanism. As Linus Torvalds put it when Git was first being designed, the SHA-1 is not the security; the real security is in distribution. Trust in the Git world is based on where you pull from, not on the hash function used to key the object store.

## What Actually Changes in Git 3.0

When Git 3.0 ships with SHA-256 as the default, new repositories created with `git init` will use SHA-256 object IDs. You can try this today:

```bash
$ git init --object-format=sha256 /tmp/example
Initialized empty Git repository in /tmp/example/.git/
$ cd /tmp/example
$ echo 'sha 256' > README.md
$ git add README.md && git commit -m 'first'
$ git log
commit 11043f6a3be7d21e999dc84550886306bee65f4faf4fc9226979108fd1a0b1af (HEAD -> main)
```

The first thing you notice is the much longer hash. The second is that you cannot push this to a forge that does not support SHA-256 repositories. That support is being added, but it is one of the things currently delaying the release.

## The Migration Costs

The interesting part is not the algorithm change itself; it is everything around it.

### Repositories Are Bucketed by Hash Format

Every repository is either SHA-1 or SHA-256. They cannot be mixed. That means when you create a repository on a forge, you have to tell it which format you want, and you have to know which format your local `git init` produced. If you get it wrong, you get this:

```bash
$ git push origin main
fatal: the receiving end does not support this repository's hash algorithm
```

For most developers, this is a new failure mode that did not exist before, and it is not obvious from the error message what went wrong.

### Submodules Must Match

Submodules can only be used with projects of the same hash format. A library that wants to be usable by both old and new projects will need to maintain two versions, or forges will need to keep mirrors in the other format. Mirrors add load to every operation on the host and further complicate the trust story.

### Converting Existing Repositories Is Disruptive

For an existing project that decides to convert from SHA-1 to SHA-256, every object in the project has to be rewritten. That breaks every existing signature. Everyone working on or using the project has to switch at the same time to avoid split-head problems, or the project has to run a mirror and cut write access over from one to the other. Even then, mirrors that do not have the SHA-256 version still have the object replacement problem.

### Links and Tooling Break

Every URL, Slack message, email, or issue comment that ever contained a SHA-1 hash becomes a dead link after conversion. Redirects can help, but only if the host supports them and the mapping still exists.

Any tooling that assumes a 40-character hash will need to be updated to detect or guess the format. That is a wide surface: CI scripts, code review bots, internal dashboards, IDE plugins, and anything else that parses commit IDs.

### Libraries Are the Long Tail

Core Git supports both formats, but the wider ecosystem does not. Because Git was developed as a non-reentrant, GPL-licensed, unlinkable library, most tools in the ecosystem use from-scratch reimplementations. Those libraries have zero or partial SHA-256 support. Any script or tool that does not shell out to the Git binary will hit breakage on SHA-256 repositories.

This is the part that is hardest to estimate. It is not a single migration; it is a long tail of small fixes across many projects.

## An Alternative: Independent Tree Hash Headers

There is a different approach that avoids bifurcating the ecosystem. Instead of changing the object format, you can independently hash the tree contents with a second algorithm and inject that hash as a header into the objects you sign.

Concretely: when you sign a commit or tag, calculate a SHA-256 (or BLAKE3, or whatever) hash of all the content in the tree, and add it as a new header in the object before signing. The signature then covers both the SHA-1-based content and history, and the independently computed tree hash. When someone pulls the object, they can verify the signature and check that the checked-out content matches both hashes.

This is not a new idea. Colin Walters' `git-evtag` has done essentially this since 2015: it is a drop-in replacement for `git tag -s` that adds a `Git-EVTag-v0-SHA512` checksum over the commit, tree, and every blob (recursing into submodules) to the tag before signing, verifiable independently of the SHA-1 of the same tree.

If SHA-256 is ever compromised, you add support for a new hash function and projects that care can start requiring it. You can even carry multiple hashes and verify none, any, or all of them. The cost of adding a new hash is a new header, not a global migration.

The tradeoff is that you cannot trust every commit this way, only the objects that have the header and are signed. Trust does not propagate down the entire history. For projects that care about this, that is usually fine, because they tend to pin to tagged releases anyway.

The cost of computing the independent hash is small. A proof-of-concept tool that recursively checksums Chromium with all submodules (a 35GB working tree of 2.1M files) generates the checksum in about 5 seconds on a multithreaded M5 Mac. The Linux tree takes 257ms for its 1.5GB tree; the Git project takes 17ms. For most projects, you could put this in every commit. It is also backfillable: you can go back and add signed, checksummed tags to historical commits.

This approach also arguably addresses the NIST compliance angle. NIST's 2030 SHA-1 deadline is about using SHA-1 "for applying cryptographic protection," not about SHA-1 existing anywhere in your stack. If every signature also covers a SHA-256 content hash, SHA-1 is no longer protecting anything; it is just a content-addressed key. FIPS-mode systems already handle this pattern: OpenSSL 3 lets an application request a non-FIPS implementation of a hash it is not using for security, and Git does not go through OpenSSL for object IDs anyway.

## The Tradeoffs, Summarized

| Approach | Cost | Benefit |
| --- | --- | --- |
| Migrate default to SHA-256 | Ecosystem-wide migration; forges, libraries, tooling, links, signatures | Removes SHA-1 collision attack surface at the object-ID level |
| Keep SHA-1, add signed tree hash headers | Small per-signature cost; trust does not propagate through history | Adds a second content trust vector without breaking the ecosystem |

Both approaches address the theoretical collision attack. The difference is where the cost lands: on every project in the ecosystem, or on the small number of projects that actually need the extra verification.

## What This Means for Teams

If you maintain a repository, a forge, a CI pipeline, or a tool that parses Git object IDs, the SHA-256 default is coming and you will need to plan for it. The practical questions are:

- Do you know which hash format each of your repositories uses?
- Does your tooling assume 40-character hashes?
- Do your submodules and dependencies use the same format?
- If you convert, how will you handle existing signatures and links?
- If you do not convert, how will you handle new repositories created with the new default?

For most teams, the answer will be to stay on SHA-1 for existing repositories as long as possible, and to treat the new default as something to be aware of rather than something to adopt immediately. The cost of converting is real, and the security benefit is largely theoretical for repositories whose trust model is based on where the code is pulled from, not on the hash function used to key the object store.

