hugo-encrypt.html 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. {{/*
  2. ## Hugo Encrypt
  3. ### Params:
  4. - `password`:
  5. require param
  6. - Simple
  7. {{< hugo-encrypt "your password" >}}
  8. your content
  9. {{< /hugo-encrypt >}}
  10. */}}
  11. {{/* DEFAULTS */}}
  12. {{ $_hugo_config := `{ "version": 1 }` }}
  13. <hugo-encrypt>
  14. {{ if .Get 0 }}
  15. {{- $passphrase := $.Scratch.Set "passphrase" (.Get 0) -}}
  16. {{ else if .Site.Params.Password }}
  17. {{- $passphrase := $.Scratch.Set "passphrase" .Site.Params.Password -}}
  18. {{ else }}
  19. {{- $passphrase -}}
  20. {{ end }}
  21. <p>{{ i18n "protectedbypwd" }}</p>
  22. <div class='hugo-encrypt-form'>
  23. <input
  24. class="hugo-encrypt-input"
  25. id="hugo-encrypt-password"
  26. placeholder='{{ i18n "inputpassword" }}'
  27. />
  28. <input
  29. class="hugo-encrypt-button"
  30. type="button"
  31. value='{{ i18n "decrypt" }}'
  32. id="button" onclick="hugoDecrypt(document.getElementById('hugo-encrypt-password').value,'input')"
  33. />
  34. </div>
  35. <cipher-text data-password="{{ $.Scratch.Get "passphrase" }}" style="display:none;">
  36. <!-- Do not indent the following two lines -->
  37. {{ .Inner }}
  38. </cipher-text>
  39. <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/core.js"></script>
  40. <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/sha1.js"></script>
  41. <script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/1.9.0/showdown.min.js"></script>
  42. <script>
  43. let cipher = document.getElementsByTagName("cipher-text")[0];
  44. const storageKey = location.pathname + "password";
  45. const userStorage = {{ if .Site.Params.hugoEncryptStorage }} window['{{.Site.Params.hugoEncryptStorage}}Storage'] {{ else }} localStorage {{ end }};
  46. /**
  47. * Encodes a utf8 string as a byte array.
  48. * @param {String} str
  49. * @returns {Uint8Array}
  50. */
  51. function str2buf(str) {
  52. return new TextEncoder("utf-8").encode(str);
  53. }
  54. /**
  55. * Decodes a byte array as a utf8 string.
  56. * @param {Uint8Array} buffer
  57. * @returns {String}
  58. */
  59. function buf2str(buffer) {
  60. return new TextDecoder("utf-8").decode(buffer);
  61. }
  62. /**
  63. * Decodes a string of hex to a byte array.
  64. * @param {String} hexStr
  65. * @returns {Uint8Array}
  66. */
  67. function hex2buf(hexStr) {
  68. return new Uint8Array(hexStr.match(/.{2}/g).map(h => parseInt(h, 16)));
  69. }
  70. /**
  71. * Given a passphrase, this generates a crypto key
  72. * using `PBKDF2` with SHA256 and 1000 iterations.
  73. * If no salt is given, a new one is generated.
  74. * The return value is an array of `[key, salt]`.
  75. * @param {String} passphrase
  76. * @param {UInt8Array} salt [salt=random bytes]
  77. * @returns {Promise<[CryptoKey,UInt8Array]>}
  78. */
  79. function deriveKey(passphrase, salt) {
  80. salt = salt || crypto.getRandomValues(new Uint8Array(8));
  81. return crypto.subtle
  82. .importKey("raw", str2buf(passphrase), "PBKDF2", false, ["deriveKey"])
  83. .then(key =>
  84. crypto.subtle.deriveKey(
  85. { name: "PBKDF2", salt, iterations: 1000, hash: "SHA-256" },
  86. key,
  87. { name: "AES-GCM", length: 256 },
  88. false,
  89. ["encrypt", "decrypt"],
  90. ),
  91. )
  92. .then(key => [key, salt]);
  93. }
  94. /**
  95. * Given a key and ciphertext (in the form of a string) as given by `encrypt`,
  96. * this decrypts the ciphertext and returns the original plaintext
  97. * @param {String} passphrase
  98. * @param {String} saltIvCipherHex
  99. * @returns {Promise<String>}
  100. */
  101. function decrypt(passphrase, saltIvCipherHex) {
  102. const [salt, iv, data] = saltIvCipherHex.split("-").map(hex2buf);
  103. return deriveKey(passphrase, salt)
  104. .then(([key]) => crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, data))
  105. .then(v => buf2str(new Uint8Array(v)));
  106. }
  107. /**
  108. * Needed to convert markdown within the decrypted text to html
  109. */
  110. function interpreteMarkdown(input) {
  111. var converter = new showdown.Converter()
  112. return converter.makeHtml(input)
  113. }
  114. /**
  115. * @name:hugoDecrypt
  116. * @description: judge the password ,and decrypt post
  117. * @param {String} password
  118. * @param {String} type
  119. */
  120. const hugoDecrypt = function(password, type) {
  121. try {
  122. decrypt(password, cipher.innerText).then(function(res) {
  123. /**
  124. * calculate sha1 of decrypted text and check if it
  125. * matches the sha1 at the bottom of the decrypted text
  126. * to get the same hash that was added during encryption we
  127. * need to remove the last line
  128. */
  129. var hash = CryptoJS.SHA1(res.replace(/\r?\n?[^\r\n]*$/, ""));
  130. var result = CryptoJS.enc.Hex.stringify(hash);
  131. if ( res.includes(result) ) {
  132. cipher.parentElement.outerHTML = interpreteMarkdown(res);
  133. userStorage.setItem(storageKey, password);
  134. document.getElementById("sha1sum").innerHTML = "sha1: " + result;
  135. } else {
  136. if (type === "input") {
  137. alert('{{ i18n "wrongpwd" }}');
  138. } else if (type === "storage") {
  139. userStorage.removeItem(storageKey);
  140. }
  141. }
  142. });
  143. } catch (error) {
  144. if (type === "input") {
  145. alert('{{ i18n "wrongpwd" }}');
  146. } else if (type === "storage") {
  147. userStorage.removeItem(location.pathname + "password");
  148. }
  149. }
  150. };
  151. </script>
  152. <script>
  153. window.onload = () => {
  154. if (userStorage.getItem(storageKey)) {
  155. hugoDecrypt(userStorage.getItem(storageKey), "storage");
  156. }
  157. };
  158. </script>
  159. </hugo-encrypt>