A method to encrypt content using hugo shortcodes without requiring additional scripts.

有一些內容開始覺得直接放出來會不太好 (指的是蠢事,不是危險事)。

花了許多時間在想辦法不靠額外的方式去加密,因為 hugo 本身有許多限制 (沒有 AES、沒有 bitwise operation) 所以有好幾次想放棄,但後來還是透過一些方法達成。雖然不是絕對安全,但是應該是夠用了。爆破的了的話,就只好讓你看看那些蠢事了。

Disclaimer: The techniques used here are not truly secure encryption methods and do not guarantee resistance to decryption. Please use them according to the situation.

For a truly secure solution using AES-256, please refer to Hugo Encryptor.

Usage

{{< encrypt 99 >}}
**secret** content
中文字
![圖片](https://lh3.googleusercontent.com/d/1tzc4vurlEWtmYHlzTUWsDo4ySM_GdVI_)
{{< /encrypt >}}

Some things are hidden...

The secret content is encrypted and stored in a hidden element, as shown in the screenshot below.

ciphertext

ciphertext

OuO

  • Do not share your source of the hugo blog
  • The password is recommended to range from 0 to 281474976710656. Techniques are applied to make decryption without the password more difficult. If you have suggestions for improvements, please let me know.

Install or setup

  • Use my costom theme: hugOuO
  • or plug below codes in your blog

Encrypt (backend)

We utilize Hugo’s shortcode system to create a template for content encryption, allowing users to define hidden sections with {{< encrypt 99 >}} and {{< /encrypt >}}, where 99 is the password.

  • themes\hugOuO\layouts\shortcodes\encrypt.html:
{{- $password := 0 -}}
{{- $msg := "The %q shortcode requires a password parameter: see %s" }}
{{ if isset .Params 0 }}
{{- $password = index .Params 0 -}}
{{- else -}}
{{- errorf $msg .Name .Position }}
{{- end -}}
{{- $chars := split (.Inner | markdownify) "" -}}
{{- $unique_id := (printf "%d" now.UnixNano) -}}
{{- $step := (int (math.Floor (div 281474976710656 (len $chars)))) -}}

<div class="encrypt-wrapper">
  <div class="input-wrapper">
    <input id={{ printf "password-%s" $unique_id }} type="text" placeholder="Password">
    <button class="toggle-password" style="cursor:pointer;" onclick="decrypt({{ $unique_id }})">
      Decrypt
    </button>
    <button class="toggle-password" style="cursor:pointer;" onclick="cleanDecrypt({{ $unique_id }})">
      Clean
    </button>
  </div>
  <div id={{ printf "encrypt-wrapper-%s" $unique_id }} style="display: none;">
    {{- range $char := $chars -}}
    {{- $x := (add (int (printf "0x%x" .)) (int $password)) -}}
    {{- $password = (mod (add $password $step) 281474976710656)}}
    {{- $y := replace (string (printf "%018U" $x)) "U+" "" -}}
    {{- $ry := delimit (collections.Reverse (split $y "")) "" -}}
    {{- $ey := (string $ry) | base64Encode -}}
    {{- printf "%s-" $ey -}}
    {{- end -}}
  </div>
  <div id={{ printf "decrypt-result-%s" $unique_id }}>
    <p>Some things are hidden...</p>
  </div>
</div>
{{/* tool: https://apps.timwhitlock.info/unicode/inspect */}}

Decrypt (frontend)

We use JavaScript to decrypt ciphertext.

  • add following content in themes\hugOuO\static\js\hugouo.js:
function rhexToBytes(hex) {
  const bytes = new Uint8Array(hex.length / 2);
  for (let i = 0; i !== bytes.length; i++) {
    bytes[i] = parseInt(hex.substring((bytes.length - i) * 2, (bytes.length - i) * 2 - 2).split("").reverse().join(""), 16);
  }
  return bytes;
}
function byteArrayToInt(byteArray) {
  let value = 0;
  for (var i = 0; i < byteArray.length; i++) {
    value = (value * 256) + byteArray[i];
  }

  return value;
}
function intToBytes(input, length) {
  const bytes = new Uint8Array(length);
  for (let i = length - 1; i >= 0; i--) {
    let byte = input & 0xff;
    bytes[i] = byte;
    input = (input - byte) / 256;
  }
  return bytes;
}
function removeLeadingZeroes(bytes) {
  let i = 0;
  while (i < bytes.length && bytes[i] === 0) {
    i++;
  }
  return bytes.slice(i);
}
function decrypt(id) {
  let success = true;
  const password_str = document.querySelector("#password-" + id).value
  if (password_str.trim() === "") {
    success = false;
  }

  let password = parseInt(password_str);
  const encodedString = document.querySelector("#encrypt-wrapper-" + id).innerText;
  const list = encodedString.split('-');
  const step = Math.floor(281474976710656 / (list.length - 1));
  let decryptResult = [];

  for (let i = 0; i < list.length; i++) {
    xb = rhexToBytes(Base64.decode(list[i]));
    x = byteArrayToInt(xb) - password;
    password = (password + step) % 281474976710656;
    dxb = intToBytes(x, xb.length);
    try {
      decryptResult.push(new TextDecoder("utf8", { fatal: true }).decode(removeLeadingZeroes(dxb)));
    } catch (e) {
      success = false;
      if (e instanceof TypeError) {
      } else {
        console.error(e);
      }
    }
  }
  if (success) {
    document.querySelector("#decrypt-result-" + id).innerHTML = '<p>' + decryptResult.join('') + '</p>';
  } else {
    document.querySelector("#decrypt-result-" + id).innerHTML = '<p>Wrong password!</p>';
  }
}
function cleanDecrypt(id) {
  document.querySelector("#decrypt-result-" + id).innerHTML = '<p>Some things are hidden...</p>';
  document.querySelector("#password-" + id).value = '';
}
  • ⊛ Back to top
  • ⊛ Go to bottom