1
0
Fork 0
mirror of https://github.com/Luzifer/go-openssl.git synced 2024-12-20 19:01:18 +00:00

Breaking: Implement PBKFD2 key derivation (#18)

This commit is contained in:
Knut Ahlers 2020-06-13 15:29:46 +02:00 committed by GitHub
parent 3eef5a5be8
commit 0d93c15cab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 287 additions and 119 deletions

View file

@ -1,9 +1,9 @@
dist: bionic
language: go language: go
go: go:
- 1.10.x - 1.13.x
- 1.11.x - 1.14.x
- 1.12.x
- tip - tip
script: script:

View file

@ -22,7 +22,7 @@ Starting with `v2.0.0` `go-openssl` generates the encryption keys using `sha256s
### 1.1.1 ### 1.1.1
The PBKDF2 key derivation is not yet supported. Starting with `v4.0.0` `go-openssl` is capable of using the PBKDF2 key derivation method for encryption. You can choose to use it by passing the corresponding `CredsGenerator`.
## Installation ## Installation
@ -31,7 +31,7 @@ The PBKDF2 key derivation is not yet supported.
go get github.com/Luzifer/go-openssl go get github.com/Luzifer/go-openssl
# OR get a specific version # OR get a specific version
go get gopkg.in/Luzifer/go-openssl.v3 go get gopkg.in/Luzifer/go-openssl.v4
``` ```
## Usage example ## Usage example
@ -43,7 +43,7 @@ The usage is quite simple as you don't need any special knowledge about OpenSSL
```go ```go
import ( import (
"fmt" "fmt"
openssl "gopkg.in/Luzifer/go-openssl.v3" openssl "gopkg.in/Luzifer/go-openssl.v4"
) )
func main() { func main() {
@ -52,7 +52,7 @@ func main() {
o := openssl.New() o := openssl.New()
enc, err := o.EncryptBytes(passphrase, []byte(plaintext), DigestSHA256Sum) enc, err := o.EncryptBytes(passphrase, []byte(plaintext), PBKDF2SHA256)
if err != nil { if err != nil {
fmt.Printf("An error occurred: %s\n", err) fmt.Printf("An error occurred: %s\n", err)
} }
@ -66,7 +66,7 @@ func main() {
```go ```go
import ( import (
"fmt" "fmt"
openssl "gopkg.in/Luzifer/go-openssl.v3" openssl "gopkg.in/Luzifer/go-openssl.v4"
) )
func main() { func main() {
@ -75,7 +75,7 @@ func main() {
o := openssl.New() o := openssl.New()
dec, err := o.DecryptBytes(passphrase, []byte(opensslEncrypted), DigestMD5Sum) dec, err := o.DecryptBytes(passphrase, []byte(opensslEncrypted), BytesToKeyMD5)
if err != nil { if err != nil {
fmt.Printf("An error occurred: %s\n", err) fmt.Printf("An error occurred: %s\n", err)
} }

View file

@ -8,7 +8,7 @@ func ExampleOpenSSL_EncryptBytes() {
o := New() o := New()
enc, err := o.EncryptBytes(passphrase, []byte(plaintext), DigestSHA256Sum) enc, err := o.EncryptBytes(passphrase, []byte(plaintext), PBKDF2SHA256)
if err != nil { if err != nil {
fmt.Printf("An error occurred: %s\n", err) fmt.Printf("An error occurred: %s\n", err)
} }
@ -22,7 +22,7 @@ func ExampleOpenSSL_DecryptBytes() {
o := New() o := New()
dec, err := o.DecryptBytes(passphrase, []byte(opensslEncrypted), DigestMD5Sum) dec, err := o.DecryptBytes(passphrase, []byte(opensslEncrypted), BytesToKeyMD5)
if err != nil { if err != nil {
fmt.Printf("An error occurred: %s\n", err) fmt.Printf("An error occurred: %s\n", err)
} }

6
go.mod
View file

@ -1 +1,5 @@
module github.com/Luzifer/go-openssl/v3 module github.com/Luzifer/go-openssl/v4
go 1.14
require golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9

58
keys.go Normal file
View file

@ -0,0 +1,58 @@
package openssl
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"hash"
"golang.org/x/crypto/pbkdf2"
)
const DefaultPBKDF2Iterations = 10000
// CredsGenerator are functions to derive a key and iv from a password and a salt
type CredsGenerator func(password, salt []byte) (Creds, error)
var (
BytesToKeyMD5 = NewBytesToKeyGenerator(md5.New)
BytesToKeySHA1 = NewBytesToKeyGenerator(sha1.New)
BytesToKeySHA256 = NewBytesToKeyGenerator(sha256.New)
PBKDF2MD5 = NewPBKDF2Generator(md5.New, DefaultPBKDF2Iterations)
PBKDF2SHA1 = NewPBKDF2Generator(sha1.New, DefaultPBKDF2Iterations)
PBKDF2SHA256 = NewPBKDF2Generator(sha256.New, DefaultPBKDF2Iterations)
)
// 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 NewBytesToKeyGenerator(hashFunc func() hash.Hash) CredsGenerator {
df := func(in []byte) []byte {
h := hashFunc()
h.Write(in)
return h.Sum(nil)
}
return func(password, salt []byte) (Creds, error) {
var m []byte
prev := []byte{}
for len(m) < 48 {
a := make([]byte, len(prev)+len(password)+len(salt))
copy(a, prev)
copy(a[len(prev):], password)
copy(a[len(prev)+len(password):], salt)
prev = df(a)
m = append(m, prev...)
}
return Creds{Key: m[:32], IV: m[32:48]}, nil
}
}
func NewPBKDF2Generator(hashFunc func() hash.Hash, iterations int) CredsGenerator {
return func(password, salt []byte) (Creds, error) {
m := pbkdf2.Key(password, salt, iterations, 32+16, hashFunc)
return Creds{Key: m[:32], IV: m[32:48]}, nil
}
}

106
keys_test.go Normal file
View file

@ -0,0 +1,106 @@
package openssl
import (
"crypto/sha256"
"testing"
)
func TestBytesToKeyGenerator(t *testing.T) {
var (
pass = []byte("myverysecretpass")
salt = []byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}
)
for name, tc := range map[string]struct {
CG CredsGenerator
OC Creds
}{
"MD5": {CG: BytesToKeyMD5, OC: Creds{
// # echo "" | openssl enc -e -P -aes-256-cbc -pass "pass:myverysecretpass" -S 0001020304050607 -md md5
// salt=0001020304050607
// key=7434342C270FA039438DA7B2898C6B3CA936DCE3D2705E805DA2987E5808CC06
// iv =E20BB8B5CCBC1405705734ACCE1040A9
Key: []uint8{0x74, 0x34, 0x34, 0x2C, 0x27, 0x0F, 0xA0, 0x39, 0x43, 0x8D, 0xA7, 0xB2, 0x89, 0x8C, 0x6B, 0x3C, 0xA9, 0x36, 0xDC, 0xE3, 0xD2, 0x70, 0x5E, 0x80, 0x5D, 0xA2, 0x98, 0x7E, 0x58, 0x08, 0xCC, 0x06},
IV: []uint8{0xE2, 0x0B, 0xB8, 0xB5, 0xCC, 0xBC, 0x14, 0x05, 0x70, 0x57, 0x34, 0xAC, 0xCE, 0x10, 0x40, 0xA9},
}},
"SHA1": {CG: BytesToKeySHA1, OC: Creds{
// # echo "" | openssl enc -e -P -aes-256-cbc -pass "pass:myverysecretpass" -S 0001020304050607 -md sha1
// salt=0001020304050607
// key=186718DE0173029146A45CE44CD5D95224DDE0CC3DA63412B5BA41F4AB4B9927
// iv =5AE1C3D9ACE659D309842CFF32A8D18B
Key: []uint8{0x18, 0x67, 0x18, 0xDE, 0x01, 0x73, 0x02, 0x91, 0x46, 0xA4, 0x5C, 0xE4, 0x4C, 0xD5, 0xD9, 0x52, 0x24, 0xDD, 0xE0, 0xCC, 0x3D, 0xA6, 0x34, 0x12, 0xB5, 0xBA, 0x41, 0xF4, 0xAB, 0x4B, 0x99, 0x27},
IV: []uint8{0x5A, 0xE1, 0xC3, 0xD9, 0xAC, 0xE6, 0x59, 0xD3, 0x09, 0x84, 0x2C, 0xFF, 0x32, 0xA8, 0xD1, 0x8B},
}},
"SHA256": {CG: BytesToKeySHA256, OC: Creds{
// # echo "" | openssl enc -e -P -aes-256-cbc -pass "pass:myverysecretpass" -S 0001020304050607 -md sha256
// salt=0001020304050607
// key=C309EE4C6809DF8C0137F80D8409DAC2C8C4E054349D17DDC1D6390C3999070B
// iv =D3411C53B5C49FB339690EAC86D07107
Key: []uint8{0xC3, 0x09, 0xEE, 0x4C, 0x68, 0x09, 0xDF, 0x8C, 0x01, 0x37, 0xF8, 0x0D, 0x84, 0x09, 0xDA, 0xC2, 0xC8, 0xC4, 0xE0, 0x54, 0x34, 0x9D, 0x17, 0xDD, 0xC1, 0xD6, 0x39, 0x0C, 0x39, 0x99, 0x07, 0x0B},
IV: []uint8{0xD3, 0x41, 0x1C, 0x53, 0xB5, 0xC4, 0x9F, 0xB3, 0x39, 0x69, 0x0E, 0xAC, 0x86, 0xD0, 0x71, 0x07},
}},
} {
res, err := tc.CG(pass, salt)
if err != nil {
t.Fatalf("Generator %s caused an error: %s", name, err)
}
if !res.equals(tc.OC) {
t.Errorf("Generator %s yielded unexpected result: exp=%#v res=%#v", name, tc.OC, res)
}
}
}
func TestPBKDF2Generator(t *testing.T) {
var (
pass = []byte("myverysecretpass")
salt = []byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}
)
for name, tc := range map[string]struct {
CG CredsGenerator
OC Creds
}{
"MD5": {CG: PBKDF2MD5, OC: Creds{
// # echo "" | openssl enc -e -P -pbkdf2 -aes-256-cbc -pass "pass:myverysecretpass" -S 0001020304050607 -md md5
// salt=0001020304050607
// key=C5D1C98445902BD0515C105C25C88DA7243B79B2D67FE1CC978397BEDC526237
// iv =F34AEAD261AAB8C16067D90A29275676
Key: []uint8{0xC5, 0xD1, 0xC9, 0x84, 0x45, 0x90, 0x2B, 0xD0, 0x51, 0x5C, 0x10, 0x5C, 0x25, 0xC8, 0x8D, 0xA7, 0x24, 0x3B, 0x79, 0xB2, 0xD6, 0x7F, 0xE1, 0xCC, 0x97, 0x83, 0x97, 0xBE, 0xDC, 0x52, 0x62, 0x37},
IV: []uint8{0xF3, 0x4A, 0xEA, 0xD2, 0x61, 0xAA, 0xB8, 0xC1, 0x60, 0x67, 0xD9, 0x0A, 0x29, 0x27, 0x56, 0x76},
}},
"SHA1": {CG: PBKDF2SHA1, OC: Creds{
// # echo "" | openssl enc -e -P -pbkdf2 -aes-256-cbc -pass "pass:myverysecretpass" -S 0001020304050607 -md sha1
// salt=0001020304050607
// key=EAE7B36DEAA01F34894722C1EBA856B5DB6FF5C34CFBDC8774B259DA9CB44837
// iv =4496482B39B410D8B2AB582FB0993D7D
Key: []uint8{0xEA, 0xE7, 0xB3, 0x6D, 0xEA, 0xA0, 0x1F, 0x34, 0x89, 0x47, 0x22, 0xC1, 0xEB, 0xA8, 0x56, 0xB5, 0xDB, 0x6F, 0xF5, 0xC3, 0x4C, 0xFB, 0xDC, 0x87, 0x74, 0xB2, 0x59, 0xDA, 0x9C, 0xB4, 0x48, 0x37},
IV: []uint8{0x44, 0x96, 0x48, 0x2B, 0x39, 0xB4, 0x10, 0xD8, 0xB2, 0xAB, 0x58, 0x2F, 0xB0, 0x99, 0x3D, 0x7D},
}},
"SHA256": {CG: PBKDF2SHA256, OC: Creds{
// # echo "" | openssl enc -e -P -pbkdf2 -aes-256-cbc -pass "pass:myverysecretpass" -S 0001020304050607 -md sha256
// salt=0001020304050607
// key=A1B5D01BF7C1A1A0BF7659850C68ADD40E1CDF6B2D603EBD03673CED1C5AF032
// iv =7DC52677DEF3D4B6D9A644209F42AE26
Key: []uint8{0xA1, 0xB5, 0xD0, 0x1B, 0xF7, 0xC1, 0xA1, 0xA0, 0xBF, 0x76, 0x59, 0x85, 0x0C, 0x68, 0xAD, 0xD4, 0x0E, 0x1C, 0xDF, 0x6B, 0x2D, 0x60, 0x3E, 0xBD, 0x03, 0x67, 0x3C, 0xED, 0x1C, 0x5A, 0xF0, 0x32},
IV: []uint8{0x7D, 0xC5, 0x26, 0x77, 0xDE, 0xF3, 0xD4, 0xB6, 0xD9, 0xA6, 0x44, 0x20, 0x9F, 0x42, 0xAE, 0x26},
}},
"SHA256_25k": {CG: NewPBKDF2Generator(sha256.New, 25000), OC: Creds{
// # echo "" | openssl enc -e -P -pbkdf2 -aes-256-cbc -pass "pass:myverysecretpass" -S 0001020304050607 -md sha256 -iter 25000
// salt=0001020304050607
// key=2D6C8A525CC457FF1C7CA1E8F366FEE441CD80562AF6AD12A6B7033C12BA0514
// iv =F10F5FAE49D9A74C104BFF8346DDEB0C
Key: []uint8{0x2D, 0x6C, 0x8A, 0x52, 0x5C, 0xC4, 0x57, 0xFF, 0x1C, 0x7C, 0xA1, 0xE8, 0xF3, 0x66, 0xFE, 0xE4, 0x41, 0xCD, 0x80, 0x56, 0x2A, 0xF6, 0xAD, 0x12, 0xA6, 0xB7, 0x03, 0x3C, 0x12, 0xBA, 0x05, 0x14},
IV: []uint8{0xF1, 0x0F, 0x5F, 0xAE, 0x49, 0xD9, 0xA7, 0x4C, 0x10, 0x4B, 0xFF, 0x83, 0x46, 0xDD, 0xEB, 0x0C},
}},
} {
res, err := tc.CG(pass, salt)
if err != nil {
t.Fatalf("Generator %s caused an error: %s", name, err)
}
if !res.equals(tc.OC) {
t.Errorf("Generator %s yielded unexpected result: exp=%#v res=%#v", name, tc.OC, res)
}
}
}

View file

@ -4,19 +4,13 @@ import (
"bytes" "bytes"
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/md5"
"crypto/rand" "crypto/rand"
"crypto/sha1"
"crypto/sha256"
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"io" "io"
) )
// CurrentOpenSSLDigestFunc is an alias to the key derivation function used in OpenSSL
var CurrentOpenSSLDigestFunc = DigestSHA256Sum
// ErrInvalidSalt is returned when a salt with a length of != 8 byte is passed // ErrInvalidSalt is returned when a salt with a length of != 8 byte is passed
var ErrInvalidSalt = errors.New("Salt needs to have exactly 8 byte") var ErrInvalidSalt = errors.New("Salt needs to have exactly 8 byte")
@ -28,9 +22,33 @@ type OpenSSL struct {
openSSLSaltHeader string openSSLSaltHeader string
} }
type openSSLCreds struct { // Creds holds a key and an IV for encryption methods
key []byte type Creds struct {
iv []byte Key []byte
IV []byte
}
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
}
// Compare keys
for j := 0; j < len(o.Key); j++ {
if o.Key[j] != i.Key[j] {
return false
}
}
// Compare IV
for j := 0; j < len(o.IV); j++ {
if o.IV[j] != i.IV[j] {
return false
}
}
return true
} }
// New instanciates and initializes a new OpenSSL encrypter // New instanciates and initializes a new OpenSSL encrypter
@ -40,37 +58,13 @@ func New() *OpenSSL {
} }
} }
// DigestFunc are functions to create a key from the passphrase
type DigestFunc func([]byte) []byte
// DigestMD5Sum uses the (deprecated) pre-OpenSSL 1.1.0c MD5 digest to create the key
func DigestMD5Sum(data []byte) []byte {
h := md5.New()
h.Write(data)
return h.Sum(nil)
}
// DigestSHA1Sum uses SHA1 digest to create the key
func DigestSHA1Sum(data []byte) []byte {
h := sha1.New()
h.Write(data)
return h.Sum(nil)
}
// DigestSHA256Sum uses SHA256 digest to create the key which is the default behaviour since OpenSSL 1.1.0c
func DigestSHA256Sum(data []byte) []byte {
h := sha256.New()
h.Write(data)
return h.Sum(nil)
}
// DecryptBytes takes a slice of bytes with base64 encoded, encrypted data to decrypt // DecryptBytes takes a slice of bytes with base64 encoded, encrypted data to decrypt
// and a key-derivation function. The key-derivation function must match the function // 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.) // 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 // 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. // condition and you will not be able to decrypt your data properly.
func (o OpenSSL) DecryptBytes(passphrase string, encryptedBase64Data []byte, kdf DigestFunc) ([]byte, error) { func (o OpenSSL) DecryptBytes(passphrase string, encryptedBase64Data []byte, cg CredsGenerator) ([]byte, error) {
data := make([]byte, base64.StdEncoding.DecodedLen(len(encryptedBase64Data))) data := make([]byte, base64.StdEncoding.DecodedLen(len(encryptedBase64Data)))
n, err := base64.StdEncoding.Decode(data, encryptedBase64Data) n, err := base64.StdEncoding.Decode(data, encryptedBase64Data)
if err != nil { if err != nil {
@ -80,7 +74,7 @@ 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]
decrypted, err := o.DecryptBinaryBytes(passphrase, data, kdf) decrypted, err := o.DecryptBinaryBytes(passphrase, data, cg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -93,7 +87,7 @@ func (o OpenSSL) DecryptBytes(passphrase string, encryptedBase64Data []byte, kdf
// //
// You should not just try to loop the digest functions as this will cause a race // 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. // condition and you will not be able to decrypt your data properly.
func (o OpenSSL) DecryptBinaryBytes(passphrase string, encryptedData []byte, kdf DigestFunc) ([]byte, error) { func (o OpenSSL) DecryptBinaryBytes(passphrase string, encryptedData []byte, cg CredsGenerator) ([]byte, error) {
if len(encryptedData) < aes.BlockSize { if len(encryptedData) < aes.BlockSize {
return nil, fmt.Errorf("Data is too short") return nil, fmt.Errorf("Data is too short")
} }
@ -103,11 +97,11 @@ func (o OpenSSL) DecryptBinaryBytes(passphrase string, encryptedData []byte, kdf
} }
salt := saltHeader[8:] salt := saltHeader[8:]
creds, err := o.extractOpenSSLCreds([]byte(passphrase), salt, kdf) creds, err := cg([]byte(passphrase), salt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return o.decrypt(creds.key, creds.iv, encryptedData) 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) {
@ -130,25 +124,25 @@ func (o OpenSSL) decrypt(key, iv, data []byte) ([]byte, error) {
// EncryptBytes encrypts a slice of bytes that are base64 encoded 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, cg CredsGenerator) ([]byte, error) {
salt, err := o.GenerateSalt() salt, err := o.GenerateSalt()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, plainData, kdf) return o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, plainData, cg)
} }
// EncryptBinaryBytes encrypts a slice of bytes in a manner compatible to OpenSSL encryption // EncryptBinaryBytes encrypts a slice of bytes 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) EncryptBinaryBytes(passphrase string, plainData []byte, kdf DigestFunc) ([]byte, error) { func (o OpenSSL) EncryptBinaryBytes(passphrase string, plainData []byte, cg CredsGenerator) ([]byte, error) {
salt, err := o.GenerateSalt() salt, err := o.GenerateSalt()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, plainData, kdf) return o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, plainData, cg)
} }
// EncryptBytesWithSaltAndDigestFunc encrypts a slice of bytes that are base64 encoded in a manner compatible to OpenSSL // EncryptBytesWithSaltAndDigestFunc encrypts a slice of bytes that are base64 encoded in a manner compatible to OpenSSL
@ -163,8 +157,8 @@ func (o OpenSSL) EncryptBinaryBytes(passphrase string, plainData []byte, kdf Dig
// //
// 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, cg CredsGenerator) ([]byte, error) {
enc, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, plainData, hashFunc) enc, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, plainData, cg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -200,7 +194,7 @@ func (o OpenSSL) encrypt(key, iv, data []byte) ([]byte, error) {
// //
// 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) EncryptBinaryBytesWithSaltAndDigestFunc(passphrase string, salt, plainData []byte, hashFunc DigestFunc) ([]byte, error) { func (o OpenSSL) EncryptBinaryBytesWithSaltAndDigestFunc(passphrase string, salt, plainData []byte, cg CredsGenerator) ([]byte, error) {
if len(salt) != 8 { if len(salt) != 8 {
return nil, ErrInvalidSalt return nil, ErrInvalidSalt
} }
@ -210,12 +204,12 @@ func (o OpenSSL) EncryptBinaryBytesWithSaltAndDigestFunc(passphrase string, salt
copy(data[8:], salt) copy(data[8:], salt)
copy(data[aes.BlockSize:], plainData) copy(data[aes.BlockSize:], plainData)
creds, err := o.extractOpenSSLCreds([]byte(passphrase), salt, hashFunc) creds, err := cg([]byte(passphrase), salt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
enc, err := o.encrypt(creds.key, creds.iv, data) enc, err := o.encrypt(creds.Key, creds.IV, data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -223,28 +217,6 @@ func (o OpenSSL) EncryptBinaryBytesWithSaltAndDigestFunc(passphrase string, salt
return enc, nil return enc, 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, hashFunc DigestFunc) (openSSLCreds, error) {
var m []byte
prev := []byte{}
for len(m) < 48 {
prev = o.hash(prev, password, salt, hashFunc)
m = append(m, prev...)
}
return openSSLCreds{key: m[:32], iv: m[32:48]}, nil
}
func (o OpenSSL) hash(prev, password, salt []byte, hashFunc DigestFunc) []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 hashFunc(a)
}
// GenerateSalt generates a random 8 byte salt // GenerateSalt generates a random 8 byte salt
func (o OpenSSL) GenerateSalt() ([]byte, error) { func (o OpenSSL) GenerateSalt() ([]byte, error) {
salt := make([]byte, 8) // Generate an 8 byte salt salt := make([]byte, 8) // Generate an 8 byte salt
@ -273,7 +245,7 @@ func (o OpenSSL) pkcs7Pad(data []byte, blocklen int) ([]byte, error) {
} }
padlen := 1 padlen := 1
for ((len(data) + padlen) % blocklen) != 0 { for ((len(data) + padlen) % blocklen) != 0 {
padlen = padlen + 1 padlen++
} }
pad := bytes.Repeat([]byte{byte(padlen)}, padlen) pad := bytes.Repeat([]byte{byte(padlen)}, padlen)

View file

@ -11,11 +11,15 @@ import (
var testTable = []struct { var testTable = []struct {
tName string tName string
tMdParam string tMdParam string
tMdFunc DigestFunc tMdFunc CredsGenerator
tPBKDF bool
}{ }{
{"MD5", "md5", DigestMD5Sum}, {"MD5", "md5", BytesToKeyMD5, false},
{"SHA1", "sha1", DigestSHA1Sum}, {"SHA1", "sha1", BytesToKeySHA1, false},
{"SHA256", "sha256", DigestSHA256Sum}, {"SHA256", "sha256", BytesToKeySHA256, false},
{"PBKDF2_MD5", "md5", PBKDF2MD5, true},
{"PBKDF2_SHA1", "sha1", PBKDF2SHA1, true},
{"PBKDF2_SHA256", "sha256", PBKDF2SHA256, true},
} }
func TestBinaryEncryptToDecryptWithCustomSalt(t *testing.T) { func TestBinaryEncryptToDecryptWithCustomSalt(t *testing.T) {
@ -25,12 +29,12 @@ func TestBinaryEncryptToDecryptWithCustomSalt(t *testing.T) {
o := New() o := New()
enc, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), DigestSHA256Sum) enc, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), BytesToKeySHA256)
if err != nil { if err != nil {
t.Fatalf("Test errored at encrypt: %s", err) t.Fatalf("Test errored at encrypt: %s", err)
} }
dec, err := o.DecryptBinaryBytes(passphrase, enc, DigestSHA256Sum) dec, err := o.DecryptBinaryBytes(passphrase, enc, BytesToKeySHA256)
if err != nil { if err != nil {
t.Fatalf("Test errored at decrypt: %s", err) t.Fatalf("Test errored at decrypt: %s", err)
} }
@ -46,12 +50,12 @@ func TestBinaryEncryptToDecrypt(t *testing.T) {
o := New() o := New()
enc, err := o.EncryptBinaryBytes(passphrase, []byte(plaintext), DigestSHA256Sum) enc, err := o.EncryptBinaryBytes(passphrase, []byte(plaintext), BytesToKeySHA256)
if err != nil { if err != nil {
t.Fatalf("Test errored at encrypt: %s", err) t.Fatalf("Test errored at encrypt: %s", err)
} }
dec, err := o.DecryptBinaryBytes(passphrase, enc, DigestSHA256Sum) dec, err := o.DecryptBinaryBytes(passphrase, enc, BytesToKeySHA256)
if err != nil { if err != nil {
t.Fatalf("Test errored at decrypt: %s", err) t.Fatalf("Test errored at decrypt: %s", err)
} }
@ -81,13 +85,19 @@ func TestBinaryEncryptToOpenSSL(t *testing.T) {
// Need to specify /dev/stdin as file so that we can pass in binary // Need to specify /dev/stdin as file so that we can pass in binary
// data to openssl without creating a file // data to openssl without creating a file
cmd := exec.Command( cmdArgs := []string{
"openssl", "aes-256-cbc", "openssl", "aes-256-cbc",
"-d", "-d",
"-pass", fmt.Sprintf("pass:%s", passphrase), "-pass", fmt.Sprintf("pass:%s", passphrase),
"-md", tc.tMdParam, "-md", tc.tMdParam,
"-in", "/dev/stdin", "-in", "/dev/stdin",
) }
if tc.tPBKDF {
cmdArgs = append(cmdArgs, "-pbkdf2")
}
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
var out bytes.Buffer var out bytes.Buffer
cmd.Stdout = &out cmd.Stdout = &out
@ -112,12 +122,12 @@ func TestBinaryEncryptWithSaltShouldHaveSameOutput(t *testing.T) {
o := New() o := New()
enc1, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), DigestSHA256Sum) enc1, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), BytesToKeySHA256)
if err != nil { if err != nil {
t.Fatalf("Test errored at encrypt: %s", err) t.Fatalf("Test errored at encrypt: %s", err)
} }
enc2, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), DigestSHA256Sum) enc2, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), BytesToKeySHA256)
if err != nil { if err != nil {
t.Fatalf("Test errored at encrypt: %s", err) t.Fatalf("Test errored at encrypt: %s", err)
} }
@ -137,12 +147,18 @@ func TestDecryptBinaryFromString(t *testing.T) {
t.Run(tc.tName, func(t *testing.T) { t.Run(tc.tName, func(t *testing.T) {
var out bytes.Buffer var out bytes.Buffer
cmd := exec.Command( cmdArgs := []string{
"openssl", "aes-256-cbc", "openssl", "aes-256-cbc",
"-pass", fmt.Sprintf("pass:%s", passphrase), "-pass", fmt.Sprintf("pass:%s", passphrase),
"-md", tc.tMdParam, "-md", tc.tMdParam,
"-in", "/dev/stdin", "-in", "/dev/stdin",
) }
if tc.tPBKDF {
cmdArgs = append(cmdArgs, "-pbkdf2")
}
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
cmd.Stdout = &out cmd.Stdout = &out
cmd.Stdin = strings.NewReader(plaintext) cmd.Stdin = strings.NewReader(plaintext)
@ -173,12 +189,18 @@ func TestDecryptFromString(t *testing.T) {
t.Run(tc.tName, func(t *testing.T) { t.Run(tc.tName, func(t *testing.T) {
var out bytes.Buffer var out bytes.Buffer
cmd := exec.Command( cmdArgs := []string{
"openssl", "aes-256-cbc", "openssl", "aes-256-cbc",
"-base64", "-base64",
"-pass", fmt.Sprintf("pass:%s", passphrase), "-pass", fmt.Sprintf("pass:%s", passphrase),
"-md", tc.tMdParam, "-md", tc.tMdParam,
) }
if tc.tPBKDF {
cmdArgs = append(cmdArgs, "-pbkdf2")
}
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
cmd.Stdout = &out cmd.Stdout = &out
cmd.Stdin = strings.NewReader(plaintext) cmd.Stdin = strings.NewReader(plaintext)
@ -205,12 +227,12 @@ func TestEncryptToDecrypt(t *testing.T) {
o := New() o := New()
enc, err := o.EncryptBytes(passphrase, []byte(plaintext), DigestSHA256Sum) enc, err := o.EncryptBytes(passphrase, []byte(plaintext), BytesToKeySHA256)
if err != nil { if err != nil {
t.Fatalf("Test errored at encrypt: %s", err) t.Fatalf("Test errored at encrypt: %s", err)
} }
dec, err := o.DecryptBytes(passphrase, enc, DigestSHA256Sum) dec, err := o.DecryptBytes(passphrase, enc, BytesToKeySHA256)
if err != nil { if err != nil {
t.Fatalf("Test errored at decrypt: %s", err) t.Fatalf("Test errored at decrypt: %s", err)
} }
@ -227,12 +249,12 @@ func TestEncryptToDecryptWithCustomSalt(t *testing.T) {
o := New() o := New()
enc, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), DigestSHA256Sum) enc, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), BytesToKeySHA256)
if err != nil { if err != nil {
t.Fatalf("Test errored at encrypt: %s", err) t.Fatalf("Test errored at encrypt: %s", err)
} }
dec, err := o.DecryptBytes(passphrase, enc, DigestSHA256Sum) dec, err := o.DecryptBytes(passphrase, enc, BytesToKeySHA256)
if err != nil { if err != nil {
t.Fatalf("Test errored at decrypt: %s", err) t.Fatalf("Test errored at decrypt: %s", err)
} }
@ -264,13 +286,19 @@ func TestEncryptToOpenSSL(t *testing.T) {
var out bytes.Buffer var out bytes.Buffer
cmd := exec.Command( cmdArgs := []string{
"openssl", "aes-256-cbc", "openssl", "aes-256-cbc",
"-base64", "-d", "-base64", "-d",
"-pass", fmt.Sprintf("pass:%s", passphrase), "-pass", fmt.Sprintf("pass:%s", passphrase),
"-md", tc.tMdParam, "-md", tc.tMdParam,
"-in", "/dev/stdin", "-in", "/dev/stdin",
) }
if tc.tPBKDF {
cmdArgs = append(cmdArgs, "-pbkdf2")
}
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
cmd.Stdout = &out cmd.Stdout = &out
cmd.Stdin = bytes.NewReader(enc) cmd.Stdin = bytes.NewReader(enc)
@ -293,12 +321,12 @@ func TestEncryptWithSaltShouldHaveSameOutput(t *testing.T) {
o := New() o := New()
enc1, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), DigestSHA256Sum) enc1, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), BytesToKeySHA256)
if err != nil { if err != nil {
t.Fatalf("Test errored at encrypt: %s", err) t.Fatalf("Test errored at encrypt: %s", err)
} }
enc2, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), DigestSHA256Sum) enc2, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), BytesToKeySHA256)
if err != nil { if err != nil {
t.Fatalf("Test errored at encrypt: %s", err) t.Fatalf("Test errored at encrypt: %s", err)
} }
@ -334,15 +362,15 @@ func TestSaltValidation(t *testing.T) {
o := New() o := New()
if _, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, []byte("12345"), []byte(plaintext), DigestSHA256Sum); err != ErrInvalidSalt { if _, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, []byte("12345"), []byte(plaintext), BytesToKeySHA256); err != ErrInvalidSalt {
t.Errorf("5-character salt was accepted, needs to have 8 character") t.Errorf("5-character salt was accepted, needs to have 8 character")
} }
if _, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, []byte("1234567890"), []byte(plaintext), DigestSHA256Sum); err != ErrInvalidSalt { if _, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, []byte("1234567890"), []byte(plaintext), BytesToKeySHA256); err != ErrInvalidSalt {
t.Errorf("10-character salt was accepted, needs to have 8 character") t.Errorf("10-character salt was accepted, needs to have 8 character")
} }
if _, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, []byte{0xcb, 0xd5, 0x1a, 0x3, 0x84, 0xba, 0xa8, 0xc8}, []byte(plaintext), DigestSHA256Sum); err == ErrInvalidSalt { if _, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, []byte{0xcb, 0xd5, 0x1a, 0x3, 0x84, 0xba, 0xa8, 0xc8}, []byte(plaintext), BytesToKeySHA256); err == ErrInvalidSalt {
t.Errorf("Salt with 8 byte unprintable characters was not accepted") t.Errorf("Salt with 8 byte unprintable characters was not accepted")
} }
} }
@ -351,47 +379,47 @@ func TestSaltValidation(t *testing.T) {
// Benchmarks // Benchmarks
// //
func benchmarkDecrypt(ciphertext []byte, kdf DigestFunc, b *testing.B) { func benchmarkDecrypt(ciphertext []byte, cg CredsGenerator, b *testing.B) {
passphrase := "z4yH36a6zerhfE5427ZV" passphrase := "z4yH36a6zerhfE5427ZV"
o := New() o := New()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
o.DecryptBytes(passphrase, ciphertext, kdf) o.DecryptBytes(passphrase, ciphertext, cg)
} }
} }
func BenchmarkDecryptMD5(b *testing.B) { func BenchmarkDecryptMD5(b *testing.B) {
benchmarkDecrypt([]byte("U2FsdGVkX19ZM5qQJGe/d5A/4pccgH+arBGTp+QnWPU="), DigestMD5Sum, b) benchmarkDecrypt([]byte("U2FsdGVkX19ZM5qQJGe/d5A/4pccgH+arBGTp+QnWPU="), BytesToKeyMD5, b)
} }
func BenchmarkDecryptSHA1(b *testing.B) { func BenchmarkDecryptSHA1(b *testing.B) {
benchmarkDecrypt([]byte("U2FsdGVkX1/Yy9kegseq2Ewd4UvjFYCpIEA1cltTA1Q="), DigestSHA1Sum, b) benchmarkDecrypt([]byte("U2FsdGVkX1/Yy9kegseq2Ewd4UvjFYCpIEA1cltTA1Q="), BytesToKeySHA1, b)
} }
func BenchmarkDecryptSHA256(b *testing.B) { func BenchmarkDecryptSHA256(b *testing.B) {
benchmarkDecrypt([]byte("U2FsdGVkX1+O68d7BO9ibP8nB5+xtb/27IHlyjJWpl8="), DigestSHA256Sum, b) benchmarkDecrypt([]byte("U2FsdGVkX1+O68d7BO9ibP8nB5+xtb/27IHlyjJWpl8="), BytesToKeySHA256, b)
} }
func benchmarkEncrypt(plaintext string, hashFunc DigestFunc, b *testing.B) { func benchmarkEncrypt(plaintext string, cg CredsGenerator, b *testing.B) {
passphrase := "z4yH36a6zerhfE5427ZV" passphrase := "z4yH36a6zerhfE5427ZV"
o := New() o := New()
salt, _ := o.GenerateSalt() salt, _ := o.GenerateSalt()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), hashFunc) o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), cg)
} }
} }
func BenchmarkEncryptMD5(b *testing.B) { func BenchmarkEncryptMD5(b *testing.B) {
benchmarkEncrypt("hallowelt", DigestMD5Sum, b) benchmarkEncrypt("hallowelt", BytesToKeyMD5, b)
} }
func BenchmarkEncryptSHA1(b *testing.B) { func BenchmarkEncryptSHA1(b *testing.B) {
benchmarkEncrypt("hallowelt", DigestSHA1Sum, b) benchmarkEncrypt("hallowelt", BytesToKeySHA1, b)
} }
func BenchmarkEncryptSHA256(b *testing.B) { func BenchmarkEncryptSHA256(b *testing.B) {
benchmarkEncrypt("hallowelt", DigestSHA256Sum, b) benchmarkEncrypt("hallowelt", BytesToKeySHA256, b)
} }
func BenchmarkGenerateSalt(b *testing.B) { func BenchmarkGenerateSalt(b *testing.B) {