mirror of
https://github.com/Luzifer/go-openssl.git
synced 2024-12-29 23:31:21 +00:00
Add encrypt/decrypt without base64 encoding
The DecryptBinaryBytes, EncryptBinaryBytes* methods allow the data being decrypted/encrypted to be in a binary form, not base64. Addresses issue #14. Signed-off-by: Owen McGill <mcgillo.rnx@gmail.com>
This commit is contained in:
parent
8aab137b28
commit
9b5d475a0b
2 changed files with 247 additions and 20 deletions
87
openssl.go
87
openssl.go
|
@ -80,10 +80,24 @@ func (o OpenSSL) DecryptBytes(passphrase string, encryptedBase64Data []byte, kdf
|
||||||
// Truncate to real message length
|
// Truncate to real message length
|
||||||
data = data[0:n]
|
data = data[0:n]
|
||||||
|
|
||||||
if len(data) < aes.BlockSize {
|
decrypted, err := o.DecryptBinaryBytes(passphrase, data, kdf)
|
||||||
|
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.
|
||||||
|
func (o OpenSSL) DecryptBinaryBytes(passphrase string, encryptedData []byte, kdf DigestFunc) ([]byte, error) {
|
||||||
|
if len(encryptedData) < aes.BlockSize {
|
||||||
return nil, fmt.Errorf("Data is too short")
|
return nil, fmt.Errorf("Data is too short")
|
||||||
}
|
}
|
||||||
saltHeader := data[:aes.BlockSize]
|
saltHeader := encryptedData[:aes.BlockSize]
|
||||||
if string(saltHeader[:8]) != o.openSSLSaltHeader {
|
if string(saltHeader[:8]) != o.openSSLSaltHeader {
|
||||||
return nil, fmt.Errorf("Does not appear to have been encrypted with OpenSSL, salt header missing")
|
return nil, fmt.Errorf("Does not appear to have been encrypted with OpenSSL, salt header missing")
|
||||||
}
|
}
|
||||||
|
@ -93,7 +107,7 @@ func (o OpenSSL) DecryptBytes(passphrase string, encryptedBase64Data []byte, kdf
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return o.decrypt(creds.key, creds.iv, data)
|
return o.decrypt(creds.key, creds.iv, encryptedData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o OpenSSL) decrypt(key, iv, data []byte) ([]byte, error) {
|
func (o OpenSSL) decrypt(key, iv, data []byte) ([]byte, error) {
|
||||||
|
@ -113,7 +127,7 @@ func (o OpenSSL) decrypt(key, iv, data []byte) ([]byte, error) {
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptBytes encrypts a slice of bytes in a manner compatible to OpenSSL encryption
|
// EncryptBytes encrypts a slice of bytes that are base64 encoded in a manner compatible to OpenSSL encryption
|
||||||
// functions using AES-256-CBC as encryption algorithm. This function generates
|
// functions using AES-256-CBC as encryption algorithm. This function generates
|
||||||
// a random salt on every execution.
|
// a random salt on every execution.
|
||||||
func (o OpenSSL) EncryptBytes(passphrase string, plainData []byte, kdf DigestFunc) ([]byte, error) {
|
func (o OpenSSL) EncryptBytes(passphrase string, plainData []byte, kdf DigestFunc) ([]byte, error) {
|
||||||
|
@ -125,7 +139,19 @@ func (o OpenSSL) EncryptBytes(passphrase string, plainData []byte, kdf DigestFun
|
||||||
return o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, plainData, kdf)
|
return o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, plainData, kdf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptBytesWithSaltAndDigestFunc encrypts a slice of bytes in a manner compatible to OpenSSL
|
// 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.
|
||||||
|
func (o OpenSSL) EncryptBinaryBytes(passphrase string, plainData []byte, kdf DigestFunc) ([]byte, error) {
|
||||||
|
salt, err := o.GenerateSalt()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, plainData, kdf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptBytesWithSaltAndDigestFunc encrypts a slice of bytes that are base64 encoded in a manner compatible to OpenSSL
|
||||||
// encryption functions using AES-256-CBC as encryption algorithm. The salt
|
// 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
|
// needs to be passed in here which ensures the same result on every execution
|
||||||
// on cost of a much weaker encryption as with EncryptString.
|
// on cost of a much weaker encryption as with EncryptString.
|
||||||
|
@ -138,21 +164,7 @@ func (o OpenSSL) EncryptBytes(passphrase string, plainData []byte, kdf DigestFun
|
||||||
// If you don't have a good reason to use this, please don't! For more information
|
// 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
|
// see this: https://en.wikipedia.org/wiki/Salt_(cryptography)#Common_mistakes
|
||||||
func (o OpenSSL) EncryptBytesWithSaltAndDigestFunc(passphrase string, salt, plainData []byte, hashFunc DigestFunc) ([]byte, error) {
|
func (o OpenSSL) EncryptBytesWithSaltAndDigestFunc(passphrase string, salt, plainData []byte, hashFunc DigestFunc) ([]byte, error) {
|
||||||
if len(salt) != 8 {
|
enc, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, plainData, hashFunc)
|
||||||
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, hashFunc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
enc, err := o.encrypt(creds.key, creds.iv, data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -176,6 +188,41 @@ func (o OpenSSL) encrypt(key, iv, data []byte) ([]byte, error) {
|
||||||
return padded, nil
|
return padded, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
func (o OpenSSL) EncryptBinaryBytesWithSaltAndDigestFunc(passphrase string, salt, plainData []byte, hashFunc DigestFunc) ([]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, hashFunc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
enc, err := o.encrypt(creds.key, creds.iv, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return enc, nil
|
||||||
|
}
|
||||||
|
|
||||||
// openSSLEvpBytesToKey follows the OpenSSL (undocumented?) convention for extracting the key and IV from passphrase.
|
// openSSLEvpBytesToKey follows the OpenSSL (undocumented?) convention for extracting the key and IV from passphrase.
|
||||||
// It uses the EVP_BytesToKey() method which is basically:
|
// 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
|
// D_i = HASH^count(D_(i-1) || password || salt) where || denotes concatentaion, until there are sufficient bytes available
|
||||||
|
|
180
openssl_test.go
180
openssl_test.go
|
@ -67,6 +67,60 @@ func TestDecryptFromStringSHA256(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDecryptBinaryFromString(t *testing.T) {
|
||||||
|
|
||||||
|
plaintext := "hallowelt"
|
||||||
|
passphrase := "z4yH36a6zerhfE5427ZV"
|
||||||
|
|
||||||
|
testtable :=
|
||||||
|
[]struct {
|
||||||
|
tname string
|
||||||
|
tMdParam string
|
||||||
|
tMdFunc DigestFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
tname: "MD5",
|
||||||
|
tMdParam: "md5",
|
||||||
|
tMdFunc: DigestMD5Sum,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tname: "SHA1",
|
||||||
|
tMdParam: "sha1",
|
||||||
|
tMdFunc: DigestSHA1Sum,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tname: "SHA256",
|
||||||
|
tMdParam: "sha256",
|
||||||
|
tMdFunc: DigestSHA256Sum,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
o := New()
|
||||||
|
|
||||||
|
for _, tc := range testtable {
|
||||||
|
t.Run(tc.tname, func(t *testing.T) {
|
||||||
|
cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("echo -n \"%s\" | openssl aes-256-cbc -pass pass:%s -md %s", plaintext, passphrase, tc.tMdParam))
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Running openssl CLI failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := o.DecryptBinaryBytes(passphrase, out.Bytes(), tc.tMdFunc)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Decryption failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(data) != plaintext {
|
||||||
|
t.Logf("Data: %s\nPlaintext: %s", string(data), plaintext)
|
||||||
|
t.Errorf("Decryption output did not equal expected output.")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEncryptToDecrypt(t *testing.T) {
|
func TestEncryptToDecrypt(t *testing.T) {
|
||||||
plaintext := "hallowelt"
|
plaintext := "hallowelt"
|
||||||
passphrase := "z4yH36a6zerhfE5427ZV"
|
passphrase := "z4yH36a6zerhfE5427ZV"
|
||||||
|
@ -88,6 +142,27 @@ func TestEncryptToDecrypt(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBinaryEncryptToDecrypt(t *testing.T) {
|
||||||
|
plaintext := "hallowelt"
|
||||||
|
passphrase := "z4yH36a6zerhfE5427ZV"
|
||||||
|
|
||||||
|
o := New()
|
||||||
|
|
||||||
|
enc, err := o.EncryptBinaryBytes(passphrase, []byte(plaintext), DigestSHA256Sum)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test errored at encrypt: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dec, err := o.DecryptBinaryBytes(passphrase, enc, DigestSHA256Sum)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test errored at decrypt: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(dec) != plaintext {
|
||||||
|
t.Errorf("Decrypted text did not match input.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEncryptToDecryptWithCustomSalt(t *testing.T) {
|
func TestEncryptToDecryptWithCustomSalt(t *testing.T) {
|
||||||
plaintext := "hallowelt"
|
plaintext := "hallowelt"
|
||||||
passphrase := "z4yH36a6zerhfE5427ZV"
|
passphrase := "z4yH36a6zerhfE5427ZV"
|
||||||
|
@ -110,6 +185,28 @@ func TestEncryptToDecryptWithCustomSalt(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBinaryEncryptToDecryptWithCustomSalt(t *testing.T) {
|
||||||
|
plaintext := "hallowelt"
|
||||||
|
passphrase := "z4yH36a6zerhfE5427ZV"
|
||||||
|
salt := []byte("saltsalt")
|
||||||
|
|
||||||
|
o := New()
|
||||||
|
|
||||||
|
enc, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), DigestSHA256Sum)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test errored at encrypt: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dec, err := o.DecryptBinaryBytes(passphrase, enc, DigestSHA256Sum)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test errored at decrypt: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(dec) != plaintext {
|
||||||
|
t.Errorf("Decrypted text did not match input.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEncryptWithSaltShouldHaveSameOutput(t *testing.T) {
|
func TestEncryptWithSaltShouldHaveSameOutput(t *testing.T) {
|
||||||
plaintext := "outputshouldbesame"
|
plaintext := "outputshouldbesame"
|
||||||
passphrase := "passphrasesupersecure"
|
passphrase := "passphrasesupersecure"
|
||||||
|
@ -132,6 +229,28 @@ func TestEncryptWithSaltShouldHaveSameOutput(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBinaryEncryptWithSaltShouldHaveSameOutput(t *testing.T) {
|
||||||
|
plaintext := "outputshouldbesame"
|
||||||
|
passphrase := "passphrasesupersecure"
|
||||||
|
salt := []byte("saltsalt")
|
||||||
|
|
||||||
|
o := New()
|
||||||
|
|
||||||
|
enc1, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), DigestSHA256Sum)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test errored at encrypt: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
enc2, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), DigestSHA256Sum)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test errored at encrypt: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(enc1) != string(enc2) {
|
||||||
|
t.Errorf("Encrypted outputs are not same.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEncryptToOpenSSL(t *testing.T) {
|
func TestEncryptToOpenSSL(t *testing.T) {
|
||||||
plaintext := "hallowelt"
|
plaintext := "hallowelt"
|
||||||
passphrase := "z4yH36a6zerhfE5427ZV"
|
passphrase := "z4yH36a6zerhfE5427ZV"
|
||||||
|
@ -172,6 +291,67 @@ func TestEncryptToOpenSSL(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBinaryEncryptToOpenSSL(t *testing.T) {
|
||||||
|
plaintext := "hallowelt"
|
||||||
|
passphrase := "z4yH36a6zerhfE5427ZV"
|
||||||
|
|
||||||
|
testtable :=
|
||||||
|
[]struct {
|
||||||
|
tname string
|
||||||
|
tMdParam string
|
||||||
|
tMdFunc DigestFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
tname: "MD5",
|
||||||
|
tMdParam: "md5",
|
||||||
|
tMdFunc: DigestMD5Sum,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tname: "SHA1",
|
||||||
|
tMdParam: "sha1",
|
||||||
|
tMdFunc: DigestSHA1Sum,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tname: "SHA256",
|
||||||
|
tMdParam: "sha256",
|
||||||
|
tMdFunc: DigestSHA256Sum,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
o := New()
|
||||||
|
|
||||||
|
for _, tc := range testtable {
|
||||||
|
t.Run(tc.tname, func(t *testing.T) {
|
||||||
|
salt, err := o.GenerateSalt()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to generate salt: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
enc, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), tc.tMdFunc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test errored at encrypt: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to specify /dev/stdin as file so that we can pass in binary
|
||||||
|
// data to openssl without creating a file
|
||||||
|
cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("openssl aes-256-cbc -pass pass:%s -md %s -d -in /dev/stdin", passphrase, tc.tMdParam))
|
||||||
|
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
cmd.Stdin = bytes.NewBuffer(enc)
|
||||||
|
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("OpenSSL errored: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.String() != plaintext {
|
||||||
|
t.Errorf("OpenSSL output did not match input.\nOutput was: %s", out.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGenerateSalt(t *testing.T) {
|
func TestGenerateSalt(t *testing.T) {
|
||||||
knownSalts := [][]byte{}
|
knownSalts := [][]byte{}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue