Request For Code Review (PBKDF2/AES256 CTR mode)


(Basebandit) #1

I am learning cyptography and chose Golang as my implementation language.I have written a simple tool to validate my understanding aes 256 with CTR mode. I would really appreciate anyone with working knowledge of crypto to help review my code. Please find the code here.
GoCrypt

The code looks like this:

package crypt

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	b64 "encoding/base64"
	"errors"

	"golang.org/x/crypto/pbkdf2"
)

//hashPassword generates a 32 byte key for use in both AES256 encryption and HMAC_SHA256 mac generation
func hashPassword(password, salt []byte) []byte {
	return pbkdf2.Key(password, salt, 10000, 32, sha256.New)
}

//VerifyHMAC compares MACs for validity in order to avoid timing side-channels.Generates the second ciphertext's MAC using the same key that generated the first ciphertext's MAC
func verifyHMAC256(ciphertext, ciphertextMAC, key []byte) bool {
	mac := hmac.New(sha256.New, key)
	mac.Write(ciphertext)
	expectedMAC := mac.Sum(nil)
	return hmac.Equal(ciphertextMAC, expectedMAC)
}

//genHMAC256 generates a hash of the encrypted text
func genHMAC256(ciphertext, key []byte) []byte {
	mac := hmac.New(sha256.New, key)
	mac.Write(ciphertext)
	hmac := mac.Sum(nil)
	return hmac
}

//Encrypt encrypts using the key from Hashpassword() then
//generates the mac of the encrypted text using GenHmac256
//and then appends the ciphertext to its mac
func Encrypt(text string, passphrase string) string {

	//It is recommended that your salt be at least 8 bytes long
	salt := make([]byte, 8)
	_, err := rand.Read(salt)

	if err != nil {
		return err.Error()
	}

	key := hashPassword([]byte(passphrase), salt)
	block, err := aes.NewCipher(key)

	if err != nil {
		return err.Error()
	}

	plaintext := []byte(text)

	ciphertext := make([]byte, aes.BlockSize+len(plaintext))

	iv := ciphertext[:aes.BlockSize]
	_, err = rand.Read(iv)
	if err != nil {
		return err.Error()
	}

	stream := cipher.NewCTR(block, iv)
	stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)

	hmac := genHMAC256(ciphertext, key)

	ciphertext = append(hmac, ciphertext...)

	return b64.StdEncoding.EncodeToString([]byte("Gocrypt_" + string(salt) + string(ciphertext)))
}

//Decrypt obtains the mac from the first 32 bytes of the ciphertext
//then checks whether its valid verifyHMAC().If it is valid it obtains
//the initialisation vector(16 bytes) also known as the nonce from the
//second batch of 32 bytes from the last byte position of the mac.It
//then obtains the ciphertext payload from the remaining slice of bytes:
//			ciphertext[48:].
//It is this ciphertext payload that is now XOR'd back to plaintext
func Decrypt(encrypted string, passphrase string) string {
	ct, err := b64.StdEncoding.DecodeString(encrypted)

	if err != nil {
		return err.Error()
	}

	if string(ct[:8]) != "Gocrypt_" {
		return ""
	}

	salt := ct[8:16]
	ct = ct[16:]

	key := hashPassword([]byte(passphrase), salt)

	block, err := aes.NewCipher(key)

	if err != nil {
		return err.Error()
	}

	hmac := ct[0:32]

	if ok := verifyHMAC256(ct[32:], hmac, key); ok {
		//length of the mac = 32 bytes
		//length of the aes block = 16 bytes
		//iv = d.ciphertext(32:48) = 16 bytes
		iv := ct[len(hmac) : len(hmac)+aes.BlockSize]

		//len(d.ciphertext) = len(mac) + len(iv) + len(ciphertext_payload)
		plaintext := make([]byte, len(ct)-(len(hmac)+aes.BlockSize))

		stream := cipher.NewCTR(block, iv)

		//len(hmac)+aes.BlockSize = 48
		stream.XORKeyStream(plaintext, ct[len(hmac)+aes.BlockSize:])

		return string(plaintext)
	}
	hmacError := errors.New("Invalid hmac")

	return hmacError.Error()

}