Package: mkdocs-encryptcontent-plugin
mkdocs-encryptcontent-pluginThis plugin allows you to have password protected articles and pages in MKdocs.The content is encrypted with AES-256 in Python using PyCryptodome and decrypted in the browser with Crypto-JS or Webcrypto.It has been tested in Python Python 3.8+UsecaseI want to be able to protect the content of the page with a password.Define a password to protect each page independently or a global password to protect them all.If a global password exists, all articles and pages are protected with this password.If a password is defined in an article or a page, it is always used even if there is a global password.If a password is defined as an empty character string, content encryption is disabled.Additionally password levels can be defined in mkdocs.yml or external yaml file with user/password credentials.New features (compared to version 2.5.x)Stronger cryptography (PBKDF2 for key derivation)Faster cryptography (Webcrypto as alternative to crypto-js)Allow password inventory or levels in mkdocs.yml or external file for credential handlingAllow user name + password as credentialsIf credential is reused in different levels it also decrypts all of their contentOptional adding of credentials to URLs for sharingOptional tamper check by signing generated files with Ed25519NewDocumentationandTest benchUpgrading from version 2.5.xTheuse_secretfunctionality was discontinued in the plugin configuration and as a meta tag.In order to use environment variables in user names or passwords, use thespecial yaml tag!ENV.Todos for 3.1.xoptional server side keystore (allows throtteling)...to be definedTable of ContentsInstallationUsageGlobal password protectionPassword inventorySecret from environmentDefault vars customizationTranslationsObfuscate pagesExample plugin configurationFeaturesOverride default templatesAdd buttonTag encrypted pageRemember passwordShare link generationStorage of additional variables in keystoreModify generated pagesEncrypt somethingInject decrypt-form.tpl to themeMix encrypted and normal contentSearch encryptionSearch index encryptionSearch index encryption for mkdocs-materialJavascript extensionsReload user-defined scriptsHighlightJS supportArithmatex supportMermaid.js supportSecurityCrypto-js, crypto-es or webcrypto?File name obfuscationSigning of generated filesInstallationInstall the package with pip:pipinstallmkdocs-encryptcontent-pluginInstall the package from source with pip:cdmkdocs-encryptcontent-plugin/
pythonsetup.pysdistbdist_wheel
pipinstall--force-reinstall--no-depsdist/mkdocs_encryptcontent_plugin-3.0.3-py3-none-any.whlEnable the plugin in yourmkdocs.yml:plugins:-search:{}-encryptcontent:{}NOTE:If you have nopluginsentry in your configuration file yet, you'll likely also want to add thesearchplugin.
MkDocs enables it by default if there is nopluginsentry set, but now you have to enable it explicitly.UsageAdd an meta tagpassword: secret passwordin your markdown files to protect them.Alternatively add the meta taglevel: secretto use one or more secrets defined at the
plugin'spassword_inventoryorpassword_filein your "mkdocs.yml".Global password protectionAddglobal_password: your_passwordin plugin configuration variable, to protect all pages with this password by defaultplugins:-encryptcontent:global_password:'your_password'If the password meta tag is defined in a markdown file, it willALWAYSoverride the global password.NOTEKeep in mind that if thepassword:tag exists without value in a page, it willnot be protected!
Use this todisableglobal_passwordon specific pages.Password inventoryWith thepassword_inventoryyou can define protection levels (assigned with the meta taglevelin markdown files).plugins:-encryptcontent:password_inventory:classified:'password1'confidential:-'password2'-'password3'secret:user4:'password4'user5:'password5'These levels may be only one password (f.ex. classified), a list of multiple passwords (f.ex. confidential)
or multiple username/password pairs (f.ex. secret).
It is possible to reuse credentials at different levels.Note that a "list of multiple passwords" comes with a downside: All entries may be tested because unlike "user/password pairs"
there is no hint to determine the distinct entry to try
(At least I found no hint that wouldn't make it easier for a brute force attacker).
This means, that a highkdf_powcould cause long waiting time even if the right password was entered.The plugin will generate one secret key for each level, which is then used to encrypt the assigned sites.Password inventory in external fileYou can define password levels in an external yaml file and link it withpassword_file.
The intention is to separate sensitive information from configuration options.plugins:-encryptcontent:password_file:'passwords.yml'passwords.yml:classified:'password1'confidential:-'password2'-'password3'secret:user4:'password4'user5:'password5'Global password(s) in inventoryYou can add the special level_global, which will be applied globally on all sites like this:plugins:-encryptcontent:password_inventory:_global:'eitherdefineonepassword'_global:-'ordefine'-'multiplepasswords'_global:user1:'oruseuser'user2:'andpasswordpairs'NOTEAdd the meta taglevel:(without a value) to pages which should be excluded from global password level.
Also note that it is always possible to set the page to a different level than the global one with thelevelmeta tag.Secret from environmentIt is possible to read values from environment variable
(as discribedhere).
This replaces the deprecateduse_secretoption from previous versions.plugins:-encryptcontent:password_inventory:secret:user1:!ENVPASSWORD1_FROM_ENVuser2:!ENV[PASSWORD2_FROM_ENV,'PasswordifPASSWORD2_FROM_ENVundefinedorempty']user3:!ENV[PASSWORD3_FROM_ENV,FALLBACK_PASSWORD3_FROM_ENV,'PasswordifneitherPASSWORD3_FROM_ENVnorFALLBACK_PASSWORD3_FROM_ENVdefined']Default vars customizationOptionally you can use some extra variables in plugin configuration to customize default strings.plugins:-encryptcontent:title_prefix:'[LOCK]'summary:'anotherinformationalmessagetoencryptedcontent'placeholder:'anotherpasswordplaceholder'decryption_failure_message:'anotherinformationalmessagewhendecryptionfails'encryption_info_message:"anotherinformationmessagewhenyoudon'thaveaccess!"input_class:'md-search__formmd-search__input'button_class:'md-search__icon'Default prefix title is[Protected].Default summary message isThis content is protected with AES encryption..Default password palceholder isProvide password and press ENTER.Default decryption failure message isInvalid password..Defaut encryption information message isContact your administrator for access to this page..NOTEAdding a prefix to the title does not change the default navigation path !TranslationsIf the plugin is used in conjunction with thestatic-i18nplugin you can providetranslationsfor the usedi18n_page_locale.-encryptcontent:#...translations:de:title_prefix:'[Verschlüsselt]'summary:'DerInhaltdieserSeiteistmitAESverschlüsselt.'placeholder:'MitStrg+EnterwirddasPasswortglobalgesetzt'password_button_text:'Entschlüsseln'decryption_failure_message:'Falschespasswort.'encryption_info_message:'BittewendenSiesichandenSystemadministratorumaufdieseSeitezuzugreifen.'Custom per-page stringsYou can set the  meta tagencryption_summaryto customizesummaryandencryption_info_messageon every page.Obfuscate pagesIf you want to make it harder for search engines to scrape you pages content,
you can setobfuscate: SomeNotSoSecretPasswordmeta tag in markdown.The page then will displaysummaryandencryption_info_messagetogether with a button labeled withpassword_button_text. In order to view the pages content one hast to press the button first.If apasswordorlevelis set, then theobfuscatefeature will be disabled.
If you want to useobfuscatein a configuration whereglobal_passwordor_globallevel is defined,
you'll need to set thepassword:or ratherlevel:meta tag (with no password/level defined) to undefine the password on this page.The keys to all obfuscated pages are also saved in every keystore, so they are decrypted if someone entered
correct credentials.Example plugin configurationplugins:-encryptcontent:title_prefix:''summary:''placeholder:'Password'placeholder_user:Userpassword_button_text:'ENTER'decryption_failure_message:'Wrongusernameorpassword.'encryption_info_message:'Legitimationrequired.'translations:de:title_prefix:''summary:''placeholder:'Passwort'placeholder_user:Benutzerpassword_button_text:'ENTER'decryption_failure_message:'FalscherBenutzeroderPasswort.'encryption_info_message:'Legitimationerforderlich.'html_template_path:"decrypt-form.tpl.html"# override default html templatepassword_button:Trueinput_class:'w3-input'# CSS class used for input username and passwordbutton_class:'w3-buttonw3-theme-l1w3-hover-theme'# CSS class for password_buttonhljs:Falsearithmatex:Falsemermaid2:Falseremember_keys:true# keys from keystore will temporarily saved to sessionStorageremember_password:false# the entered credentials are not savedremember_prefix:encryptcontent_plugin_# use different prefixes if other sites are running on the same domainencrypted_something:# additionally encrypt some html elements#myNav: [div, id]myToc:[div,id]myTocButton:[div,id]search_index:'dynamically'# dynamically encrypt mkdocs search indexwebcrypto:true# use browsers webcrypto support#selfhost: true # use self-hosted crypto-js#selfhost_download: true # download crypt-js for self-hosting#selfhost_dir: 'theme_override' # where to download crypto-js#reload_scripts:#  - '#theme'password_file:'passwords.yml'# file with password inventory#kdf_pow: 4 # default for crypto-js: 4, default for webcrypto: 5sign_files:'encryptcontent-plugin.json'# save ed25519 signatures here#hash_filenames: # add hash to file names of assets (to make them impossible to guess#  extensions:#    - 'png'#    - 'jpg'#    - 'jpeg'#    - 'svg'#  except:#    - 'logo.svg'FeaturesOverride default templatesRelated toissue #32You can override the default templates with your own templates by providing an actual replacement
path in thehtml_template_path(HTML)andjs_template_path(JS)directives.
Overridden templatescompletelyreplace the default templates. Youmusttherefore copy the
default templates as a starting point to keep this plugin working.plugins:-encryptcontent:html_template_path:"/root/real/path/mkdocs_poc/my_html_template.html"js_template_path:"/root/real/path/mkdocs_poc/my_js_template.js"form_class:'md-content__innermd-typeset'input_class:'md-input'button_class:'md-buttonmd-button--primary'Useform_class,input_classandbutton_classto optionally set a CSS class for the password input field and the button.When overriding the default templates, you can add and use your own Jinja variables
and enrich your template, by defininghtml_extra_varsandjs_extra_varsdirectives in key/value format.
Added values can be used in your Jinja templates via the variableextra.plugins:-encryptcontent:html_extra_vars:my_extra:"extravalue"<key>:<value>js_extra_vars:my_extra:"extravalue"<key>:<value>For example, you can modify your HTML template, to add a new title with your own text variable.[ ... ]<h2>{{extra.my_extra}}</h2>[ ... ]NOTEIssues related to template override will not be addressed.Add buttonAddpassword_button: Truein plugin configuration variable, to add a button to the right of the password field.When enabled, it allows to decrypt the content just like the classic keypress ENTER.Optionally, you can addpassword_button_text: 'custom_text_button'to customize the button text.plugins:-encryptcontent:password_button:Truepassword_button_text:'custom_text_button'Tag encrypted pageEnable by defaultRelated toissue #7This feature adds an additional attributeencryptedwith valueTrueto the mkdocs typemkdocs.nav.pageobject.You can addtag_encrypted_page: Falsein plugin configuration, to disable tagging of encrypted pages.When enabled, it is possible to use theencryptedattribute in the jinja template of your theme, as a condition to perform custom modification.{%-fornav_iteminnav%}{%ifnav_item.encrypted%}<!-- Do something -->{%endif%}{%-endfor%}For example, you can use conditional check to add a custom class:<a{%ifnav_item.encrypted%}class="mkdocs-encrypted-class"{%endif%}href="{{nav_item.url|url}}">{{nav_item.title}}</a>Remember passwordRelated toissue #6By default the plugin will save the decrypted AES keys to session storage of the browser (can be disabled by settingremember_keys: False).
This is enabled for convenience, so you are able to browse between multiple encrypted pages without having to re-enter the password.Additionally it is possible to save the entered user/password in session storage (settingremember_password: True). Use this for
additional convenience duringmkdocs serve, because the AES key are regenerated every time MkDocs starts
(rendering the old ones invalid and requiring to re-enter a valid credential again).To avoid problems when multiple sites are hosted within the same domain, it is possible to customize the name of
the keys saved to storage withremember_prefix.This feature is not really secure !decryption keys are store in clear text inside session storage.Instead of using these features, I recommend to use a password manager with its browser plugin.
For exampleKeepassXCallows you to detect the password fieldmkdocs-content-passwordand fill it automatically in a much more secure way.It is also possible to save the used credentials permanently to local storage (settingsession_storage: False), but
this should only be done for testing purposes. The local storage of a browser is most likely readable
for every other process that can access the file system.The session storage however should only be located in memory and be forgotten after the browser tab is closed.plugins:-encryptcontent:remember_keys:Trueremember_password:Falseremember_prefix:secretsite_Share link generationIt is possible to share valid credentials by adding them to the hash part of the URL.
The plugin can also generate share links for certain pages if the meta tagsharelink: trueis defined in markdown.
It will use the first credential for the pages level or the pages password.
The credentials for auto-generated links are base64url encoded.plugins:-encryptcontent:sharelinks:Truesharelinks_output:'sharelinks.txt'# generated share links are saved hereOne condition applies: The user name must not contain the ":" character (Passwords may use that character).However ifsharelinks: Trueis enabled in the plugin configuration you can generate share links yourself:https://your-domain.com/your-site/protected-page/#!username:passwordfor user/password orhttps://your-domain.com/your-site/protected-page/#!passwordfor only password.Then another condition applies: If non-aphanumeric characters are used in user/password,
they need to be URLencoded (f.ex. %20 = space character). Some browsers may do that automatically (Do a copy/paste from the browsers address bar then).Incomplete Share linksSince version 3.0.3 it is possible to leave out one part of the password when share links are generated via meta tag.
To do this use the ":" character in a password to divide the part that is incorporated to the share link and the part that remains secret,
like "PartThatIsEncodedToTheShareLink:PartThatRemainsSecret".
The feature is enabled by setting the optionsharelinks_incomplete: true.
If the password that is read from the share link ends with the ":" character, then an additional password input field is displayed for entering the secret part.If the feature is used, then passwords must not end with the ":" character.Storage of additional variables in keystoreSince version 3.0.3 it is possible to set arbitrary session store variables after decryption.
These can be used by javascript functions f. ex. to read API tokens or show the user name or set visibility of menu entries.
The variables are encrypted together with the content keys in the keystore.plugins:-encryptcontent:additional_storage_file:'additional_storage.yaml'The file may contain auserpass:dictionary, wich refers to user names
and/or apassword:dictionary, that refers to password-only credentials.
It may contain a arbitrary number of key/value pairs,
but keep in mind to check if the key name is reasonable and not already used by other parts of the page
(as it will be overwritten after decryption).userpass:alice:username:Alice McAlicefaceuserid:1token:uCZDqa2vzuSPFX-o9TebinUAnD9sePJqpPYhavMkCH7gGo7lhjWRS17-HgBys9UKnFR37zXO4Q_f1_ywZJqBlA==password:Head32_Sculpture_bovine_:username:Head McPasswordModify generated pagesEncrypt somethingRelated toissue #9Addencrypted_something: {}in the plugin configuration variable, to encrypt something else on the page.The syntax of this new variableMUSTfollow the yaml format of a dictionary.
Child elements ofencrypted_somethingare build with a key<unique name>in string format and a list as value.
The list has to be constructed with the name of an HTML tag<html tag>as first item
andidorclassas the second item.plugins:-encryptcontent:encrypted_something:<uniqe name>:[<html tag>,<'class' or 'id'>]The<unique name>key identifies the name of a specific element of the page that will be
searched by Beautiful Soup.
The first value of the list (<html tag>) identifies the type of HTML tag in which the name is present.
The second value of the list ('id'or'class') specifies the type of the attribute which contains
the unique name in the html tag.Prefer to use an'id', however depending on the template of your theme, it is not always possible to use the id.
So we can use the class attribute to define your unique name inside the html tag.
Beautiful Soup will encrypt all HTML elements discovered with the class.When the feature is enabled every element is decrypted upon successfull content decryption on the page.Use this to hide the table of contents on the page or sub pages in the menuBy defaultevery child itemare encrypted and the encrypted elements have setstyle="display:none;"to hide their content.How to use it?! ExamplesYou can use thepage.encryptedtag to add attributes to html tags in the HTML templates of your theme
or identify usable tag ids or classes that are already in the theme.Then add these elements in the format of a yaml dictionary under the variableencrypted_something.For example, encrypt ToC in a theme where ToC is under 'div' element like this :<div class=".."{%ifpage.encrypted%}id="mkdocs-encrypted-toc"{%endif%}><ul class=".."><li class=".."><a href="{{toc_item.url}}">{{toc_item.title}}</a></li><li><a href="{{toc_item.url}}">{{toc_item.title}}</a></li></ul></div>Set your configuration like this :plugins:-encryptcontent:encrypted_something:mkdocs-encrypted-toc:[div,id]Other example, with multiple targets. You are using a custom version of Material theme and
want to encrypt ToC content and Footer.Material generates 2<nav>structures with the same templatetoc.html, so you need to use aclassinstead of an id for this part.
The footer part, is generated by thefooter.htmltemplate in a classic div so anidis sufficientAfter modification, your template looks like this :toc.html:<nav class="md-nav md-nav--secondary{%ifpage.encrypted%}mkdocs-encrypted-toc{%endif%}" aria-label="{{lang.t('toc.title')}}"><label class="md-nav__title" for="__toc"> ... </label><ul class="md-nav__list" data-md-scrollfix> ... </ul></nav>footer.html:<footer class="md-footer"><div class="md-footer-nav"{%ifpage.encrypted%}id="mkdocs-encrypted-footer"{%endif%}> ... </div><div class="md-footer-meta md-typeset"{%ifpage.encrypted%}id="mkdocs-encrypted-footer-meta"{%endif%}></footer>Your configuration would look like this :plugins:-encryptcontent:encrypted_something:mkdocs-encrypted-toc:[nav,class]mkdocs-encrypted-footer:[div,id]mkdocs-encrypted-footer-meta:[div,id]If you are using unmodified mkdocs-material, then this example will encrypt menu, toc and footer
But you'd need the Navigation pruning feature to hide the title of encrypted pages from navigation (or see 2.).plugins:-encryptcontent:encrypted_something:md-footer__inner:[nav,class]md-nav:[nav,class]Inject decrypt-form.tpl to themeSome themes or plugins might interfere with the way this plugin encrypts the content of a page.
In this case this feature will find and encrypt a unique tag in the same way asencrypted_somethingdoes and additionally inject itsdecrypt-form.tpl.htmlin front of it.plugins:-encryptcontent:inject:<uniq name>:[<html tag>,<'class' or 'id'>]This is an example for mkdocs-material:plugins:-blog-encryptcontent:encrypted_something:md-footer__inner:[nav,class]#Footermd-nav:[nav,class]#Menu and tocinject:md-content:[div,class]This feature overrides the normal practice of replacing the rendered content of a page.Mix encrypted and normal contentIt is possible to only encrypt parts of the page and also to remove parts on successful decryption.First install PyMdown Extensions by enteringpip install --upgrade pymdown-extensions,
then enable them in yourmkdocs.yml:markdown_extensions:-pymdownx.blocks.htmlThis is an example of a mixed markdown page:title: This page mixes encrypted and normal content
level: secret
inject_id: protected
delete_id: teaser

/// html | div#teaser
## Teaser

You won't believe which secrets this page will unveil.
Find out more after you enter the correct password...
///

/// html | div#protected
## Secret

Well, the princess is another castle.
///The markdown extension enables us to wrap a div tag around content by/// html | div#some-id.
It ends with///. The meta taginject_iddefines which div id we would like to encrypt
(it also injects the decryption form here). And the div id found atdelete_idwill be deleted
on successful decryption.Search encryptionSearch index encryptionDefault value is "encrypted"Related toissue #13:warning:The configuration mode "clear" of this functionality can cause DATA LEAKThe unencrypted content of each page is accessible through the search index.
Not encrypting the search index means completely removing the protection provided by this plugin.
You have been warnedThis feature allows you to control the behavior of the encryption plugin with the search index.
Three configuration modes are possible:clear: Search index is not encrypted. Search is possible even on protected pages.dynamically: Search index is encrypted on build. Search is possible once the pages have been decrypted.encrypted: Search index of encrypted pages is removed on build. Search is not possible on encrypted pages.You can setsearch_index: '<mode_name>'in yourmkdocs.ymlto change the search index encryption mode. Possible values areclear,dynamically,encrypted. The default mode is "encrypted".plugins:-encryptcontent:search_index:'dynamically'This functionality modifies the search index created by the “search” plug-in.
Some themes might override the default search plug-in provided by mkdocs,
but so far the structure of thesearch/search_index.jsonfile is consistent.The modification of the search index is carried out last (ifencryptcontentis also last inplugins).
But always double-check the generated index aftermkdocs buildto see if your information is protected.When the configuration mode is set to "dynamically", thejavascripts contribution filesare used to override the default search plugin files provided by MkDocs.
They include a process of decrypting and keeping the search index in a SessionStorage.Search index encryption for mkdocs-materialMaterial for MkDocsuses a different search plugin that
cannot "easily" be overridden like the default search plugin.
However this Plugin will still remove encrypted pages (encrypted) or encrypt the search entries (dynamically)
formkdocs-material.In order to be able to decrypt the search index (dynamically)mkdocs-materialneeds to be customized (patched).Patches for different versions can be foundhere.Material 8.xYou'll need someprerequisitesand also execute these commands:gitclonehttps://github.com/squidfunk/mkdocs-materialcdmkdocs-material
pipinstallmkdocs-minify-plugin
pipinstallmkdocs-redirects
npminstall#copy material_search_worker.patch to mkdocs-materialpatch-p0<material_search_worker8.patch

pipinstall--force-reinstall.#pip install --force-reinstall --no-deps . #faster if mkdocs-material was already installedMaterial 9.xFollow the instructions forTheme developmentcarefully.Apply the patch beforeBuilding the theme:patch-p0<material_browser_request9.patch# until Material 9.3patch-p0<material_browser_request9_4p.patch# Material 9.4+Javascript extensionsReload user-defined scriptsRelated toissue #14You can setreload_scripts:in yourmkdocs.ymlwith list of script sources
or script ids, to reload and execute some js lib after decryption process.plugins:-encryptcontent:reload_scripts:-'js/example.js'-'#autoexec'It is also possible to reload a script id like<script id="autoexec">console.log('test');</script>that was encrypted within the page
(related toissue #30).HighlightJS supportEnable by defaultIf HighlightJS module is detected in your theme to improve code color rendering, reload renderer after
decryption process.
If HighlightJS module is not correctly detected, you can force the detection by addinghljs: Trueon the plugin configuration
or sethljs: Falseto disable this feature.When enabled the following part of the template is added to force reloading decrypted content.{%ifhljs%}document.getElementById("mkdocs-decrypted-content").querySelectorAll('pre code').forEach((block) => {hljs.highlightElement(block);});{%endif%}Arithmatex supportEnable by defaultRelated toissue #12If Arithmatex markdown extension is detected in your markdown extensions to improve math equations rendering,
reload renderer after decryption process.
If the Arithmatex markdown extension is not correctly detected, you can force the detection by addingarithmatex: Trueon the plugin configuration
or setarithmatex: Falseto disable this feature.When enabled, the following part of the template is added to force math equations rendering on decrypted content.{%ifarithmatex%}MathJax.typesetPromise(){%endif%}NOTEIt has been tested in Arithmatexgenericmode only.Mermaid.js supportmkdocs-mermaid2-pluginEnable by defaultRelated toissue #22If mermaid2 plugin is detected in your configuration to generate graph from text, reload renderer after
decryption process.
If the Mermaid2 plugin is not correctly detected, you can force the detection by addingmermaid2: Trueon the plugin configuration
or setmermaid2: Falseto disable this feature.When enabled, the following part of the template is added to force graph rendering on decrypted content.{%ifmermaid2%}mermaid.contentLoaded();{%endif%}NOTEit currently only works with mermaid version < 10. Also encryptcontent needs to be injected,
because the mermaid2 plugin otherwise won't detect the page content correctly.activate the plugin like this:plugins:-mermaid2:version:9.4.3markdown_extensions:-pymdownx.blocks.htmlExample usage:password: 1234
inject_id: inject


/// html | div#inject

```mermaid
graph LR
    hello --> world
    world --> again
    again --> hello
```

///mkdocs-materialAdd support for mermaid graphs by adding thepymdownx.superfencestomarkdown_extensionsas
describedhere.Copy the "material-encryptcontent.mjs" file fromhereto "/assets/javascripts/" and include it to "mkdocs.yml" like this:extra_javascript:-assets/javascripts/material-encryptcontent.mjsThe script is called after successful decryption and renders the mermaid graphs in a similar way as
the theme would normally do.SecurityCrypto-js, crypto-es or webcrypto?By default the plugin uses the crypto-js library for page decryption, but using
the browser's built-in webcrypto engine is also possible (setwebcrypto: true).The main advantage of webcrypto over crypto-js is that it is much faster, allowing higher
calculation difficulty for key derivation (kdf_pow). Also it may be easier to implement
key derivation functions other than PBKDF2 with webcrypto in the future.On the other hand crypto-js is implemented in pure Javascript without any dependencies and well
tested (but it probably won't receive any updates as development stalled in 2021, seehere)
and we know nothing about how good or bad webcrypto is implemented in different browsers.There is a new variation ofcrypto-js, calledcrypto-es.
It provides the same functions, but is implemented in modern javascript and can be activated by settingesm: true.Ifwebcrypto: trueandesm: trueis set, then webcrypto will be loaded as a
javascript module (speeding up material theme with instant loading feature).Crypto-js causes a bug when browsing through encrypted pages,
if used inmkdocs-materialtogether with thenavigation.instantfeature.
It is advised to usewebcrypto: trueor crypto-es withesm: truein this case.Self-host Crypto-js or Crypto-esIf you enableselfhostthen you'll choose to upload crypto-js to your web server rather than using cloudflare CDN.
The self-host location is "SITE_URL/assets/javascripts/cryptojs/".Additionally if you setselfhost_downloadthen the required files will be automatically downloaded if needed.
The files are checked for their MD5 hash and saved todocs_dirorselfhost_dir(relative tomkdocs.yml).plugins:-encryptcontent:selfhost:Trueselfhost_download:Falseselfhost_dir:'theme_overrides'KDF key generation cachingEither way (webcrypto or crypto-js) theKDFkey generation needs to be done for each credential.
This may take some additional time when building the site, especially when there are many different ones.
That's why these keys and salt are cached by default to a yaml file named "encryptcontent.cache".plugins:-encryptcontent:cache_file:'encryptcontent.cache'# change file name if neededCaching can be disabled by settingcache_file: ''.File name obfuscationImagine your pages contain many images and you labeled them "1.jpg", "2.jpg" and so on for some reason.
If you'd like to encrypt one of these pages, an attacker could try guessing the image file names
and would be able to download them despite not having the password to the page.This feature should make it impossible or at least way harder for an external attacker to guess the file names.
Please also check and disable directory listing for that matter.
Keep in mind that your hosting provider is still able to see all your images and files.To counter file name guessing you could activate the feature like this:plugins:-encryptcontent:selfhost:trueselfhost_download:falsehash_filenames:extensions:-'png'-'jpg'-'jpeg'-'svg'except:-'lilien.svg'Atextensionswe define which file name extensions to obfuscate
(extension is taken from the part after the last ".",
so the extension of "image.jpg" is "jpg" and of "archive.tar.gz" is "gz").You can define multiple exceptions at theexceptlist.
The file names that end with these strings will be skipped.
You should use this if some images are used by themes or other plugins.
Otherwise, you'd need to change these file names to the obfuscated ones.The file names are obfuscated in a way that the corresponding file is hashed with MD5
and the hash is added to the file name
(If the file content is not changed the file name remains the same), like this:some_image_1_bb80db433751833b8f8b4ad23767c0fc.jpg
("bb80db433751833b8f8b4ad23767c0fc" being the MD5 hash of said image.)The file name obfuscation is currently applied to the whole site - not just the encrypted pages...Signing of generated filesAn attacker would most likely have a hard time brute forcing your encrypted content, given a good
password entropy. It would be much easier for him to fish for passwords by modifying the
generated pages, if he is able to hack your web space.This feature will sign all encrypted pages and used javascript files with Ed25519. It will also generate
an examplecanary script, which can be
customized to alert if files were modified.NOTEIf Mkdocs is running withmkdocs serve, then signature verification of encrypted pages
will fail, because the files are modified by Mkdocs to enable live reload.sign_files:'signatures.json'sign_key:'encryptcontent.key'#optionalcanary_template_path:'/path/to/canary.tpl.py'#optionalFirst an Ed25519 private key is generated at "encryptcontent.key" (besidesmkdocs.yml), however you can supply an
existing private key as long as it's in PEM format.After generation the signatures are saved to "signatures.json" insite_dir, so this file also needs to be uploaded
to the web space. The canary script will download this file and compare the URLs to its own list and then download
all files and verify the signatures.As long as the private key used for signing remains secret, the canary script will be able to determine
if someone tampered with the files on the server. But you should run the canary script from another machine
that is not related to the server, otherwise the attacker could also modify the canary script and sign with his
private key instead.