Add new packages for cryptographic operations, session management, and I/O handling: - pkg/crypt/chachapoly: ChaCha20-Poly1305 AEAD encryption - pkg/crypt/lthn: Lethean-specific key derivation and encryption - pkg/crypt/rsa: RSA key generation, encryption, and signing - pkg/io/node: CryptoNote node I/O and protocol handling - pkg/io/sigil: Cryptographic sigil generation and verification - pkg/session: Session parsing, HTML rendering, search, and video - internal/cmd/forge: Forgejo auth status command - internal/cmd/session: Session management CLI command Also gitignore build artifacts (bugseti binary, i18n-validate). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
94 lines
2.9 KiB
Go
94 lines
2.9 KiB
Go
// Package lthn implements the LTHN quasi-salted hash algorithm (RFC-0004).
|
|
//
|
|
// LTHN produces deterministic, verifiable hashes without requiring separate salt
|
|
// storage. The salt is derived from the input itself through:
|
|
// 1. Reversing the input string
|
|
// 2. Applying "leet speak" style character substitutions
|
|
//
|
|
// The final hash is: SHA256(input || derived_salt)
|
|
//
|
|
// This is suitable for content identifiers, cache keys, and deduplication.
|
|
// NOT suitable for password hashing - use bcrypt, Argon2, or scrypt instead.
|
|
//
|
|
// Example:
|
|
//
|
|
// hash := lthn.Hash("hello")
|
|
// valid := lthn.Verify("hello", hash) // true
|
|
package lthn
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
)
|
|
|
|
// keyMap defines the character substitutions for quasi-salt derivation.
|
|
// These are inspired by "leet speak" conventions for letter-number substitution.
|
|
// The mapping is bidirectional for most characters but NOT fully symmetric.
|
|
var keyMap = map[rune]rune{
|
|
'o': '0', // letter O -> zero
|
|
'l': '1', // letter L -> one
|
|
'e': '3', // letter E -> three
|
|
'a': '4', // letter A -> four
|
|
's': 'z', // letter S -> Z
|
|
't': '7', // letter T -> seven
|
|
'0': 'o', // zero -> letter O
|
|
'1': 'l', // one -> letter L
|
|
'3': 'e', // three -> letter E
|
|
'4': 'a', // four -> letter A
|
|
'7': 't', // seven -> letter T
|
|
}
|
|
|
|
// SetKeyMap replaces the default character substitution map.
|
|
// Use this to customize the quasi-salt derivation for specific applications.
|
|
// Changes affect all subsequent Hash and Verify calls.
|
|
func SetKeyMap(newKeyMap map[rune]rune) {
|
|
keyMap = newKeyMap
|
|
}
|
|
|
|
// GetKeyMap returns the current character substitution map.
|
|
func GetKeyMap() map[rune]rune {
|
|
return keyMap
|
|
}
|
|
|
|
// Hash computes the LTHN hash of the input string.
|
|
//
|
|
// The algorithm:
|
|
// 1. Derive a quasi-salt by reversing the input and applying character substitutions
|
|
// 2. Concatenate: input + salt
|
|
// 3. Compute SHA-256 of the concatenated string
|
|
// 4. Return the hex-encoded digest (64 characters, lowercase)
|
|
//
|
|
// The same input always produces the same hash, enabling verification
|
|
// without storing a separate salt value.
|
|
func Hash(input string) string {
|
|
salt := createSalt(input)
|
|
hash := sha256.Sum256([]byte(input + salt))
|
|
return hex.EncodeToString(hash[:])
|
|
}
|
|
|
|
// createSalt derives a quasi-salt by reversing the input and applying substitutions.
|
|
// For example: "hello" -> reversed "olleh" -> substituted "011eh"
|
|
func createSalt(input string) string {
|
|
if input == "" {
|
|
return ""
|
|
}
|
|
runes := []rune(input)
|
|
salt := make([]rune, len(runes))
|
|
for i := 0; i < len(runes); i++ {
|
|
char := runes[len(runes)-1-i]
|
|
if replacement, ok := keyMap[char]; ok {
|
|
salt[i] = replacement
|
|
} else {
|
|
salt[i] = char
|
|
}
|
|
}
|
|
return string(salt)
|
|
}
|
|
|
|
// Verify checks if an input string produces the given hash.
|
|
// Returns true if Hash(input) equals the provided hash value.
|
|
// Uses direct string comparison - for security-critical applications,
|
|
// consider using constant-time comparison.
|
|
func Verify(input string, hash string) bool {
|
|
return Hash(input) == hash
|
|
}
|