I have problems sharing
I’ve always been a very shy person and not very good in new social situations, though over the years I’ve become better at this. I’ve (mostly) managed to hide this side by “faking it until you make it” but this anxiety still creeps through in other ways. Ways that are, or were, reflected in the design of this site. For example, there was no index page listing all my posts and each URL contained a unique sha1sum of gibberish making it nearly impossible to find a post without knowledge of the URL.
Hell, one reason I haven’t written much lately is because some of my previous posts received a lot of attention and I didn’t really want the extra scrutiny. Just because I put something online doesn’t mean it’s for everyone, especially not hacker news. Sometimes a man just wants to yell at a cloud and not have it yell back.
When I started to open up my blog to more people I wanted a way to restrict some of my previous writings, as they were not for a wider audience, but lacked a good way to do this. I’d written chrome extension a few years back which used some client side encryption/decryption which gave me the idea to just encrypt the blog posts I want to keep semi-private and share the key with whoever should read it.
Since I use Jekyll to power my blog I wanted it to be a part of that work flow instead of a tool I had to run after the fact. Luckily, Jekyll has support for a few different type of extensions and one of them was perfect for this.
The extension is super simple - it’s masquerading as a “convertor” in Jekyll parlance, which just means it gets the raw content of a post and does ‘something’ to it such as converting from markdown to html, or in our case plaintext to cipher text. I tried to do AES in ruby for about 10 minutes (I don’t know ruby) before I gave up and just called out to command line openssl because I don’t like ruby and I’m bad at it/everything. It then takes this base64’d encrypted post and sticks it into a bunch of Javascript which will prompt for a key. Upon success decryption the page will render like before.
To write an encrypted blog post, I use a change the extension from .md (markdown) to .enc and set the password by adding the line “XXXX ENC:
I even did some fancy storing of the password in your browsers local storage so you don’t have to reenter the key every time.
Go try it out, read one of my old posts where I talk why AV sucks the key is “hello friends!”.
The code for the plugin is below. I’d put it on github but I don’t think anyone should use it.
require 'open3'
require "base64"
module Jekyll
class BlogPostEncrypter < Converter
safe true
priority :low
def matches(ext)
ext =~ /^\.enc$/i
end
def output_ext(ext)
".html"
end
def convert(content)
if content.include? "XXXX ENC:"
header, rest = content.split("XXXX ENC:")
password, content = rest.split("\n",2)
password = password.strip()
puts password
else
password = "this is definitely the same as what's on theobsidiantower.com"
end
marker = 'AAAA1234567890AAAA'
md = Jekyll::Converters::Markdown.new(@config)
content = md.convert(content)
stdin, stdout, stderr = Open3.popen3("openssl enc -e -base64 -A -aes-256-cbc -pass pass:'#{password}'")
stdin.puts(marker + content)
stdin.close
enc = stdout.gets()
new_html = "
<div id='inputs'>
<input id='blogpost_password' type='text'><button type='button' onclick='attempt_password()'>decrypt</button>
</div>
<script src='/assets/aes.js'></script>
<script>
var encrypted_blogpost = '#{enc}';
function decrypt(password) {
plaintext = CryptoJS.AES.decrypt(encrypted_blogpost, password).toString();
var marker = Array.prototype.map.call('#{marker}', function(x) { return x.charCodeAt(0).toString(16) }).join('')
if(plaintext.startsWith(marker)) {
return plaintext.slice(marker.length);
}
return '';
}
function set_html(html) {
hmm = html.match(/.{1,2}/g).map(function(v){
return String.fromCharCode(parseInt(v, 16));
}).join('')
document.getElementsByClassName('post-content')[0].innerHTML = decodeURIComponent(escape(hmm));
if(document.getElementById('inputs')) {
document.getElementById('inputs').style.display = 'none';
}
}
function attempt_passwords() {
passwords = JSON.parse(localStorage.getItem('passwords'));
if(!passwords)
return;
for(var i = 0 ; i < passwords.length; i++)
{
plaintext = decrypt(passwords[i]);
if(plaintext.length > 0) {
set_html(plaintext);
break;
}
}
}
function add_password(newpassword) {
passwords = JSON.parse(localStorage.getItem('passwords'));
if(passwords == undefined)
{
passwords = [newpassword]
}
else
{
if(passwords.includes(newpassword))
return
passwords.push(newpassword);
}
localStorage.setItem('passwords', JSON.stringify(passwords));
}
function attempt_password() {
password = document.getElementById('blogpost_password').value;
plaintext = decrypt(password);
if(plaintext) {
add_password(password);
set_html(plaintext);
}
}
attempt_passwords();
</script>
"
end
end
end