2018-04-26 09:08:28 +00:00
|
|
|
package openssl
|
2015-07-17 21:49:39 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"crypto/aes"
|
|
|
|
"crypto/cipher"
|
|
|
|
"crypto/rand"
|
|
|
|
"encoding/base64"
|
2017-09-18 13:49:33 +00:00
|
|
|
"errors"
|
2015-07-17 21:49:39 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
)
|
|
|
|
|
2018-09-11 10:16:18 +00:00
|
|
|
// ErrInvalidSalt is returned when a salt with a length of != 8 byte is passed
|
2017-09-18 13:49:33 +00:00
|
|
|
var ErrInvalidSalt = errors.New("Salt needs to have exactly 8 byte")
|
|
|
|
|
2015-07-17 21:49:39 +00:00
|
|
|
// OpenSSL is a helper to generate OpenSSL compatible encryption
|
|
|
|
// with autmatic IV derivation and storage. As long as the key is known all
|
|
|
|
// data can also get decrypted using OpenSSL CLI.
|
|
|
|
// Code from http://dequeue.blogspot.de/2014/11/decrypting-something-encrypted-with.html
|
|
|
|
type OpenSSL struct {
|
|
|
|
openSSLSaltHeader string
|
|
|
|
}
|
|
|
|
|
2020-06-13 13:29:46 +00:00
|
|
|
// Creds holds a key and an IV for encryption methods
|
|
|
|
type Creds struct {
|
|
|
|
Key []byte
|
|
|
|
IV []byte
|
2015-07-17 21:49:39 +00:00
|
|
|
}
|
|
|
|
|
2020-06-13 13:29:46 +00:00
|
|
|
func (o Creds) equals(i Creds) bool {
|
|
|
|
// If lengths does not match no chance they are equal
|
|
|
|
if len(o.Key) != len(i.Key) || len(o.IV) != len(i.IV) {
|
|
|
|
return false
|
2015-07-17 21:49:39 +00:00
|
|
|
}
|
|
|
|
|
2020-06-13 13:29:46 +00:00
|
|
|
// Compare keys
|
|
|
|
for j := 0; j < len(o.Key); j++ {
|
|
|
|
if o.Key[j] != i.Key[j] {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
2018-11-02 11:37:30 +00:00
|
|
|
|
2020-06-13 13:29:46 +00:00
|
|
|
// Compare IV
|
|
|
|
for j := 0; j < len(o.IV); j++ {
|
|
|
|
if o.IV[j] != i.IV[j] {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
2018-04-26 09:08:28 +00:00
|
|
|
|
2020-06-13 13:29:46 +00:00
|
|
|
return true
|
2018-11-02 11:37:30 +00:00
|
|
|
}
|
2018-09-11 06:51:21 +00:00
|
|
|
|
2020-06-13 13:29:46 +00:00
|
|
|
// New instanciates and initializes a new OpenSSL encrypter
|
|
|
|
func New() *OpenSSL {
|
|
|
|
return &OpenSSL{
|
|
|
|
openSSLSaltHeader: "Salted__", // OpenSSL salt is always this string + 8 bytes of actual salt
|
|
|
|
}
|
2018-09-11 06:51:21 +00:00
|
|
|
}
|
|
|
|
|
2018-04-26 09:08:28 +00:00
|
|
|
// DecryptBytes takes a slice of bytes with base64 encoded, encrypted data to decrypt
|
2018-11-02 11:37:30 +00:00
|
|
|
// and a key-derivation function. The key-derivation function must match the function
|
|
|
|
// used to encrypt the data. (In OpenSSL the value of the `-md` parameter.)
|
|
|
|
//
|
|
|
|
// You should not just try to loop the digest functions as this will cause a race
|
|
|
|
// condition and you will not be able to decrypt your data properly.
|
2020-06-13 13:29:46 +00:00
|
|
|
func (o OpenSSL) DecryptBytes(passphrase string, encryptedBase64Data []byte, cg CredsGenerator) ([]byte, error) {
|
2018-04-26 09:08:28 +00:00
|
|
|
data := make([]byte, base64.StdEncoding.DecodedLen(len(encryptedBase64Data)))
|
|
|
|
n, err := base64.StdEncoding.Decode(data, encryptedBase64Data)
|
2015-07-17 21:49:39 +00:00
|
|
|
if err != nil {
|
2018-04-26 09:08:28 +00:00
|
|
|
return nil, fmt.Errorf("Could not decode data: %s", err)
|
2015-07-17 21:49:39 +00:00
|
|
|
}
|
2018-04-26 09:08:28 +00:00
|
|
|
|
|
|
|
// Truncate to real message length
|
|
|
|
data = data[0:n]
|
|
|
|
|
2020-06-13 13:29:46 +00:00
|
|
|
decrypted, err := o.DecryptBinaryBytes(passphrase, data, cg)
|
2019-04-29 13:37:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return decrypted, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// DecryptBinaryBytes takes a slice of binary bytes, encrypted data to decrypt
|
|
|
|
// and a key-derivation function. The key-derivation function must match the function
|
|
|
|
// used to encrypt the data. (In OpenSSL the value of the `-md` parameter.)
|
|
|
|
//
|
|
|
|
// You should not just try to loop the digest functions as this will cause a race
|
|
|
|
// condition and you will not be able to decrypt your data properly.
|
2020-06-13 13:29:46 +00:00
|
|
|
func (o OpenSSL) DecryptBinaryBytes(passphrase string, encryptedData []byte, cg CredsGenerator) ([]byte, error) {
|
2019-04-29 13:37:29 +00:00
|
|
|
if len(encryptedData) < aes.BlockSize {
|
2017-04-04 21:08:20 +00:00
|
|
|
return nil, fmt.Errorf("Data is too short")
|
|
|
|
}
|
2019-04-29 13:37:29 +00:00
|
|
|
saltHeader := encryptedData[:aes.BlockSize]
|
2015-07-17 21:49:39 +00:00
|
|
|
if string(saltHeader[:8]) != o.openSSLSaltHeader {
|
2018-09-11 10:16:18 +00:00
|
|
|
return nil, fmt.Errorf("Does not appear to have been encrypted with OpenSSL, salt header missing")
|
2015-07-17 21:49:39 +00:00
|
|
|
}
|
|
|
|
salt := saltHeader[8:]
|
2018-09-11 06:51:21 +00:00
|
|
|
|
2020-06-13 13:29:46 +00:00
|
|
|
creds, err := cg([]byte(passphrase), salt)
|
2018-11-02 11:37:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2015-07-17 21:49:39 +00:00
|
|
|
}
|
2020-06-13 13:29:46 +00:00
|
|
|
return o.decrypt(creds.Key, creds.IV, encryptedData)
|
2015-07-17 21:49:39 +00:00
|
|
|
}
|
|
|
|
|
2018-04-26 09:08:28 +00:00
|
|
|
func (o OpenSSL) decrypt(key, iv, data []byte) ([]byte, error) {
|
2015-07-17 21:49:39 +00:00
|
|
|
if len(data) == 0 || len(data)%aes.BlockSize != 0 {
|
2018-09-11 10:16:18 +00:00
|
|
|
return nil, fmt.Errorf("bad blocksize(%v), aes.BlockSize = %v", len(data), aes.BlockSize)
|
2015-07-17 21:49:39 +00:00
|
|
|
}
|
|
|
|
c, err := aes.NewCipher(key)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
cbc := cipher.NewCBCDecrypter(c, iv)
|
|
|
|
cbc.CryptBlocks(data[aes.BlockSize:], data[aes.BlockSize:])
|
|
|
|
out, err := o.pkcs7Unpad(data[aes.BlockSize:], aes.BlockSize)
|
|
|
|
if out == nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
2019-04-29 13:37:29 +00:00
|
|
|
// EncryptBytes encrypts a slice of bytes that are base64 encoded in a manner compatible to OpenSSL encryption
|
2018-04-26 09:08:28 +00:00
|
|
|
// functions using AES-256-CBC as encryption algorithm. This function generates
|
|
|
|
// a random salt on every execution.
|
2020-06-13 13:29:46 +00:00
|
|
|
func (o OpenSSL) EncryptBytes(passphrase string, plainData []byte, cg CredsGenerator) ([]byte, error) {
|
2018-09-11 08:56:37 +00:00
|
|
|
salt, err := o.GenerateSalt()
|
2015-07-17 21:49:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-06-13 13:29:46 +00:00
|
|
|
return o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, plainData, cg)
|
2018-09-11 08:56:37 +00:00
|
|
|
}
|
|
|
|
|
2019-04-29 13:37:29 +00:00
|
|
|
// EncryptBinaryBytes encrypts a slice of bytes in a manner compatible to OpenSSL encryption
|
|
|
|
// functions using AES-256-CBC as encryption algorithm. This function generates
|
|
|
|
// a random salt on every execution.
|
2020-06-13 13:29:46 +00:00
|
|
|
func (o OpenSSL) EncryptBinaryBytes(passphrase string, plainData []byte, cg CredsGenerator) ([]byte, error) {
|
2019-04-29 13:37:29 +00:00
|
|
|
salt, err := o.GenerateSalt()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-06-13 13:29:46 +00:00
|
|
|
return o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, plainData, cg)
|
2019-04-29 13:37:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// EncryptBytesWithSaltAndDigestFunc encrypts a slice of bytes that are base64 encoded in a manner compatible to OpenSSL
|
2018-09-11 08:56:37 +00:00
|
|
|
// encryption functions using AES-256-CBC as encryption algorithm. The salt
|
|
|
|
// needs to be passed in here which ensures the same result on every execution
|
|
|
|
// on cost of a much weaker encryption as with EncryptString.
|
|
|
|
//
|
|
|
|
// The salt passed into this function needs to have exactly 8 byte.
|
|
|
|
//
|
|
|
|
// The hash function corresponds to the `-md` parameter of OpenSSL. For OpenSSL pre-1.1.0c
|
|
|
|
// DigestMD5Sum was the default, since then it is DigestSHA256Sum.
|
|
|
|
//
|
|
|
|
// If you don't have a good reason to use this, please don't! For more information
|
|
|
|
// see this: https://en.wikipedia.org/wiki/Salt_(cryptography)#Common_mistakes
|
2020-06-13 13:29:46 +00:00
|
|
|
func (o OpenSSL) EncryptBytesWithSaltAndDigestFunc(passphrase string, salt, plainData []byte, cg CredsGenerator) ([]byte, error) {
|
|
|
|
enc, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, plainData, cg)
|
2015-07-17 21:49:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return []byte(base64.StdEncoding.EncodeToString(enc)), nil
|
|
|
|
}
|
|
|
|
|
2018-04-26 09:08:28 +00:00
|
|
|
func (o OpenSSL) encrypt(key, iv, data []byte) ([]byte, error) {
|
2015-07-17 21:49:39 +00:00
|
|
|
padded, err := o.pkcs7Pad(data, aes.BlockSize)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
c, err := aes.NewCipher(key)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
cbc := cipher.NewCBCEncrypter(c, iv)
|
|
|
|
cbc.CryptBlocks(padded[aes.BlockSize:], padded[aes.BlockSize:])
|
|
|
|
|
|
|
|
return padded, nil
|
|
|
|
}
|
|
|
|
|
2019-04-29 13:37:29 +00:00
|
|
|
// EncryptBinaryBytesWithSaltAndDigestFunc encrypts a slice of bytes in a manner compatible to OpenSSL
|
|
|
|
// encryption functions using AES-256-CBC as encryption algorithm. The salt
|
|
|
|
// needs to be passed in here which ensures the same result on every execution
|
|
|
|
// on cost of a much weaker encryption as with EncryptString.
|
|
|
|
//
|
|
|
|
// The salt passed into this function needs to have exactly 8 byte.
|
|
|
|
//
|
|
|
|
// The hash function corresponds to the `-md` parameter of OpenSSL. For OpenSSL pre-1.1.0c
|
|
|
|
// DigestMD5Sum was the default, since then it is DigestSHA256Sum.
|
|
|
|
//
|
|
|
|
// If you don't have a good reason to use this, please don't! For more information
|
|
|
|
// see this: https://en.wikipedia.org/wiki/Salt_(cryptography)#Common_mistakes
|
2020-06-13 13:29:46 +00:00
|
|
|
func (o OpenSSL) EncryptBinaryBytesWithSaltAndDigestFunc(passphrase string, salt, plainData []byte, cg CredsGenerator) ([]byte, error) {
|
2019-04-29 13:37:29 +00:00
|
|
|
if len(salt) != 8 {
|
|
|
|
return nil, ErrInvalidSalt
|
|
|
|
}
|
|
|
|
|
|
|
|
data := make([]byte, len(plainData)+aes.BlockSize)
|
|
|
|
copy(data[0:], o.openSSLSaltHeader)
|
|
|
|
copy(data[8:], salt)
|
|
|
|
copy(data[aes.BlockSize:], plainData)
|
|
|
|
|
2020-06-13 13:29:46 +00:00
|
|
|
creds, err := cg([]byte(passphrase), salt)
|
2019-04-29 13:37:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-06-13 13:29:46 +00:00
|
|
|
enc, err := o.encrypt(creds.Key, creds.IV, data)
|
2019-04-29 13:37:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return enc, nil
|
|
|
|
}
|
|
|
|
|
2018-11-02 11:37:30 +00:00
|
|
|
// GenerateSalt generates a random 8 byte salt
|
|
|
|
func (o OpenSSL) GenerateSalt() ([]byte, error) {
|
|
|
|
salt := make([]byte, 8) // Generate an 8 byte salt
|
|
|
|
_, err := io.ReadFull(rand.Reader, salt)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-07-17 21:49:39 +00:00
|
|
|
|
2018-11-02 11:37:30 +00:00
|
|
|
return salt, nil
|
2018-09-11 06:51:21 +00:00
|
|
|
}
|
2018-09-11 08:56:37 +00:00
|
|
|
|
2018-11-02 11:37:30 +00:00
|
|
|
// MustGenerateSalt is a wrapper around GenerateSalt which will panic on an error.
|
|
|
|
// This allows you to use this function as a parameter to EncryptBytesWithSaltAndDigestFunc
|
|
|
|
func (o OpenSSL) MustGenerateSalt() []byte {
|
|
|
|
s, err := o.GenerateSalt()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return s
|
2015-07-17 21:49:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// pkcs7Pad appends padding.
|
2018-04-26 09:08:28 +00:00
|
|
|
func (o OpenSSL) pkcs7Pad(data []byte, blocklen int) ([]byte, error) {
|
2015-07-17 21:49:39 +00:00
|
|
|
if blocklen <= 0 {
|
|
|
|
return nil, fmt.Errorf("invalid blocklen %d", blocklen)
|
|
|
|
}
|
|
|
|
padlen := 1
|
|
|
|
for ((len(data) + padlen) % blocklen) != 0 {
|
2020-06-13 13:29:46 +00:00
|
|
|
padlen++
|
2015-07-17 21:49:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pad := bytes.Repeat([]byte{byte(padlen)}, padlen)
|
|
|
|
return append(data, pad...), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// pkcs7Unpad returns slice of the original data without padding.
|
2018-04-26 09:08:28 +00:00
|
|
|
func (o OpenSSL) pkcs7Unpad(data []byte, blocklen int) ([]byte, error) {
|
2015-07-17 21:49:39 +00:00
|
|
|
if blocklen <= 0 {
|
|
|
|
return nil, fmt.Errorf("invalid blocklen %d", blocklen)
|
|
|
|
}
|
|
|
|
if len(data)%blocklen != 0 || len(data) == 0 {
|
|
|
|
return nil, fmt.Errorf("invalid data len %d", len(data))
|
|
|
|
}
|
|
|
|
padlen := int(data[len(data)-1])
|
|
|
|
if padlen > blocklen || padlen == 0 {
|
|
|
|
return nil, fmt.Errorf("invalid padding")
|
|
|
|
}
|
|
|
|
pad := data[len(data)-padlen:]
|
|
|
|
for i := 0; i < padlen; i++ {
|
|
|
|
if pad[i] != byte(padlen) {
|
|
|
|
return nil, fmt.Errorf("invalid padding")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return data[:len(data)-padlen], nil
|
|
|
|
}
|