dannys42/swiftfnvhash
A pure Swift implementation of the non-cryptographic Fowler–Noll–Vo hash functions for Apple platforms.
When to use FNV
FNV is appropriate when you need a small, inexpensive, reproducible hash for trusted or non-adversarial input. Common uses include deterministic partitioning or sharding, cache keys, persistent identifiers, hash tables whose inputs cannot be chosen by an attacker, deduplication prefilters, and checks for accidental data corruption.
Applications must still tolerate collisions. For example, compare the original values after a hash match. Use a cryptographic hash or an appropriate keyed hash when inputs may be attacker-controlled or when correctness, integrity, or security depends on collisions being infeasible.
Requirements
- Swift 6.0 or newer
- macOS 15+, iOS 18+, tvOS 15+, watchOS 8+, Mac Catalyst 15+, or visionOS 1+
The 32- and 64-bit APIs support every deployment target above. Native UInt128 requires Swift and OS runtime support, so Hash128 requires macOS 15+, iOS 18+, tvOS 18+, watchOS 11+, Mac Catalyst 18+, or visionOS 2+.
Installation
There is not yet a tagged release. Add the package's main branch to Package.swift:
dependencies: [
.package(url: "https://github.com/dannys42/SwiftFNVHash.git", branch: "main")
]Then add FNVHash to the dependencies of your target. In Xcode, use File → Add Package Dependencies, enter the same repository URL, and select the main branch.
Usage
Use FNV1 or FNV1a with the desired digest width. hash(data:) accepts byte sequences such as Data, [UInt8], and String.UTF8View. hash(string:) hashes the string's UTF-8 bytes.
import Foundation
import FNVHash
let data = Data("Hello, World!".utf8)
let dataDigest: UInt64 = FNV1a.Hash64.hash(data: data)
let stringDigest: UInt64 = FNV1a.Hash64.hash(string: "Hello, World!")For incremental hashing, update with any number of byte chunks and finalize once:
import FNVHash
let firstChunk = Array("Hello, ".utf8)
let secondChunk = Array("World!".utf8)
var hasher = FNV1a.Hash64()
hasher.update(data: firstChunk)
hasher.update(data: secondChunk)
let digest: UInt64 = hasher.finalize()After calling finalize(), create a new hasher before processing more input.
The six concrete hash types are:
| Variant | 32-bit | 64-bit | 128-bit | | --- | --- | --- | --- | | FNV-1 (multiply, then XOR) | FNV1.Hash32 | FNV1.Hash64 | FNV1.Hash128 | | FNV-1a (XOR, then multiply) | FNV1a.Hash32 | FNV1a.Hash64 | FNV1a.Hash128 |
Guard Hash128 calls with an availability check:
import FNVHash
if #available(macOS 15, iOS 18, tvOS 18, watchOS 11, macCatalyst 18, visionOS 2, *) {
let digest: UInt128 = FNV1a.Hash128.hash(string: "Hello, World!")
print(digest)
}FNV-1a generally distributes some input patterns better than FNV-1, but neither variant provides cryptographic security.
Command-line tool
The fnv executable hashes one or more files. File paths are positional arguments:
swift run fnv README.md
swift run fnv --bits 32 --algorithm fnv1 README.md
swift run fnv --bits 128 --algorithm fnv1a README.md LICENSEOptions:
-b, --bits <32|64|128>selects the digest width; the default is64.-a, --algorithm <fnv1|fnv1a>selects the variant; the default isfnv1a.
Files are hashed concurrently with a bounded number of tasks. Results retain the input order, including repeated paths, and use checksum-compatible output: a fixed-width lowercase hexadecimal digest, two spaces, and the original path.
References
License
This project is licensed under the terms in LICENSE.
Package Metadata
Repository: dannys42/swiftfnvhash
Default branch: main
README: README.md