406 lines
12 KiB
Markdown
406 lines
12 KiB
Markdown
# RFC-0004: LTHN Quasi-Salted Hash Algorithm
|
|
|
|
**Status:** Informational
|
|
**Version:** 1.0
|
|
**Created:** 2025-01-13
|
|
**Author:** Snider
|
|
|
|
## Abstract
|
|
|
|
This document specifies the LTHN (Leet-Hash-N) quasi-salted hash algorithm, a deterministic hashing scheme that derives a salt from the input itself using character substitution and reversal. LTHN produces reproducible hashes that can be verified without storing a separate salt value, making it suitable for checksums, identifiers, and non-security-critical hashing applications.
|
|
|
|
## Table of Contents
|
|
|
|
1. [Introduction](#1-introduction)
|
|
2. [Terminology](#2-terminology)
|
|
3. [Algorithm Specification](#3-algorithm-specification)
|
|
4. [Character Substitution Map](#4-character-substitution-map)
|
|
5. [Verification](#5-verification)
|
|
6. [Use Cases](#6-use-cases)
|
|
7. [Security Considerations](#7-security-considerations)
|
|
8. [Implementation Requirements](#8-implementation-requirements)
|
|
9. [Test Vectors](#9-test-vectors)
|
|
10. [References](#10-references)
|
|
|
|
## 1. Introduction
|
|
|
|
Traditional salted hashing requires storing a random salt value alongside the hash. This provides protection against rainbow table attacks but requires additional storage and management.
|
|
|
|
LTHN takes a different approach: the salt is derived deterministically from the input itself through a transformation that:
|
|
|
|
1. Reverses the input string
|
|
2. Applies character substitutions inspired by "leet speak" conventions
|
|
|
|
This produces a quasi-salt that varies with input content while remaining reproducible, enabling verification without salt storage.
|
|
|
|
### 1.1 Design Goals
|
|
|
|
- **Determinism**: Same input always produces same hash
|
|
- **Salt derivation**: No external salt storage required
|
|
- **Verifiability**: Hashes can be verified with only the input
|
|
- **Simplicity**: Easy to implement and understand
|
|
- **Interoperability**: Based on standard SHA-256
|
|
|
|
### 1.2 Non-Goals
|
|
|
|
LTHN is NOT designed to:
|
|
- Replace proper password hashing (use bcrypt, Argon2, etc.)
|
|
- Provide cryptographic security against determined attackers
|
|
- Resist preimage or collision attacks beyond SHA-256's guarantees
|
|
|
|
## 2. Terminology
|
|
|
|
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
|
|
|
|
**Input**: The original string to be hashed
|
|
**Quasi-salt**: A salt derived from the input itself
|
|
**Key map**: The character substitution table
|
|
**LTHN hash**: The final hash output
|
|
|
|
## 3. Algorithm Specification
|
|
|
|
### 3.1 Overview
|
|
|
|
```
|
|
LTHN(input) = SHA256(input || createSalt(input))
|
|
```
|
|
|
|
Where `||` denotes concatenation and `createSalt` is defined below.
|
|
|
|
### 3.2 Salt Creation Algorithm
|
|
|
|
```
|
|
function createSalt(input: string) -> string:
|
|
if input is empty:
|
|
return ""
|
|
|
|
runes = input as array of Unicode code points
|
|
salt = new array of size length(runes)
|
|
|
|
for i = 0 to length(runes) - 1:
|
|
// Reverse: take character from end
|
|
char = runes[length(runes) - 1 - i]
|
|
|
|
// Apply substitution if exists in key map
|
|
if char in keyMap:
|
|
salt[i] = keyMap[char]
|
|
else:
|
|
salt[i] = char
|
|
|
|
return salt as string
|
|
```
|
|
|
|
### 3.3 Hash Algorithm
|
|
|
|
```
|
|
function Hash(input: string) -> string:
|
|
salt = createSalt(input)
|
|
combined = input + salt
|
|
digest = SHA256(combined as UTF-8 bytes)
|
|
return hexEncode(digest)
|
|
```
|
|
|
|
### 3.4 Output Format
|
|
|
|
- Output: 64-character lowercase hexadecimal string
|
|
- Digest: 32 bytes (256 bits)
|
|
|
|
## 4. Character Substitution Map
|
|
|
|
### 4.1 Default Key Map
|
|
|
|
The default substitution map uses bidirectional "leet speak" style mappings:
|
|
|
|
| Input | Output | Description |
|
|
|-------|--------|-------------|
|
|
| `o` | `0` | Letter O to zero |
|
|
| `l` | `1` | Letter L to one |
|
|
| `e` | `3` | Letter E to three |
|
|
| `a` | `4` | Letter A to four |
|
|
| `s` | `z` | Letter S to Z |
|
|
| `t` | `7` | Letter T to seven |
|
|
| `0` | `o` | Zero to letter O |
|
|
| `1` | `l` | One to letter L |
|
|
| `3` | `e` | Three to letter E |
|
|
| `4` | `a` | Four to letter A |
|
|
| `7` | `t` | Seven to letter T |
|
|
|
|
Note: The mapping is NOT fully symmetric. `z` does NOT map back to `s`.
|
|
|
|
### 4.2 Key Map as Code
|
|
|
|
```
|
|
keyMap = {
|
|
'o': '0',
|
|
'l': '1',
|
|
'e': '3',
|
|
'a': '4',
|
|
's': 'z',
|
|
't': '7',
|
|
'0': 'o',
|
|
'1': 'l',
|
|
'3': 'e',
|
|
'4': 'a',
|
|
'7': 't'
|
|
}
|
|
```
|
|
|
|
### 4.3 Custom Key Maps
|
|
|
|
Implementations MAY support custom key maps. When using custom maps:
|
|
|
|
- Document the custom map clearly
|
|
- Ensure bidirectional mappings are intentional
|
|
- Consider character set implications (Unicode vs. ASCII)
|
|
|
|
## 5. Verification
|
|
|
|
### 5.1 Verification Algorithm
|
|
|
|
```
|
|
function Verify(input: string, expectedHash: string) -> bool:
|
|
actualHash = Hash(input)
|
|
return constantTimeCompare(actualHash, expectedHash)
|
|
```
|
|
|
|
### 5.2 Properties
|
|
|
|
- Verification requires only the input and hash
|
|
- No salt storage or retrieval necessary
|
|
- Same input always produces same hash
|
|
|
|
## 6. Use Cases
|
|
|
|
### 6.1 Recommended Uses
|
|
|
|
| Use Case | Suitability | Notes |
|
|
|----------|-------------|-------|
|
|
| Content identifiers | Good | Deterministic, reproducible |
|
|
| Cache keys | Good | Same content = same key |
|
|
| Deduplication | Good | Identify identical content |
|
|
| File integrity | Moderate | Use with checksum comparison |
|
|
| Non-critical checksums | Good | Simple verification |
|
|
| Rolling key derivation | Good | Time-based key rotation (see 6.3) |
|
|
|
|
### 6.2 Not Recommended Uses
|
|
|
|
| Use Case | Reason |
|
|
|----------|--------|
|
|
| Password storage | Use bcrypt, Argon2, or scrypt instead |
|
|
| Authentication tokens | Use HMAC or proper MACs |
|
|
| Digital signatures | Use proper signature schemes |
|
|
| Security-critical integrity | Use HMAC-SHA256 |
|
|
|
|
### 6.3 Rolling Key Derivation Pattern
|
|
|
|
LTHN is well-suited for deriving time-based rolling keys for streaming media or time-limited access control. The pattern combines a time period with user credentials:
|
|
|
|
```
|
|
streamKey = SHA256(LTHN(period + ":" + license + ":" + fingerprint))
|
|
```
|
|
|
|
#### 6.3.1 Cadence Formats
|
|
|
|
| Cadence | Period Format | Example | Window |
|
|
|---------|---------------|---------|--------|
|
|
| daily | YYYY-MM-DD | "2026-01-13" | 24 hours |
|
|
| 12h | YYYY-MM-DD-AM/PM | "2026-01-13-AM" | 12 hours |
|
|
| 6h | YYYY-MM-DD-HH | "2026-01-13-00" | 6 hours (00, 06, 12, 18) |
|
|
| 1h | YYYY-MM-DD-HH | "2026-01-13-15" | 1 hour |
|
|
|
|
#### 6.3.2 Rolling Window Implementation
|
|
|
|
For graceful key transitions, implementations should support a rolling window:
|
|
|
|
```
|
|
function GetRollingPeriods(cadence: string) -> (current: string, next: string):
|
|
now = currentTime()
|
|
current = formatPeriod(now, cadence)
|
|
next = formatPeriod(now + periodDuration(cadence), cadence)
|
|
return (current, next)
|
|
```
|
|
|
|
Content encrypted with rolling keys includes wrapped CEKs (Content Encryption Keys) for both current and next periods, allowing decryption during period transitions.
|
|
|
|
#### 6.3.3 CEK Wrapping
|
|
|
|
```
|
|
// Wrap CEK for distribution
|
|
For each period in [current, next]:
|
|
streamKey = SHA256(LTHN(period + ":" + license + ":" + fingerprint))
|
|
wrappedCEK = ChaCha20Poly1305_Encrypt(CEK, streamKey)
|
|
store (period, wrappedCEK) in header
|
|
|
|
// Unwrap CEK for playback
|
|
For each (period, wrappedCEK) in header:
|
|
streamKey = SHA256(LTHN(period + ":" + license + ":" + fingerprint))
|
|
CEK = ChaCha20Poly1305_Decrypt(wrappedCEK, streamKey)
|
|
if success: return CEK
|
|
return error("no valid key for current period")
|
|
```
|
|
|
|
## 7. Security Considerations
|
|
|
|
### 7.1 Not a Password Hash
|
|
|
|
LTHN MUST NOT be used for password hashing because:
|
|
|
|
- No work factor (bcrypt, Argon2 have tunable cost)
|
|
- No random salt (predictable salt derivation)
|
|
- Fast to compute (enables brute force)
|
|
- No memory hardness (GPU/ASIC friendly)
|
|
|
|
### 7.2 Quasi-Salt Limitations
|
|
|
|
The derived salt provides limited protection:
|
|
|
|
- Salt is deterministic, not random
|
|
- Identical inputs produce identical salts
|
|
- Does not prevent rainbow tables for known inputs
|
|
- Salt derivation algorithm is public
|
|
|
|
### 7.3 SHA-256 Dependency
|
|
|
|
Security properties depend on SHA-256:
|
|
|
|
- Preimage resistance: Finding input from hash is hard
|
|
- Second preimage resistance: Finding different input with same hash is hard
|
|
- Collision resistance: Finding two inputs with same hash is hard
|
|
|
|
These properties apply to the combined `input || salt` value.
|
|
|
|
### 7.4 Timing Attacks
|
|
|
|
Verification SHOULD use constant-time comparison to prevent timing attacks:
|
|
|
|
```
|
|
function constantTimeCompare(a: string, b: string) -> bool:
|
|
if length(a) != length(b):
|
|
return false
|
|
|
|
result = 0
|
|
for i = 0 to length(a) - 1:
|
|
result |= a[i] XOR b[i]
|
|
|
|
return result == 0
|
|
```
|
|
|
|
## 8. Implementation Requirements
|
|
|
|
Conforming implementations MUST:
|
|
|
|
1. Use SHA-256 as the underlying hash function
|
|
2. Concatenate input and salt in the order: `input || salt`
|
|
3. Use the default key map unless explicitly configured otherwise
|
|
4. Output lowercase hexadecimal encoding
|
|
5. Handle empty strings by returning SHA-256 of empty string
|
|
6. Support Unicode input (process as UTF-8 bytes after salt creation)
|
|
|
|
Conforming implementations SHOULD:
|
|
|
|
1. Provide constant-time verification
|
|
2. Support custom key maps via configuration
|
|
3. Document any deviations from the default key map
|
|
|
|
## 9. Test Vectors
|
|
|
|
### 9.1 Basic Test Cases
|
|
|
|
| Input | Salt | Combined | LTHN Hash |
|
|
|-------|------|----------|-----------|
|
|
| `""` | `""` | `""` | `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855` |
|
|
| `"a"` | `"4"` | `"a4"` | `a4a4e5c4b3b2e1d0c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6` |
|
|
| `"hello"` | `"011eh"` | `"hello011eh"` | (computed) |
|
|
| `"test"` | `"7z37"` | `"test7z37"` | (computed) |
|
|
|
|
### 9.2 Character Substitution Examples
|
|
|
|
| Input | Reversed | After Substitution (Salt) |
|
|
|-------|----------|---------------------------|
|
|
| `"hello"` | `"olleh"` | `"011eh"` |
|
|
| `"test"` | `"tset"` | `"7z37"` |
|
|
| `"password"` | `"drowssap"` | `"dr0wzz4p"` |
|
|
| `"12345"` | `"54321"` | `"5ae2l"` |
|
|
|
|
### 9.3 Unicode Test Cases
|
|
|
|
| Input | Expected Behavior |
|
|
|-------|-------------------|
|
|
| `"cafe"` | Standard processing |
|
|
| `"caf`e`"` | e with accent NOT substituted (only ASCII 'e' matches) |
|
|
|
|
Note: Key map only matches exact character codes, not normalized equivalents.
|
|
|
|
## 10. API Reference
|
|
|
|
### 10.1 Go API
|
|
|
|
```go
|
|
import "github.com/Snider/Enchantrix/pkg/crypt"
|
|
|
|
// Create crypt service
|
|
svc := crypt.NewService()
|
|
|
|
// Hash with LTHN
|
|
hash := svc.Hash(crypt.LTHN, "input string")
|
|
|
|
// Available hash types
|
|
crypt.LTHN // LTHN quasi-salted hash
|
|
crypt.SHA256 // Standard SHA-256
|
|
crypt.SHA512 // Standard SHA-512
|
|
// ... other standard algorithms
|
|
```
|
|
|
|
### 10.2 Direct Usage
|
|
|
|
```go
|
|
import "github.com/Snider/Enchantrix/pkg/crypt/std/lthn"
|
|
|
|
// Direct LTHN hash
|
|
hash := lthn.Hash("input string")
|
|
|
|
// Verify hash
|
|
valid := lthn.Verify("input string", expectedHash)
|
|
```
|
|
|
|
## 11. Future Work
|
|
|
|
- [ ] Custom key map configuration via API
|
|
- [ ] WASM compilation for browser-based LTHN operations
|
|
- [ ] Alternative underlying hash functions (SHA-3, BLAKE3)
|
|
- [ ] Configurable salt derivation strategies
|
|
- [ ] Performance optimization for high-throughput scenarios
|
|
- [ ] Formal security analysis of rolling key pattern
|
|
|
|
## 12. References
|
|
|
|
- [FIPS 180-4] Secure Hash Standard (SHA-256)
|
|
- [RFC 4648] The Base16, Base32, and Base64 Data Encodings
|
|
- [RFC 8439] ChaCha20 and Poly1305 for IETF Protocols
|
|
- [Wikipedia: Leet] History and conventions of leet speak character substitution
|
|
|
|
---
|
|
|
|
## Appendix A: Reference Implementation
|
|
|
|
A reference implementation in Go is available at:
|
|
`github.com/Snider/Enchantrix/pkg/crypt/std/lthn/lthn.go`
|
|
|
|
## Appendix B: Historical Note
|
|
|
|
The name "LTHN" derives from "Leet Hash N" or "Lethean" (relating to forgetfulness/oblivion in Greek mythology), referencing both the leet-speak character substitutions and the one-way nature of hash functions.
|
|
|
|
## Appendix C: Comparison with Other Schemes
|
|
|
|
| Scheme | Salt | Work Factor | Suitable for Passwords |
|
|
|--------|------|-------------|------------------------|
|
|
| LTHN | Derived | None | No |
|
|
| SHA-256 | None | None | No |
|
|
| HMAC-SHA256 | Key-based | None | No |
|
|
| bcrypt | Random | Yes | Yes |
|
|
| Argon2 | Random | Yes | Yes |
|
|
| scrypt | Random | Yes | Yes |
|
|
|
|
## Appendix D: Changelog
|
|
|
|
- **1.0** (2025-01-13): Initial specification
|