Crockford Base32 encoding and CRC-10 checksums for human-readable, error-correcting identifiers.
-
Crockford Base32 Encoding
- Human-readable alphabet (excludes ambiguous characters: I, L, O, U)
- Case-insensitive decoding
- Automatic error correction (I→1, L→1, O→0)
- Fixed-length and compact encoding modes
- URL-safe output
-
CRC-10 Checksums
- 99.9%+ error detection rate
- Detects single character errors (100%)
- Detects transpositions (99.9%+)
- Detects double errors (99.9%+)
- Only 2 characters overhead
go get github.com/jasoet/pkg/v2package main
import (
"fmt"
"github.com/jasoet/pkg/v2/base32"
)
func main() {
// Encode a number
id, err := base32.EncodeBase32(12345, 8) // "0000C1S", nil
if err != nil {
panic(err)
}
// Add checksum for error detection
idWithChecksum, err := base32.AppendChecksum(id)
if err != nil {
panic(err)
}
// Validate checksum
if base32.ValidateChecksum(idWithChecksum) {
fmt.Println("Valid ID!")
}
// Decode back
value, _ := base32.DecodeBase32(id)
fmt.Println(value) // 12345
}// Database ID to short code
databaseID := uint64(123456789)
shortCode := base32.EncodeBase32Compact(databaseID)
// https://short.url/3QTYY1
// Decode back
decoded, _ := base32.DecodeBase32(shortCode)// Generate order ID with timestamp and sequence
timestamp := uint64(time.Now().Unix())
sequence := uint64(12345)
timeCode, _ := base32.EncodeBase32(timestamp, 8)
seqCode, _ := base32.EncodeBase32(sequence, 4)
orderID, _ := base32.AppendChecksum("ORD-" + timeCode + "-" + seqCode)
// ORD-6HG4K2N0-00C1P9XYproductID := uint64(42)
customerID := uint64(789)
product, _ := base32.EncodeBase32(productID, 2)
customer, _ := base32.EncodeBase32(customerID, 4)
licenseKey, _ := base32.AppendChecksum(product + customer)
// Format: 16-00NC-XYvoucherID := uint64(9999)
code, _ := base32.EncodeBase32(voucherID, 4)
codeWithChecksum, _ := base32.AppendChecksum(code)
// 09ZZ-XY (easy to type, error-correcting)deviceSerial := uint64(123456)
deviceID := base32.EncodeBase32Compact(deviceSerial)
// Compact, human-readable device identifierEncodes an unsigned integer to a fixed-length Base32 string.
encoded, err := base32.EncodeBase32(42, 4) // "001A", nil
encoded, err := base32.EncodeBase32(12345, 6) // "000C1S", nilEncodes to the minimum number of characters needed (no error return).
encoded := base32.EncodeBase32Compact(0) // "0"
encoded := base32.EncodeBase32Compact(12345) // "C1S"Decodes a Base32 string to an unsigned integer.
value, err := base32.DecodeBase32("C1P9") // 12345, nil
value, err := base32.DecodeBase32("c1p9") // 12345, nil (case-insensitive)
value, err := base32.DecodeBase32("C1PO") // 12345, nil (O→0 correction)Normalizes Base32 input by:
- Converting to uppercase
- Removing dashes and spaces
- Correcting common mistakes (I→1, L→1, O→0)
base32.NormalizeBase32("abc-def") // "ABCDEF"
base32.NormalizeBase32("1O 2I") // "1021"Checks if a character is valid in Base32 encoding.
base32.IsValidBase32Char('A') // true
base32.IsValidBase32Char('O') // true (auto-corrected)
base32.IsValidBase32Char('U') // falseComputes a 2-character CRC-10 checksum.
checksum, err := base32.CalculateChecksum("ABC123") // "XY", nilAdds checksum to the end of data.
withChecksum, err := base32.AppendChecksum("ABC123") // "ABC123XY", nilVerifies checksum validity.
valid := base32.ValidateChecksum("ABC123XY") // true
valid := base32.ValidateChecksum("ABC123ZZ") // falseRemoves the last 2 characters (checksum).
data := base32.StripChecksum("ABC123XY") // "ABC123"Extracts the last 2 characters (checksum).
checksum := base32.ExtractChecksum("ABC123XY") // "XY"The CRC-10 checksum provides excellent error detection:
| Error Type | Detection Rate |
|---|---|
| Single character error | 100% |
| Transposition (AB→BA) | 99.9%+ |
| Double errors | 99.9%+ |
| Insertion/deletion | High |
// Valid ID
validID, _ := base32.AppendChecksum("ABC123")
// Corrupted ID (A → X)
corrupted := "XBC123" + base32.ExtractChecksum(validID)
base32.ValidateChecksum(corrupted) // false - detected!
// Transposition (AB → BA)
chars := []rune(validID)
chars[0], chars[1] = chars[1], chars[0]
transposed := string(chars)
base32.ValidateChecksum(transposed) // false - detected!Run comprehensive examples:
go run -tags=example ./base32/examplesSee examples/main.go for detailed usage patterns.
Benchmarks on modern hardware:
BenchmarkEncodeBase32-8 50000000 25.3 ns/op
BenchmarkDecodeBase32-8 30000000 45.2 ns/op
BenchmarkCalculateChecksum-8 10000000 125.0 ns/op
BenchmarkValidateChecksum-8 8000000 160.0 ns/op
0 1 2 3 4 5 6 7 8 9 A B C D E F G H J K M N P Q R S T V W X Y Z
Excluded characters:
I- Looks like1(auto-corrected to1)L- Looks like1(auto-corrected to1)O- Looks like0(auto-corrected to0)U- Could be confused withV
This design minimizes human transcription errors.
-
Always use checksums for user-facing IDs
// ✓ Good id, err := base32.AppendChecksum(data) // ✗ Avoid id := data // No error detection
-
Normalize user input
userInput := "ab-cd-ef" normalized := base32.NormalizeBase32(userInput)
-
Use fixed-length encoding for databases
// Consistent length for indexing id := base32.EncodeBase32(value, 10)
-
Use compact encoding for URLs
// Shorter URLs shortCode := base32.EncodeBase32Compact(value)
If migrating from github.com/jasoet/tix-core/encoding:
Before:
import "github.com/jasoet/tix-core/encoding"After:
import "github.com/jasoet/pkg/v2/base32"API is 100% compatible - only the import path and package name change.
See the main pkg/v2 repository for contribution guidelines.
MIT License - see LICENSE for details.
Part of github.com/jasoet/pkg/v2 - Production-ready Go utility packages.