Alright, let’s talk about making digital fingerprints and passing secret notes. In the world of Hugo, that’s the job of the crypto and encoding functions: md5, sha256, base64Encode, and base64Decode. These are your go-to tools for creating checksums, obscuring data in URLs, or working with basic encoding. They’re not for encrypting your secret diary—that’s a different conversation—but they are incredibly useful for the day-to-day grunt work of building a site.

Why You’d Use These (And Why You Wouldn’t)

First, let’s be clear: md5 and sha256 are hashing functions, not encryption. Encryption is a two-way street; you encrypt something to later decrypt it. Hashing is a one-way trip to Brownsville. You convert an input (a string, file content, etc.) into a fixed-size string of characters, a “digest” or “hash.” The primary superpowers here are:

  1. Consistency: The same input will always produce the exact same hash. Every. Single. Time.
  2. Uniqueness: A tiny change in the input (even one bit) produces a drastically different hash. This is called the “avalanche effect.”
  3. Improbability: It’s computationally infeasible to take a hash and reverse-engineer the original input or to find two different inputs that produce the same hash (a “collision”).

You use these for things like verifying data integrity, creating unique keys for cached content, or generating ETags for HTTP headers. You do not use them for storing passwords. For that, you need deliberately slow, salted algorithms like bcrypt, which Hugo doesn’t provide (and shouldn’t—that’s an application-level concern). If you’re doing that in your template, you’re doing it very, very wrong.

md5 is fast and was once the standard, but cryptographic weaknesses have made it susceptible to collisions. sha256 is part of the SHA-2 family and is currently considered secure for most non-password purposes. For Hugo’s use cases, sha256 is generally the safer bet.

Hashing Strings and Files

The syntax is beautifully simple. You feed them a string, they give you back a hex-encoded hash.

{{ $myString := "The quick brown fox jumps over the lazy dog." }}
<p>MD5: {{ md5 $myString }}</p>
<p>SHA256: {{ sha256 $myString }}</p>

This will output:

<p>MD5: e4d909c290d0fb1ca068ffaddf22cbd0</p>
<p>SHA256: ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c</p>

Where this gets genuinely useful is hashing file contents. Why would you do this? To create a unique cache-busting fingerprint for your CSS or JS files. When you change the file, the hash changes, which forces browsers to download the new version instead of serving the old one from cache.

{{ with resources.Get "css/main.css" }}
  {{ $styles := . | minify | fingerprint "sha256" }}
  <link rel="stylesheet" href="{{ $styles.RelPermalink }}" integrity="{{ $styles.Data.Integrity }}">
{{ end }}

The fingerprint resource method uses sha256 under the hood for the integrity attribute. It’s the same concept, just packaged nicely.

The Base64 Tango: Encoding and Decoding

Base64 is not a hash; it’s an encoding scheme. It takes binary data and translates it into a text format (specifically, ASCII) so it can be safely sent over protocols that expect text, like email (MIME) or embedded in HTML, CSS, or URLs without mangling.

Imagine you want to inline a small SVG directly in your CSS to save an HTTP request. You can’t just plop binary data in there. You have to encode it.

{{ $svgData := "data:image/svg+xml;base64," }}
{{ $svgContent := "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'><circle cx='5' cy='5' r='4' fill='red'/></svg>" }}
{{ $encodedSVG := $svgContent | base64Encode }}

<style>
  .icon {
    background-image: url('{{ $svgData }}{{ $encodedSVG }}');
  }
</style>

Decoding is its inverse operation. You take a base64 string and get the original data back. This is crucial for, say, reading a value passed from an API that was encoded for transport.

{{ $encodedSecret := "SGVsbG8sIFdvcmxkIQ==" }} <!-- "Hello, World!" encoded -->
{{ $decodedSecret := $encodedSecret | base64Decode }}

<p>The secret message is: {{ $decodedSecret }}</p> <!-- Outputs: Hello, World! -->

Common Pitfalls and “Oh, C’mon” Moments

  1. Encoding the Wrong Thing: A classic head-scratcher. base64Encode expects a string. If you have a file resource, you need to get its content with .Content.

    <!-- WRONG -->
    {{ $pic := resources.Get "images/logo.jpg" }}
    {{ $pic | base64Encode }} <!-- This will try to encode the *Resource* object, not the image data -->
    
    <!-- RIGHT -->
    {{ $pic := resources.Get "images/logo.jpg" }}
    {{ $pic.Content | base64Encode }} <!-- This encodes the actual binary data of the image -->
    
  2. Expecting Magic: These functions are dumb in the best way. They don’t guess character encoding for you. If you have a string with special characters, you are responsible for ensuring it’s represented correctly before hashing or encoding. Hugo templates are UTF-8, so just be consistent.

  3. URL Woes: A base64-encoded string can contain +, /, and = characters, which are not URL-safe. If you’re putting this in a URL, you must use the urlquery function afterwards to escape them properly. This is a common “why isn’t this working?!” moment.

    {{ $unsafeString := "Hello World + Goodbye/" | base64Encode }} <!-- Contains '+' and '/' -->
    {{ $safeString := $unsafeString | urlquery }} <!-- Converts to "SGVsbG8gV29ybGQgKyBHb29kYnllLw%3D%3D" -->
    
    <a href="/download?data={{ $safeString }}">Download</a>
    

So there you have it. They’re simple, sharp tools. Use sha256 for fingerprints and integrity checks, use base64 to smuggle binary data into text-based environments, and always remember to URL-escape your encoded strings. Now go build something.