Hey there! Ready to dive into the world of hashing with Go? Whether you’re building a secure application or just trying to speed up your data structures, you’re in the right place. Let’s break down everything you need to know about using hash functions in Go, with plenty of real-world examples you can start using today.
What’s Hashing All About in Go?
Before we jump into the code, let’s get something straight: Go gives us two main types of hash functions to play with. Think of them as two different tools in your toolbox:
- Non-cryptographic hashes: These are your speed demons (like FNV)
- Cryptographic hashes: These are your security guards (like SHA-256)
Let me show you what I mean.
The Speed Demon: Non-Cryptographic Hashes
When you just need something fast and don’t care about security (like for an internal cache), FNV is your friend. Here’s how you’d use it:
package main
import (
"fmt"
"hash/fnv"
)
func getFNVHash(s string) uint64 {
h := fnv.New64a()
h.Write([]byte(s))
return h.Sum64()
}
func main() {
text := "hey there!"
hash := getFNVHash(text)
fmt.Printf("Here's your FNV hash: %dn", hash)
}
Pretty straightforward, right? But here’s the thing: never use this for passwords or anything security-related. It’s like using a rubber lock – quick to use, but not very secure!
The Security Guard: Cryptographic Hashes
Now, when you need the real deal – like handling user passwords or verifying file integrity – you’ll want to use cryptographic hashes. Here’s how you use SHA-256, one of the most popular choices:
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
)
func getSHA256Hash(data string) string {
hasher := sha256.New()
hasher.Write([]byte(data))
return hex.EncodeToString(hasher.Sum(nil))
}
func main() {
input := "my super secret data"
hash := getSHA256Hash(input)
fmt.Printf("Your secure hash: %sn", hash)
}
Real-World Stuff You Can Do With Hashing
Let’s look at some actual problems you might face and how to solve them with hashing.
Checking if Files Have Been Tampered With
Ever needed to verify if a file has been modified? Here’s a practical way to do it:
package main
import (
"crypto/sha256"
"fmt"
"io"
"os"
)
func getFileHash(filePath string) (string, error) {
file, err := os.Open(filePath)
if err != nil {
return "", fmt.Errorf("couldn't open file: %v", err)
}
defer file.Close()
hasher := sha256.New()
if _, err := io.Copy(hasher, file); err != nil {
return "", fmt.Errorf("error hashing file: %v", err)
}
return hex.EncodeToString(hasher.Sum(nil)), nil
}
Pro tip: For big files, you’ll want to read them in chunks. Here’s how:
func getFileHashBuffered(filePath string) (string, error) {
file, err := os.Open(filePath)
if err != nil {
return "", fmt.Errorf("couldn't open file: %v", err)
}
defer file.Close()
hasher := sha256.New()
buf := make([]byte, 1024*1024) // Reading in 1MB chunks
for {
n, err := file.Read(buf)
if n > 0 {
hasher.Write(buf[:n])
}
if err == io.EOF {
break
}
if err != nil {
return "", fmt.Errorf("error reading file: %v", err)
}
}
return hex.EncodeToString(hasher.Sum(nil)), nil
}
Storing Passwords Safely
Let’s talk about password hashing. Please, please don’t roll your own solution here! Use bcrypt – it’s designed specifically for this:
package main
import (
"golang.org/x/crypto/bcrypt"
"log"
)
func hashPassword(password string) (string, error) {
// The cost parameter controls how slow the hash is
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
}
func checkPassword(password, hashedPassword string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
return err == nil
}
func main() {
password := "mySecretPassword123"
hashedPassword, err := hashPassword(password)
if err != nil {
log.Fatal("Failed to hash password:", err)
}
// , when checking the password...
isCorrect := checkPassword(password, hashedPassword)
fmt.Printf("Password correct? %vn", isCorrect)
}
Building a Simple Cache
Need to cache some data? Here’s a thread-safe cache implementation using hashing:
package main
import (
"encoding/json"
"hash/fnv"
"sync"
)
type Cache struct {
mu sync.RWMutex
items map[uint64]interface{}
}
func NewCache() *Cache {
return &Cache{
items: make(map[uint64]interface{}),
}
}
func (c *Cache) getHash(key interface{}) (uint64, error) {
bytes, err := json.Marshal(key)
if err != nil {
return 0, err
}
h := fnv.New64a()
h.Write(bytes)
return h.Sum64(), nil
}
func (c *Cache) Get(key interface{}) (interface{}, bool) {
hash, err := c.getHash(key)
if err != nil {
return nil, false
}
c.mu.RLock()
defer c.mu.RUnlock()
value, exists := c.items[hash]
return value, exists
}
Quick Tips to Remember
-
Speed vs Security: If you’re hashing for security (passwords, file verification, etc.), always use cryptographic hashes. If you just need a quick hash for an internal data structure, FNV is fine.
-
Handle Your Errors: Always check for errors when hashing, especially with files. You don’t want to assume a hash was successful when it wasn’t.
-
Buffer Large Data: When hashing big files or data streams, read them in chunks. Your memory will thank you.
Wrapping Up
There you have it – a practical guide to using hashing in Go! Remember, hashing is like choosing the right tool for the job. Need speed? Go with FNV. Need security? Stick with crypto packages. And when in doubt about password hashing, bcrypt is your best friend.
Got questions about implementing any of these patterns? Feel free to experiment with the code examples – they’re all ready to use. Happy hashing! 🚀
Want to Learn More?
- Check out the official Go docs for the
hash
andcrypto
packages - Look into other hashing algorithms like BLAKE2b and SHA-3
- Explore the
golang.org/x/crypto
package for more cryptographic tools
Keep coding, and remember: with great hashing power comes great responsibility! 😉