1
0
Fork 0
mirror of https://github.com/Luzifer/envrun.git synced 2024-11-14 00:52:42 +00:00
envrun/vendor/github.com/Luzifer/go-openssl/openssl.go

234 lines
7.4 KiB
Go
Raw Normal View History

package openssl
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"io"
)
var ErrInvalidSalt = errors.New("Salt needs to have exactly 8 byte")
// 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
}
type openSSLCreds struct {
key []byte
iv []byte
}
// 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
}
}
// DecryptString decrypts a string that was encrypted using OpenSSL and AES-256-CBC
func (o OpenSSL) DecryptString(passphrase, encryptedBase64String string) ([]byte, error) {
return o.DecryptBytes(passphrase, []byte(encryptedBase64String))
}
// DecryptBytes takes a slice of bytes with base64 encoded, encrypted data to decrypt
func (o OpenSSL) DecryptBytes(passphrase string, encryptedBase64Data []byte) ([]byte, error) {
data := make([]byte, base64.StdEncoding.DecodedLen(len(encryptedBase64Data)))
n, err := base64.StdEncoding.Decode(data, encryptedBase64Data)
if err != nil {
return nil, fmt.Errorf("Could not decode data: %s", err)
}
// Truncate to real message length
data = data[0:n]
if len(data) < aes.BlockSize {
return nil, fmt.Errorf("Data is too short")
}
saltHeader := data[:aes.BlockSize]
if string(saltHeader[:8]) != o.openSSLSaltHeader {
return nil, fmt.Errorf("Does not appear to have been encrypted with OpenSSL, salt header missing.")
}
salt := saltHeader[8:]
creds, err := o.extractOpenSSLCreds([]byte(passphrase), salt)
if err != nil {
return nil, err
}
return o.decrypt(creds.key, creds.iv, data)
}
func (o OpenSSL) decrypt(key, iv, data []byte) ([]byte, error) {
if len(data) == 0 || len(data)%aes.BlockSize != 0 {
return nil, fmt.Errorf("bad blocksize(%v), aes.BlockSize = %v\n", len(data), aes.BlockSize)
}
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
}
// EncryptString 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.
func (o OpenSSL) EncryptBytes(passphrase string, plainData []byte) ([]byte, error) {
salt := make([]byte, 8) // Generate an 8 byte salt
_, err := io.ReadFull(rand.Reader, salt)
if err != nil {
return nil, err
}
return o.EncryptBytesWithSalt(passphrase, salt, plainData)
}
// EncryptString encrypts a string in a manner compatible to OpenSSL encryption
// functions using AES-256-CBC as encryption algorithm. This function generates
// a random salt on every execution.
func (o OpenSSL) EncryptString(passphrase, plaintextString string) ([]byte, error) {
salt := make([]byte, 8) // Generate an 8 byte salt
_, err := io.ReadFull(rand.Reader, salt)
if err != nil {
return nil, err
}
return o.EncryptStringWithSalt(passphrase, salt, plaintextString)
}
// EncryptStringWithSalt encrypts a string 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.
//
// 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
func (o OpenSSL) EncryptStringWithSalt(passphrase string, salt []byte, plaintextString string) ([]byte, error) {
return o.EncryptBytesWithSalt(passphrase, salt, []byte(plaintextString))
}
// EncryptBytesWithSalt 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.
//
// 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
func (o OpenSSL) EncryptBytesWithSalt(passphrase string, salt, plainData []byte) ([]byte, error) {
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)
creds, err := o.extractOpenSSLCreds([]byte(passphrase), salt)
if err != nil {
return nil, err
}
enc, err := o.encrypt(creds.key, creds.iv, data)
if err != nil {
return nil, err
}
return []byte(base64.StdEncoding.EncodeToString(enc)), nil
}
func (o OpenSSL) encrypt(key, iv, data []byte) ([]byte, error) {
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
}
// openSSLEvpBytesToKey follows the OpenSSL (undocumented?) convention for extracting the key and IV from passphrase.
// It uses the EVP_BytesToKey() method which is basically:
// D_i = HASH^count(D_(i-1) || password || salt) where || denotes concatentaion, until there are sufficient bytes available
// 48 bytes since we're expecting to handle AES-256, 32bytes for a key and 16bytes for the IV
func (o OpenSSL) extractOpenSSLCreds(password, salt []byte) (openSSLCreds, error) {
m := make([]byte, 48)
prev := []byte{}
for i := 0; i < 3; i++ {
prev = o.hash(prev, password, salt)
copy(m[i*16:], prev)
}
return openSSLCreds{key: m[:32], iv: m[32:]}, nil
}
func (o OpenSSL) hash(prev, password, salt []byte) []byte {
a := make([]byte, len(prev)+len(password)+len(salt))
copy(a, prev)
copy(a[len(prev):], password)
copy(a[len(prev)+len(password):], salt)
return o.md5sum(a)
}
func (o *OpenSSL) md5sum(data []byte) []byte {
h := md5.New()
h.Write(data)
return h.Sum(nil)
}
// pkcs7Pad appends padding.
func (o OpenSSL) pkcs7Pad(data []byte, blocklen int) ([]byte, error) {
if blocklen <= 0 {
return nil, fmt.Errorf("invalid blocklen %d", blocklen)
}
padlen := 1
for ((len(data) + padlen) % blocklen) != 0 {
padlen = padlen + 1
}
pad := bytes.Repeat([]byte{byte(padlen)}, padlen)
return append(data, pad...), nil
}
// pkcs7Unpad returns slice of the original data without padding.
func (o OpenSSL) pkcs7Unpad(data []byte, blocklen int) ([]byte, error) {
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
}