Dead Men Tell No Tales
Why
I found this video, and found the concept a rather intriguing. Though, instead of the whole switching it on before the encryption starts, is definitely not how I would do it.
For me, a dead man's switch should be switched on by default, and only decrypt upon asked along with the proper password of course.
The Idea
The whole plan is simple enough, let's make a small go-program to encrypt a file on start, then, when we receive the password it shall decrypt out file again. Such, as before you go out to do the all the nastinesses, you start the program.
Now, for funzies and making this a tad more secure and obscure we will be using an e-mail to unlock our file again.
The Research
The E-mail
First of all, we need a way to receive e-mails, this is definitely the hardest part.
For this post, though, we will go with emersion's go-imap
.
The Encryption
To be completely honest, I do not know much about encryption, hashes and decryption. So I will just go with what what seems like a great encryption algorithm, namely the AES 256
.
AES 256 is virtually impenetrable using brute-force methods. While a 56-bit DES key can be cracked in less than a day, AES would take billions of years to break using current computing technology. Hackers would be foolish to even attempt this type of attack.
The Code
32 characters
First we need to create a secure 32 character password for our AES:
echo 'madco.me' | sha256sum | base64 | head -c 32 ; echo
Please, do not use
madco.me
as your password.
Code
First, let's set some rights for our files that will be created. These are stolen from stackoverflow.com/a/42718395/754471.
const (
// https://stackoverflow.com/a/42718395/754471
OS_READ = 04
OS_WRITE = 02
OS_USER_SHIFT = 6
OS_USER_R = OS_READ << OS_USER_SHIFT
OS_USER_W = OS_WRITE << OS_USER_SHIFT
)
I use this helper function a lot, to check for errors
.
func e(err error) {
if err != nil {
log.Fatalln(err)
}
}
Now we need an encrypt
-func
func encrypt(key string, bytes []byte) []byte {
c, err := aes.NewCipher([]byte(key))
e(err)
gcm, err := cipher.NewGCM(c)
e(err)
nonce := make([]byte, gcm.NonceSize())
_, err = io.ReadFull(rand.Reader, nonce)
e(err)
return gcm.Seal(nonce, nonce, bytes, nil)
}
And of course a way to decrypt our file again
func decrypt(key, bytes []byte) ([]byte, error) {
c, err := aes.NewCipher([]byte(key))
if err != nil {
return []byte(""), err
}
gcm, err := cipher.NewGCM(c)
if err != nil {
return []byte(""), err
}
nonceSize := gcm.NonceSize()
if len(bytes) < nonceSize {
return []byte(""), err
}
nonce, cipherText := bytes[:nonceSize], bytes[nonceSize:]
plain, err := gcm.Open(nil, nonce, cipherText, nil)
if err != nil {
return []byte(""), err
}
return plain, nil
}
Great, now that these are done, let's build the e-mail checker
We use these two modules to access the gmail imap:
In here we read five messages, and return a true if we receive the Unlock
subject along with the proper passphrase, we generated this here.
We then split our body of the e-mail into smaller strings, and use the third one as our code. I had to fiddle a bit to figure this. So try out yourselves if you want to checkout what values the body provides.
If we receive the proper code, we decrypt and write the back a new and decrypted version of the encrypted file.
func checker(user, pass, file string, bytes []byte) bool {
c, err := client.DialTLS("imap.gmail.com:993", nil)
e(err)
defer func() {
err := c.Logout()
e(err)
}()
err = c.Login(user, pass)
e(err)
mbox, err := c.Select("INBOX", true)
e(err)
seqset := new(imap.SeqSet)
seqset.AddRange(mbox.Messages-5, mbox.Messages)
section := &imap.BodySectionName{}
items := []imap.FetchItem{section.FetchItem()}
msgs := make(chan *imap.Message, 5)
done := make(chan error, 1)
go func() {
done <- c.Fetch(seqset, items, msgs)
}()
for msg := range msgs {
m, err := mail.ReadMessage(msg.GetBody(section))
if err != nil {
log.Println(err)
break
}
subject := m.Header.Get("Subject")
if subject == "Unlock" {
body, err := ioutil.ReadAll(m.Body)
if err != nil {
log.Println(err)
break
}
bodies := strings.Split(string(body), "\r\n")
dat, err := decrypt([]byte(bodies[3]), bytes)
e(err)
orig := strings.Split(file, ".enc")[0]
return ioutil.WriteFile(orig, dat, OS_USER_R|OS_USER_W) == nil
}
}
return false
}
Finally we have the main func, in here we set two flags:
e
: To choose if the file has already been encrypted - which is great for testing, as the original file will be deleted upon running this program.f
: The file to encrypt and remove.
Next-up we read in the lines from a pipe, I'll explain how to do this in after the code. Basically we read all the piped in lines and use those - There should always be - at least - three(!).
Finally we check the mail server every 2 seconds, just to set a limit.
func main() {
encryptFile := flag.Bool("e", false, "To encrypt before running?")
file := flag.String("f", "./data.txt", "File to encrypt")
flag.Parse()
reader := bufio.NewReader(os.Stdin)
var inputs []string
for {
input, _, err := reader.ReadLine()
if err != nil {
break
}
inputs = append(inputs, string(input))
}
base := filepath.Base(*file)
dir := filepath.Dir(*file)
var err error
var encrypted []byte
if *encryptFile {
// If not encrypted, let's encrypt out file first
fileBytes, err := ioutil.ReadFile(*file)
e(err)
key := inputs[0]
encrypted = encrypt(key, fileBytes)
err = ioutil.WriteFile(dir+"/"+base+".enc", encrypted, OS_USER_R|OS_USER_W)
e(err)
os.Remove(*file)
} else {
// Read the contents of the already encrypted file
encrypted, err = ioutil.ReadFile(*file + ".enc")
e(err)
}
mail, pass := inputs[1], inputs[2]
for range time.Tick(2 * time.Second) {
if succ := checker(mail, pass, *file+".enc", encrypted); succ {
break
}
}
}
Running
To run this program is quite easy.
We simply create a credentials file first - make sure this stays secret for absolutely everybody. This file should contain three lines:
- The 32 char secret
- Your e-mail
- An app password for your e-mail, as we always want to call back the password, if needed.
To make an app password you simply navigate to this page: myaccount.google.com/apppasswords and sign-in. Then you choose
Other
, give it a name and you'll see a password. Save this, as you won't be able to see it again.
Prod?
You should not take this code as no way near production ready. But hack on it, and I believe in you! You'll make something out off this.
{ Best, Mads Bram Cordes }