Tovi Jaeschke

Links:

Recent posts:

How to write a file encryption program in Go

Here I will show you how to write a file encryption program in Go.

Encryption is the process of encoding data in a way that only people with the correct decryption method and key can read the data. Some popular encryption utilities include LUKS, GnuPG, or the famously outdated TrueCrypt.

So if there are already so many encryption programs, why are we going to write yet another? Well first off, is to build an understanding on how encryption works, and why it is important. There is a lot of code floating around in this age of information, and if you ask me, knowledge is power. Secondly, in my next post I will be showing you how this same code that is designed to keep your data safe, can be used in a malicious fashion, and why it's dangerous.

Please note, I use vim throughout this tutorial. Feel free to replace that with your editor of choosing.

We will start with the encryption logic, which will consist of

In this first segment, we will not be implementing any malicious functionality into our code, only the encryption logic. There is no reason the code (in this segment) could not be used for a regular file encryption program.

Key Hashes, Cipher Blocks, and Secure Deletion

First, we will create a new directory called $GOPATH/src/FileEncryption, and create a new file called CreateCipherblock.go


mkdir $GOPATH/src/FileEncryption
vim $GOPATH/src/FileEncryption/CreateCipherblock.go

We will start by importing the necessary go packages, and defining a function to create the sha256 hash, cipher block, and a function to securely delete unwanted files.

package FileEncryption

import (
	
	"crypto/aes"
	"crypto/cipher"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	)

func CreateHash(key string) []byte {
	h := hmac.New(sha256.New, []byte(key))
	h.Write([]byte(key))
	fmt.Println(h.Size())
	return h.Sum(nil)
}

func CreateKey(hashedKey []byte) (cipher.Block, error) {
	block, err := aes.NewCipher(hashedKey)
	if err != nil {
		return nil, err
	}
	return block, nil
}

func SecureDelete(FilePath string) error {
	file, err := os.OpenFile(FilePath, os.O_RDWR, 0666)
	defer file.Close()
	// Find out how large is the target file
	fileInfo, err := file.Stat()
	if err != nil {
		return err
	}
	var size int64 = fileInfo.Size()
	// Create byte array filled with zero's
	zeroBytes := make([]byte, size)
	_, err = file.Write([]byte(zeroBytes))
	if err != nil {
		return err
	}
	err = os.Remove(FilePath)
	if err != nil {
		return err
	}
	return nil
}

Ok, so what is this code doing? The CreateHash function is creating a HMAC(hash-based message authentication code) with our secret key, and then passing that same secret key through the HMAC, to create a password hash. The second function, CreateKey, is creating an AES cipher block to be used in our block encryption algorithm, this is what will will use to actually encrypt our data. The third function, SecureDelete, is used to overwrite all the data contained in the plaintext file after encrypting the data. We need to do this because when you simply delete a file, the data remains on the HDD until the operating system decides to use this space again, it simply isn't listed in your filesystem. This function will prevent forensics experts from recovering the data that you are trying to keep secret.

File Encryption

Second, we will create the code to Encrypt the file


vim $GOPATH/src/FileEncryption/EncryptFile.go


package FileEncryption

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"io"
	"io/ioutil"
	"os"
)

func EncryptFile(password string, FilePath string) error {
	hashedKey := CreateHash(password)
	plaintext, err := ioutil.ReadFile(FilePath)
	if err != nil {
		return nil
	}
	ciphertext := make([]byte, aes.BlockSize+len(hashedKey)+len(plaintext))
	iv := ciphertext[:aes.BlockSize]
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		return err
	}
	block, err := CreateKey(hashedKey)
	if err != nil {
		return err
	}
	stream := cipher.NewCFBEncrypter(block, iv)
	stream.XORKeyStream(ciphertext[aes.BlockSize:], []byte(hashedKey))
	stream.XORKeyStream(ciphertext[aes.BlockSize+len([]byte(hashedKey)):], plaintext)

	// open output file
	encryptedFile, err := os.Create(FilePath + ".enc")
	if err != nil {
		return nil
	}
	defer func() {
		encryptedFile.Close()
		SecureDelete(FilePath)
	}()
	_, err = io.Copy(encryptedFile, bytes.NewReader(ciphertext))
	if err != nil {
		return err
	}
	return nil
}

In the EncryptFile function, we are doing a few things.
  1. We create the sha256 password hash using the function we defined earlier
  2. We open the unencrypted file and store the data in a variable called plaintext
  3. Next, we create a byte array to store the encrypted data, IV, and cipherblock (also using our predefined function)
  4. At line 28 we begin the encryption process by creating a Cipher Feedback Stream or CFB Stream. This will encrypt all the data fed through it using the XORKeyStream function, that as the name suggests, works by XOR'ing (Exclusive or) each byte in the given slice.
  5. The rest of the code is quite easy to understand (especially if you are versed in golang).
  6. We create a new file to hold our encrypted data, with the extension ".enc". Please note, windows will corrolate this file extension with malware, so you may want to change it.
  7. We then defer a function, that will be run after the EncryptFile function finishes that closes the encrypted file, and securely deletes the plaintext file with the function we defined earlier.
  8. Lastly, we copy the all the newly encrypted data to the new ".enc" file

File Decryption


vim $GOPATH/src/FileEncryption/DecryptFile.go


package FileEncryption

import (
	"os"
	"io"
	"bytes"
	"errors"
	"strings"
	"syscall"
	"io/ioutil"
	"crypto/aes"
	"crypto/cipher"
)

func DecryptFile(password string, FilePath string) error {
	OldFilePath := FilePath[:(len(FilePath) - 4)]
	hashedKey := CreateHash(password)
	ciphertext, err := ioutil.ReadFile(FilePath)
	if err != nil {
		return false, err
	}
	if len(ciphertext) < aes.BlockSize {
		return errors.New("ciphertext too short")
	}

	iv := ciphertext[:aes.BlockSize]
	encKey := ciphertext[:32 + aes.BlockSize][aes.BlockSize:]
	ciphertext = ciphertext[aes.BlockSize + 32:]

	block, err := CreateKey(hashedKey)

	stream := cipher.NewCFBDecrypter(block, iv)
	stream.XORKeyStream(encKey, encKey)

	for i := range encKey {
		if encKey[i] != hashedKey[i] {
			return errors.New("Incorrect Password")
		}
	}

	stream.XORKeyStream(ciphertext, ciphertext)

	plaintextFile, err := os.Create(OldFilePath)
	if err != nil {
		return err
	}
	_, err = io.Copy(plaintextFile, bytes.NewReader(ciphertext))
	if err != nil {
		return err
	}
	os.Remove(FilePath)
	return nil
}

The DecryptFile function is almost identical to the EncryptFile function, apart from lines 35-39, which compares the byte arrays of the new hashed password, and the old one that we wrote to the encrypted file.

Linking It All Together

Now, lets start writing the code that actually utilizes the previous code we have written, so we can create a useful encryption program.


vim main.go


package main

import (
	"FileEncryption"
	"errors"
	"flag"
	"fmt"
	"os"
	"syscall"

	"golang.org/x/crypto/ssh/terminal"
)

func GetPassword() string {
	fmt.Print("Password: ")
	password, err := terminal.ReadPassword(int(syscall.Stdin))
	if err != nil {
		fmt.Println("Unable to get password")
	}
	fmt.Print("\n")
	return string(password)
}

func main() {
	encFlag := flag.Bool("e", false, "Encrypt a file")
	decFlag := flag.Bool("d", false, "Decrypt a file")
	helpFlag := flag.Bool("h", false, "Shows help message")

	flag.Parse()

	if (!*encFlag && !*decFlag) || *helpFlag {
		var CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
		fmt.Fprintf(CommandLine.Output(), "Usage of %s:\n", os.Args[0])
		flag.PrintDefaults()
		os.Exit(0)
	}

	for i := 0; i < len(flag.Args()); i++ {
		if _, err := os.Stat(flag.Args()[i]); os.IsNotExist(err) {
			fmt.Println(errors.New(fmt.Sprintf("Error: %s does not exist", flag.Args()[i])))
			os.Exit(1)
		}
	}

	if *encFlag {
		pass := GetPassword()
		for i := 0; i < len(flag.Args()); i++ {
			err := FileEncryption.EncryptFile(pass, flag.Args()[i])
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			} else {
				fmt.Printf("Sucessfully encrypted %s\n", flag.Args()[i])
			}
		}
	} else if *decFlag {
		pass := GetPassword()
		for i := 0; i < len(flag.Args()); i++ {
			err := FileEncryption.DecryptFile(pass, flag.Args()[i])
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			} else {
				fmt.Printf("Sucessfully decrypted %s\n", flag.Args()[i])
			}
		}
	}
}

This code is fairly simple compared to the previous code we have written. It uses the flag package to prompt and parse the user for command line arguments, and outputs the help message if the program is not being used correctly. It then checks if the "tail" arguments (flag.Args()) are existing files, and if that returns true, it prompts the user for a password to encrypt/decrypt the file. I am using the golang.org/x/crypto/ssh/terminal package to prompt the user for a password, so the password is not echoed onto the screen as it is being typed, to get this package run

go get golang.org/x/crypto/ssh/terminal

Building

Finally, we need to build the package using


go build main.go

And there we have it, our own encryption program that we actually understand, and can tinker with to increase its security and effectiveness.