hierogolyph.go
package hierogolyph
import (
"fmt"
"strings"
"github.com/evalphobia/hierogolyph/hasher"
"github.com/evalphobia/hierogolyph/hsm"
)
// Hierogolyph treats encryption and decryption.
type Hierogolyph struct {
Config
Password string
Salt string
EncryptionKey string // generated by password and salt, used for encryption/decryption and verifying password.
}
// CreateHierogolyph creates new Hierogolyph from given password, which is used for encryption.
// (after the first encryption, don't use this constructor.)
func CreateHierogolyph(password string, conf Config) (Hierogolyph, error) {
salt, err := getRandomString(20)
if err != nil {
return Hierogolyph{}, err
}
h := Hierogolyph{
Config: conf,
Password: password,
Salt: salt,
}
err = h.SetEncryptionKey()
if err != nil {
return Hierogolyph{}, err
}
return h, nil
}
// SetEncryptionKey sets an encryption key generated from password and salt.
func (h *Hierogolyph) SetEncryptionKey() error {
ek, err := h.createEncryptionKey()
if err != nil {
return err
}
h.EncryptionKey = ek
return nil
}
// Unlock creates Content Encryption Key.
func (h Hierogolyph) Unlock() (cek string, err error) {
z1, z2 := createDigests(h.Password, h.Salt, h.Config.Hasher)
maskedCipherText, err := decodeBase64(h.EncryptionKey)
if err != nil {
return "", err
}
// get XOR between Z1 and R'
encryptedSecretR := xor(maskedCipherText, z1)
secretR, err := h.Config.HSM.Decrypt(encryptedSecretR)
if err != nil {
return "", err
}
return createCEK(z2, secretR), nil
}
// Encrypt encrypts given plainText.
func (h Hierogolyph) Encrypt(plainText string) (cipherText string, err error) {
cek, err := h.Unlock()
if err != nil {
return "", err
}
fingerPrint := HashHMAC(plainText, h.Config.HMACKey)
fingerprintedText := fmt.Sprintf("%s.%s", encodeBase64String(plainText), encodeBase64String(fingerPrint))
cipherText, err = h.Config.Cipher.Encrypt(fingerprintedText, []byte(cek))
if err != nil {
return "", err
}
encrypted := encodeBase64String(cipherText)
return fmt.Sprintf("%s.%s", encodeBase64String(h.EncryptionKey), encrypted), nil
}
// Decrypt decrypts given cipherText.
func (h Hierogolyph) Decrypt(cipherText string) (plainText string, err error) {
encryptionKey, encryptedText, err := decodeCipherText(cipherText)
if err != nil {
return "", err
}
h.EncryptionKey = encryptionKey
cek, err := h.Unlock()
if err != nil {
return "", err
}
fingerprintedText, err := h.Config.Cipher.Decrypt(encryptedText, []byte(cek))
if err != nil {
return "", err
}
parts := strings.Split(fingerprintedText, ".")
if len(parts) != 2 {
return "", fmt.Errorf("fingerprintedText=[%s] must have one dot `.`", fingerprintedText)
}
plainText, err = decodeBase64(parts[0])
if err != nil {
return "", err
}
fingerPrint, err := decodeBase64(parts[1])
if err != nil {
return "", err
}
expected := HashHMAC(plainText, h.Config.HMACKey)
if fingerPrint != expected {
return "", fmt.Errorf("HMAC finger print error: expected=[%s], actual=[%s]", expected, fingerPrint)
}
return plainText, nil
}
// createEncryptionKey creates encryption key from password and salt.
func (h *Hierogolyph) createEncryptionKey() (string, error) {
secretR, err := getRandomBytes(32)
if err != nil {
return "", err
}
conf := h.Config
z1, _ := createDigests(h.Password, h.Salt, conf.Hasher)
return createEncryptionKey(z1, string(secretR), conf.HSM)
}
// decodeCipherText decodes from cipherText and returns encryptionKey and encryptedText.
func decodeCipherText(cipherText string) (encryptionKey, encryptedText string, err error) {
parts := strings.Split(cipherText, ".")
if len(parts) != 2 {
return "", "", fmt.Errorf("cipherText=[%s] must have one dot `.`", cipherText)
}
encryptionKey, err = decodeBase64(parts[0])
if err != nil {
return "", "", err
}
encryptedText, err = decodeBase64(parts[1])
if err != nil {
return "", "", err
}
return encryptionKey, encryptedText, nil
}
// createDigests creates 32byte string pair from given password and salt by hashing.
func createDigests(password, salt string, hasher hasher.Hasher) (z1, z2 string) {
digest := hasher.Hash(password, salt)
return digest[0:32], digest[32:64]
}
// createEncryptionKey creates EncryptionKey from Z1 and R with HSM eryption.
func createEncryptionKey(z1, secretR string, hsm hsm.HSM) (encryptionKey string, err error) {
encryptedSecretR, err := hsm.Encrypt(secretR)
if err != nil {
return "", err
}
// get XOR between Z1 and R'
maskedCipherText := xor(string(encryptedSecretR), z1)
return encodeBase64(maskedCipherText), nil
}
// createCEK returns Content Encryption Key from Z2 and R.
func createCEK(z2, secretR string) (cek string) {
return HashSHA256(z2 + secretR)
}
// xor gets XOR bytes between 'a' and 'b'.
// The results is based on 'a's length.
// If 'a' is longer than 'b', 'b' will be padded by 0.
func xor(a, b string) []byte {
byteSize := len(a)
paddedB := paddingLeft(string(b), byteSize, "0")
result := make([]byte, byteSize)
for i := 0; i < byteSize; i++ {
result[i] = paddedB[i] ^ a[i]
}
return result
}