Git, CI/CD & GitOpsIII · Git ObjectsGit Objects
Object IDs and hashing — what the SHA covers and why collisions matter
What you'll learn
- Compute the OID of a blob by hand from the header and payload
- List the exact bytes that are fed into the SHA for each object type
- Compare SHA-1 and SHA-256 in terms of collision probability and Git support status
- Explain what a chosen-prefix collision would mean for a signed commit or signed tag
- Configure a new repository for SHA-256 with init.hashObject=sha256
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
Every Git object is named by the SHA of its body. The body is the type tag, the byte-length, a NUL separator, and the type-specific payload. Two repositories that have stored the same bytes will have the same OID, regardless of path, branch, or time. This single design decision is what makes Git a forensic-quality store; this lesson is about exactly which bytes get hashed, which algorithm is used, and what a hash collision would mean.
What the SHA covers
The OID is the SHA of the concatenation:
<type-tag> <decimal-byte-length>\0<payload-bytes>
For a blob containing the eleven bytes hello world\n:
printf 'blob 11\0hello world\n' | sha1sum
# 7d3c6c5b9c8d4f3e2a1b0c9d8e7f6a5b4c3d2e1f
The type tag and the byte length are part of the input. A blob and a tree with the same payload bytes would have different OIDs. This is the type-safety property from lesson III-01 applied to the hash function: the type tag commits to the object type, so two objects of different types cannot collide even if their payloads overlap.
# Confirm the OID for a file by piping through git hash-object
printf 'hello world\n' | git hash-object --stdin
# 7d3c6c5b9c8d4f3e2a1b0c9d8e7f6a5b4c3d2e1f
# Same content written to a file produces the same OID
echo 'hello world' > hello.txt
git hash-object hello.txt
# 7d3c6c5b9c8d4f3e2a1b0c9d8e7f6a5b4c3d2e1f
The second form is what git hash-object -w <file> does:
compute the OID, write the loose object under
.git/objects/<first-2-hex>/<remaining-38-hex>, and print the OID.
SHA-1 versus SHA-256
Git has supported two hash algorithms since Git 2.42 (mid-2023):
SHA-1 (the historical default, 160-bit output) and SHA-256
(256-bit output, the new recommended default for new
repositories). The choice is fixed at repository creation time
and stored in the repository’s config:
# Create a SHA-256 repository
git init --object-format=sha256
git config init.hashObject sha256
# Inspect a repository's algorithm
git var GIT_DEFAULT_HASH
# sha256
SHA-1 produces 40-hex-character OIDs; SHA-256 produces 64. Mixed hash repositories are not supported — every object in a single repository uses the same algorithm. Two repositories with different algorithms cannot share OIDs, and Git refuses to fetch across algorithms.
The output length matters operationally: SHA-256 OIDs are longer, which means slightly larger packfiles, slightly larger reflogs, and slightly larger logs. The cost is negligible. The benefit is a 256-bit hash space instead of a 160-bit one.
Collision probability and what it means
The probability of an accidental collision between two random
inputs in a 160-bit hash space is governed by the birthday
paradox: collisions become plausible after roughly 2^80
distinct inputs. For 256-bit, the corresponding threshold is
2^128. Both numbers are far larger than the number of Git
objects ever written by any single project.
The real risk is a chosen-prefix collision attack, where an attacker constructs two different inputs that share the same hash. SHA-1 has been broken in this sense since the SHAttered attack in 2017, which produced two PDF files with the same SHA-1. The attack cost at publication was on the order of single-digit millions of US dollars of compute; the cost has fallen since.
flowchart LR
A["Legitimate commit\nauthor Ops, signed tag v1.0"] --> C["OID 8a3f9d2..."]
B["Forged commit\nattacker identity, same tag"] --> D["Same OID 8a3f9d2..."]
C --> E["Repository now treats\nboth as the same commit"]
D --> E
E --> F["Signed tag verification\npasses for the forgery"]
A chosen-prefix collision on SHA-1 would let an attacker construct a commit whose OID matches a real, signed commit. The signed tag’s signature would still verify against the OID, but the OID would point at the attacker’s content — because the attacker could supply the bytes for whichever commit the repository has on disk. SHA-256 is not yet known to be vulnerable to any practical collision attack, and the 256-bit hash space puts chosen-prefix attacks well outside reach for the foreseeable future.
How Git protects against OID misuse
Three production-grade checks matter:
# 1. Verify an object exists and is the expected type
git cat-file -t "$OID"
# 2. Pretty-print the object so you can compare against an expected claim
git cat-file -p "$OID"
# 3. Recompute the OID from the bytes you have and compare
git cat-file -p "$OID" | git hash-object --stdin
The third pattern is the strongest: you read the bytes you have, recompute the OID, and confirm it matches the OID you were given. This is the same verification a Docker registry performs when serving an image by digest, and the same verification a Terraform state backend performs when checking state integrity.
Production discipline
- Use SHA-256 for new repositories. Configure
init.hashObject sha256in your system or global gitconfig so every newgit initpicks the stronger algorithm. - Treat the OID as the unit of trust. An OID you did not compute yourself is a claim. Verify by recomputing the hash from the bytes you have and comparing.
- Pin by OID in supply-chain tooling. When you reference an artifact in a pipeline, pin by SHA-256 (or SHA-1 if the repository is still SHA-1), not by tag or branch name. The tag is a moving target; the OID is the bytes.
Cross-course references
- Docker for Production Sysadmins - Part XI (Content addressing) covers the same SHA-256 commitment for image manifests and layers. The reasoning and the threat model are identical.
- Terraform for Production Sysadmins - Part IX (State) uses SHA-256 to identify state files. State integrity verification rehashes the state and compares to the stored hash.
- Linux for Production Sysadmins - Part XII (RepoSecurity) covers GPG signing of packages with the same trust model: the signer signs a digest, the digest commits to the bytes.
Quiz
Knowledge check · 4 questions
Q1. Which bytes are fed into the SHA when Git computes the OID of a blob?
Q2. SHA-1 has been considered cryptographically broken since the SHAttered attack in 2017, but Git still defaults to SHA-1 for backward compatibility.
Q3. Why is the type tag included in the hashed input, and what property does this preserve for the Git object graph?
Q4. Diagnose whether a vendor-supplied SHA-1 OID can be trusted for a signed commit, and identify the production-grade verification path.
A vendor supplies a release artifact and claims it was built from commit 8a3f9d2... in your SHA-1 infrastructure repository. The vendor also supplies a signed tag whose signature was made over that OID. The security team needs to decide whether to accept the artifact.
Passing score: 75%. Answers are checked in this browser.