Dennis Rodewyk 5 năm trước cách đây
mục cha
commit
b618f025bd
6 tập tin đã thay đổi với 232 bổ sung67 xóa
  1. 1 0
      .gitignore
  2. 1 1
      Dockerfile
  3. 40 54
      README.md
  4. 20 6
      hugo-encrypt.go
  5. 6 6
      i18n/en-us.toml
  6. 164 0
      shortcodes/hugo-encrypt.html

+ 1 - 0
.gitignore

@@ -6,3 +6,4 @@
 /themes/
 /config.toml
 hugo.exe
+hugo-encrypt

+ 1 - 1
Dockerfile

@@ -2,4 +2,4 @@ FROM golang
 
 WORKDIR /data
 
-RUN go get github.com/chaosbunker/hugo-encrypt
+RUN go get code.chaosbunker.com/rootless/hugo-encrypt

+ 40 - 54
README.md

@@ -1,123 +1,109 @@
 # Hugo Encrypt
 
-**Hugo-Encrypt** is a golang port of [Hugo Encryptor](https://github.com/Li4n0/hugo_encryptor)
-   
-**Hugo-Encrypt** is a tool to protect your [Hugo](https://gohugo.io) posts. It uses AES-256-GCM to encrypt the contents of your posts, and inserts a snippet of `<script>` code to verify whether the password is correct or not in readers' browser. Without a correct key, nobody can decrypt your private posts.
+`hugo-encrypt` is a golang port of [Hugo Encryptor](https://github.com/Li4n0/hugo_encryptor)
+
+`hugo-encrypt` is a tool that encrpyts content in your [Hugo](https://gohugo.io) posts. It uses AES-256-GCM to encrypt the contents of your posts, and inserts the necessary javascript code into the encrypted posts that decrypts the content after the correntc passphrase has been entered.
 
 ## Installation
 
-Environmental dependence: go 1.11+
+Environmental dependency: go 1.11+
 
-**Step 1: Install Hugo-Encrypt.**
+**Step 1: Install hugo-encrypt.**
 
-    $ go get github.com/Izumiko/hugo-encrypt
+		$ go get github.com/Izumiko/hugo-encrypt
 
-**Step 2: Place `hugo-encrypt` in the root directory of your blog, or add `hugo-encrypt` to `PATH`**
+**Step 2: Place `hugo-encrypt` in the root directory of your blog, or add `hugo-encrypt` to your `$PATH`**
 
-    $ cp hugo-encrypt /path/to/your/blog/
+		$ cp hugo-encrypt /path/to/your/blog/
 
-    or
+		or
 
-    $ export PATH=/path/of/hugo-encrypt/:$PATH
+		$ export PATH=/path/to/hugo-encrypt/:$PATH
 
 **Step 3: Place `shortcodes/hugo-encryptor.html` in the shortcode directory of your blog:**
 
-    $ mkdir -p /path/to/your/blog/layouts/shortcodes
-    $ cp shortcodes/hugo-encryptor.html /path/to/your/blog/layouts/shortcodes/hugo-encryptor.html
+		$ mkdir -p /path/to/your/blog/layouts/shortcodes
+		$ cp shortcodes/hugo-encryptor.html /path/to/your/blog/layouts/shortcodes/hugo-encryptor.html
 
 **Step 4: Merge i18n translation files and/or add your own language.**
 
-    $ cp -r i18n /path/to/your/blog/
+		$ cp -r i18n /path/to/your/blog/
 
 ## Usage
 
-**Step 1: Use `hugo-encryptor` tag surround the text you want to encrypt **
-
-**Attention! There must be some text and the `<!--more-->` tag before the hugo-encryptor:**
+**Step 1: Use the `hugo-encryptor` tag around the text you want to encrypt **
 
 ```markdown
 ---
 title: "This Is An Encrypted Post"
 ---
 
-**There must be some text, and the summary tag is also needed:**
-<!--more-->
-{{% hugo-encryptor "PASSWORD" %}}
+{{< hugo-encryptor .Site.Params.Password >}}
 
-# You cannot see me unless you've got the password!
+# You cannot see me unless you've got the passphrase!
 
 This is the content you want to encrypt!
 
-**Do remember to close the `hugo-encryptor` shortcodes tag:**
-
-{{% /hugo-encryptor %}}
+{{< /hugo-encryptor >}}
 ```
 
 **Step 2: Generate your site as usual**
 
 It may be something like:
 
-    $ hugo
+		$ hugo
 
-**Step 3: Get the encryption done!**
+**Step 3: Get the encryption done**
 
-    $ ./hugo-encrypt
+		$ ./hugo-encrypt
 
-    or (if added to PATH)
+		or (if added to PATH)
 
-    $ hugo-encrypt
+		$ hugo-encrypt
 
 
-Then all the private posts in your `public` directory would be encrypted thoroughly, congrats!
+Now all the private posts in your `public` directory are encrypted.
 
 ## Configuration
 
-Although the **Hugo-Encrypt** can run without any configure, we provide some settings params to help you configure **Hugo-Encrypt** to your liking.
-
+Although `hugo-encrypt` works right out of the box, there are a few things that can be customized to your liking.
+.
 ### Language
 
-**Hugo-Encrypt** uses i18n settings to display. You can change it by param below. Be sure to add the corresponding language file to the i18n folder.
+Use i18n to display content generated by `hugo-Encrypt` in the language of your choice by setting the following param in your config file. Make sure to set the Param `DefaultContentLanguage` and add the corresponding language file to the i18n folder.
 
 ```toml
 [params]
- 		 ......
-  DefaultContentLanguage = "zh-cn"
+	DefaultContentLanguage = "en-us"
 ```
 
-### The way of client password storage
+### Password storage
 
-As default,**Hugo-Encrypt** use `localStorage` to storage the password in client. By adding `hugoEncryptorStorage` param in your blog's config file, you can change the storage method into `sessionStorage`. Such as below:
+`hugo-encrypt` uses _localStorage_ by default. This means the passphrase is permanently stored in the browser. By adding the `hugoEncryptStorage` param in your blog's config file you can set the storage method to _sessionStorage_.
 
 ```toml
 [params]
- 		 ......
-  hugoEncryptorStorage = "session" # or "local"
+	hugoEncryptStorage = "session" # or "local"
 ```
 
-For the difference of two storage ways:
-
 * **localStorage**:
 
-  Once a reader input the correct password,the authorization status will not expire, the reader can read the article at any time without having to enter the password again. Unless you change the password or the reader clean his browser cache.
+	Once a visitor entered the correct passphrase the authorization status will not expire. The visitor can read the article until the passphrase has been changed or the browser cache is cleared.
 
 * **sessionStorage**:
 
-  If a reader input the correct password, he could read the article without having to enter the password again until the user close his browser.
-
-### Style
-
-As default, **Hugo-Encrypt** has no style,but we have already give all the visual element a class name, you can add style for them in your css files.
+	Once a visitor entered the correct passphrase the authorization will expire after the browser is closed.
 
-## Attentions
+## Attention
 
-* Do remember to keep the source code of your encrypted posts private. Never push your blog directory into a public repository.
+* Remember to keep the source files of your encrypted posts private. Never push your blog directory into a public repository as the encryption happens after generating the html files with `hugo`.
 
-* Every time when you generate your site, you should run `$ hugo-encrypt` again to encrypt the posts which you want to be protected. If you are worried about you will forgot that, it's a good idea to use a shell script to take the place of  `$ hugo` ,such as below:
+* Remember to run `hugo-encrypt` every time after generating your site with `hugo` to encrypt the posts you want to be passphrase-protected. You might want to use a shell script instead of `hugo` to take care of both generation and encryption of your html files:
 
-  ```bash
-  #!/bin/bash
-  hugo && hugo-encrypt
-  ```
+	```bash
+	#!/bin/bash
 
-  
+	hugo
+	hugo-encrypt
+	```
 

+ 20 - 6
hugo-encrypt.go

@@ -2,9 +2,11 @@ package main
 
 import (
 	"bytes"
+	"flag"
 	"crypto/aes"
 	"crypto/cipher"
 	"crypto/rand"
+	"crypto/sha1"
 	"crypto/sha256"
 	"encoding/hex"
 	"fmt"
@@ -54,7 +56,12 @@ func encryptPage(path string) {
 
 		password, _ := block.Attr("data-password")
 		blockhtml, _ := block.Html()
-		enchtml := encrypt(password, blockhtml)
+		data := []byte(blockhtml)
+		fmt.Printf("\nSHA1: % x\n", sha1.Sum(data))
+		sha1_sum := sha1.Sum(data)
+		sha1_string := hex.EncodeToString(sha1_sum[:])
+		finalcontent := ("<div id='beginProtectedContent'>" + sha1_string + "</div>\n" + blockhtml + "\n<div id='endProtectedContent'>" + sha1_string + "</div>")
+		enchtml := encrypt(password, finalcontent)
 		block.RemoveAttr("data-password")
 		block.SetHtml(enchtml)
 		wholehtml, _ := doc.Html()
@@ -62,12 +69,19 @@ func encryptPage(path string) {
 	}
 }
 
+
+
 func main() {
-	sitePath := os.Args[1]
-	if !sitePath {
-        sitePath = "public"
-    }
-	err := filepath.Walk(sitePath, func(path string, f os.FileInfo, err error) error {
+	sitePathPtr := flag.String("sitePath", "", "Relative or absolute path of the public directory generated by hugo")
+
+	flag.Parse()
+
+	if *sitePathPtr == "" {
+		flag.PrintDefaults()
+		os.Exit(1)
+	}
+
+	err := filepath.Walk(*sitePathPtr, func(path string, f os.FileInfo, err error) error {
 		if f == nil {
 			return err
 		}

+ 6 - 6
i18n/en-us.toml

@@ -1,11 +1,11 @@
 [protectedbypwd]
-other = "The following content is password protected."
+other = "The following content is protected."
 
-[inputpassword]
-other = "Please input the password"
+[inputpassphrase]
+other = "Please input the passphrase"
 
-[submit]
-other = "Submit"
+[decrypt]
+other = "Decrypt"
 
 [wrongpwd]
-other = "Wrong password"
+other = "Passphrase is incorrect"

+ 164 - 0
shortcodes/hugo-encrypt.html

@@ -0,0 +1,164 @@
+{{/*
+	## Hugo Encrypt
+	### Params:
+	- `password`:
+
+	require param
+	- Simple
+
+	{{< hugo-encrypt "your password" >}}
+		your content
+	{{< /hugo-encrypt >}}
+
+*/}}
+{{/* DEFAULTS */}}
+{{ $_hugo_config := `{ "version": 1 }` }}
+
+<hugo-encrypt>
+	{{ if .Get 0 }}
+		{{- $password := $.Scratch.Set "password" (.Get 0) -}}
+	{{ else if .Site.Params.Passwoird }}
+		{{- $password := $.Scratch.Set "password" .Site.Params.Password -}}
+	{{ else }}
+		{{- $password -}}
+	{{ end }}
+	<p>{{ i18n "protectedbypwd" }}</p>
+
+	<div class='hugo-encrypt-form'>
+		<input
+			class="hugo-encrypt-input"
+			id="hugo-encrypt-password"
+			placeholder='{{ i18n "inputpassword" }}'
+		/>
+		<input
+			class="hugo-encrypt-button"
+			type="button"
+			value='{{ i18n "decrypt" }}'
+			id="button" onclick="hugoDecrypt(document.getElementById('hugo-encrypt-password').value,'input')"
+		/>
+	</div>
+	<cipher-text data-password="{{ $password }}" style="display:none;">
+<!-- Do not indent the following two lines -->
+<p id="verifyText" style="display:none;">The quick brown fox jumps over the lazy dog</p>
+{{ .Inner }}
+	</cipher-text>
+	<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/1.9.0/showdown.min.js"></script>
+	<script>
+		let cipher = document.getElementsByTagName("cipher-text")[0];
+		const storageKey = location.pathname + "password";
+		const userStorage = {{ if .Site.Params.hugoEncryptStorage }} window['{{.Site.Params.hugoEncryptStorage}}Storage'] {{ else }} localStorage {{ end }};
+		/**
+		 * Encodes a utf8 string as a byte array.
+		 * @param {String} str
+		 * @returns {Uint8Array}
+		 */
+		function str2buf(str) {
+			return new TextEncoder("utf-8").encode(str);
+		}
+		/**
+		 * Decodes a byte array as a utf8 string.
+		 * @param {Uint8Array} buffer
+		 * @returns {String}
+		 */
+		function buf2str(buffer) {
+			return new TextDecoder("utf-8").decode(buffer);
+		}
+		/**
+		 * Decodes a string of hex to a byte array.
+		 * @param {String} hexStr
+		 * @returns {Uint8Array}
+		 */
+		function hex2buf(hexStr) {
+			return new Uint8Array(hexStr.match(/.{2}/g).map(h => parseInt(h, 16)));
+		}
+		/**
+		 * Given a passphrase, this generates a crypto key
+		 * using `PBKDF2` with SHA256 and 1000 iterations.
+		 * If no salt is given, a new one is generated.
+		 * The return value is an array of `[key, salt]`.
+		 * @param {String} passphrase
+		 * @param {UInt8Array} salt [salt=random bytes]
+		 * @returns {Promise<[CryptoKey,UInt8Array]>}
+		 */
+		function deriveKey(passphrase, salt) {
+			salt = salt || crypto.getRandomValues(new Uint8Array(8));
+			return crypto.subtle
+				.importKey("raw", str2buf(passphrase), "PBKDF2", false, ["deriveKey"])
+				.then(key =>
+					crypto.subtle.deriveKey(
+						{ name: "PBKDF2", salt, iterations: 1000, hash: "SHA-256" },
+						key,
+						{ name: "AES-GCM", length: 256 },
+						false,
+						["encrypt", "decrypt"],
+					),
+				)
+				.then(key => [key, salt]);
+		}
+		/**
+		 * Given a key and ciphertext (in the form of a string) as given by `encrypt`,
+		 * this decrypts the ciphertext and returns the original plaintext
+		 * @param {String} passphrase
+		 * @param {String} saltIvCipherHex
+		 * @returns {Promise<String>}
+		 */
+		function decrypt(passphrase, saltIvCipherHex) {
+			const [salt, iv, data] = saltIvCipherHex.split("-").map(hex2buf);
+			return deriveKey(passphrase, salt)
+				.then(([key]) => crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, data))
+				.then(v => buf2str(new Uint8Array(v)));
+		}
+		/**
+		 * The html entities in the decrypted text need to be decoded
+		 * before converting any markdown to html
+		 */
+		function htmlDecode(input){
+			var doc = new DOMParser().parseFromString(input, "text/html");
+			return doc.documentElement.textContent;
+		}
+		/**
+		 * Needed to convert markdown within the decrypted text to html
+		 */
+		function interpreteMarkdown(input) {
+			var converter = new showdown.Converter()
+			return converter.makeHtml(input)
+		}
+		/**
+		 * @name:hugoDecrypt
+		 * @description: judge the password ,and decrypt post
+		 * @param {String} password
+		 * @param {String} type
+		 */
+		const hugoDecrypt = function(password, type) {
+			try {
+				decrypt(password, cipher.innerText).then(function(res) {
+					if ( res.includes("The quick brown fox jumps over the lazy dog") ) {
+						cipher.parentElement.outerHTML = interpreteMarkdown(htmlDecode(res));
+						userStorage.setItem(storageKey, password);
+						document.getElementById("verifyText").outerHTML = "";
+					} else {
+						if (type === "input") {
+							alert('{{ i18n "wrongpwd" }}');
+						} else if (type === "storage") {
+							userStorage.removeItem(storageKey);
+						}
+					}
+				});
+			} catch (error) {
+				if (type === "input") {
+					alert('{{ i18n "wrongpwd" }}');
+				} else if (type === "storage") {
+					userStorage.removeItem(location.pathname + "password");
+				}
+			}
+		};
+	</script>
+	<script>
+		window.onload = () => {
+			if (userStorage.getItem(storageKey)) {
+				hugoDecrypt(userStorage.getItem(storageKey), "storage");
+			}
+		};
+	</script>
+</hugo-encrypt>
+