1
0
Fork 0
mirror of https://github.com/Luzifer/go-openssl.git synced 2024-10-18 06:14:21 +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
go:
- 1.10.x
- 1.11.x
- 1.12.x
- 1.13.x
- 1.14.x
- tip
script:

View file

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

View file

@ -8,7 +8,7 @@ func ExampleOpenSSL_EncryptBytes() {
o := New()
enc, err := o.EncryptBytes(passphrase, []byte(plaintext), DigestSHA256Sum)
enc, err := o.EncryptBytes(passphrase, []byte(plaintext), PBKDF2SHA256)
if err != nil {
fmt.Printf("An error occurred: %s\n", err)
}
@ -22,7 +22,7 @@ func ExampleOpenSSL_DecryptBytes() {
o := New()
dec, err := o.DecryptBytes(passphrase, []byte(opensslEncrypted), DigestMD5Sum)
dec, err := o.DecryptBytes(passphrase, []byte(opensslEncrypted), BytesToKeyMD5)
if err != nil {
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"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"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
var ErrInvalidSalt = errors.New("Salt needs to have exactly 8 byte")
@ -28,9 +22,33 @@ type OpenSSL struct {
openSSLSaltHeader string
}
type openSSLCreds struct {
key []byte
iv []byte
// Creds holds a key and an IV for encryption methods
type Creds struct {
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
@ -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
// 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) 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)))
n, err := base64.StdEncoding.Decode(data, encryptedBase64Data)
if err != nil {
@ -80,7 +74,7 @@ func (o OpenSSL) DecryptBytes(passphrase string, encryptedBase64Data []byte, kdf
// Truncate to real message length
data = data[0:n]
decrypted, err := o.DecryptBinaryBytes(passphrase, data, kdf)
decrypted, err := o.DecryptBinaryBytes(passphrase, data, cg)
if err != nil {
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
// 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 {
return nil, fmt.Errorf("Data is too short")
}
@ -103,11 +97,11 @@ func (o OpenSSL) DecryptBinaryBytes(passphrase string, encryptedData []byte, kdf
}
salt := saltHeader[8:]
creds, err := o.extractOpenSSLCreds([]byte(passphrase), salt, kdf)
creds, err := cg([]byte(passphrase), salt)
if err != nil {
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) {
@ -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
// 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, kdf DigestFunc) ([]byte, error) {
func (o OpenSSL) EncryptBytes(passphrase string, plainData []byte, cg CredsGenerator) ([]byte, error) {
salt, err := o.GenerateSalt()
if err != nil {
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
// 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) {
func (o OpenSSL) EncryptBinaryBytes(passphrase string, plainData []byte, cg CredsGenerator) ([]byte, error) {
salt, err := o.GenerateSalt()
if err != nil {
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
@ -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
// see this: https://en.wikipedia.org/wiki/Salt_(cryptography)#Common_mistakes
func (o OpenSSL) EncryptBytesWithSaltAndDigestFunc(passphrase string, salt, plainData []byte, hashFunc DigestFunc) ([]byte, error) {
enc, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, plainData, hashFunc)
func (o OpenSSL) EncryptBytesWithSaltAndDigestFunc(passphrase string, salt, plainData []byte, cg CredsGenerator) ([]byte, error) {
enc, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, plainData, cg)
if err != nil {
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
// 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 {
return nil, ErrInvalidSalt
}
@ -210,12 +204,12 @@ func (o OpenSSL) EncryptBinaryBytesWithSaltAndDigestFunc(passphrase string, salt
copy(data[8:], salt)
copy(data[aes.BlockSize:], plainData)
creds, err := o.extractOpenSSLCreds([]byte(passphrase), salt, hashFunc)
creds, err := cg([]byte(passphrase), salt)
if err != nil {
return nil, err
}
enc, err := o.encrypt(creds.key, creds.iv, data)
enc, err := o.encrypt(creds.Key, creds.IV, data)
if err != nil {
return nil, err
}
@ -223,28 +217,6 @@ func (o OpenSSL) EncryptBinaryBytesWithSaltAndDigestFunc(passphrase string, salt
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
func (o OpenSSL) GenerateSalt() ([]byte, error) {
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
for ((len(data) + padlen) % blocklen) != 0 {
padlen = padlen + 1
padlen++
}
pad := bytes.Repeat([]byte{byte(padlen)}, padlen)

View file

@ -11,11 +11,15 @@ import (
var testTable = []struct {
tName string
tMdParam string
tMdFunc DigestFunc
tMdFunc CredsGenerator
tPBKDF bool
}{
{"MD5", "md5", DigestMD5Sum},
{"SHA1", "sha1", DigestSHA1Sum},
{"SHA256", "sha256", DigestSHA256Sum},
{"MD5", "md5", BytesToKeyMD5, false},
{"SHA1", "sha1", BytesToKeySHA1, false},
{"SHA256", "sha256", BytesToKeySHA256, false},
{"PBKDF2_MD5", "md5", PBKDF2MD5, true},
{"PBKDF2_SHA1", "sha1", PBKDF2SHA1, true},
{"PBKDF2_SHA256", "sha256", PBKDF2SHA256, true},
}
func TestBinaryEncryptToDecryptWithCustomSalt(t *testing.T) {
@ -25,12 +29,12 @@ func TestBinaryEncryptToDecryptWithCustomSalt(t *testing.T) {
o := New()
enc, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), DigestSHA256Sum)
enc, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), BytesToKeySHA256)
if err != nil {
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 {
t.Fatalf("Test errored at decrypt: %s", err)
}
@ -46,12 +50,12 @@ func TestBinaryEncryptToDecrypt(t *testing.T) {
o := New()
enc, err := o.EncryptBinaryBytes(passphrase, []byte(plaintext), DigestSHA256Sum)
enc, err := o.EncryptBinaryBytes(passphrase, []byte(plaintext), BytesToKeySHA256)
if err != nil {
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 {
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
// data to openssl without creating a file
cmd := exec.Command(
cmdArgs := []string{
"openssl", "aes-256-cbc",
"-d",
"-pass", fmt.Sprintf("pass:%s", passphrase),
"-md", tc.tMdParam,
"-in", "/dev/stdin",
)
}
if tc.tPBKDF {
cmdArgs = append(cmdArgs, "-pbkdf2")
}
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
var out bytes.Buffer
cmd.Stdout = &out
@ -112,12 +122,12 @@ func TestBinaryEncryptWithSaltShouldHaveSameOutput(t *testing.T) {
o := New()
enc1, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), DigestSHA256Sum)
enc1, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), BytesToKeySHA256)
if err != nil {
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 {
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) {
var out bytes.Buffer
cmd := exec.Command(
cmdArgs := []string{
"openssl", "aes-256-cbc",
"-pass", fmt.Sprintf("pass:%s", passphrase),
"-md", tc.tMdParam,
"-in", "/dev/stdin",
)
}
if tc.tPBKDF {
cmdArgs = append(cmdArgs, "-pbkdf2")
}
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
cmd.Stdout = &out
cmd.Stdin = strings.NewReader(plaintext)
@ -173,12 +189,18 @@ func TestDecryptFromString(t *testing.T) {
t.Run(tc.tName, func(t *testing.T) {
var out bytes.Buffer
cmd := exec.Command(
cmdArgs := []string{
"openssl", "aes-256-cbc",
"-base64",
"-pass", fmt.Sprintf("pass:%s", passphrase),
"-md", tc.tMdParam,
)
}
if tc.tPBKDF {
cmdArgs = append(cmdArgs, "-pbkdf2")
}
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
cmd.Stdout = &out
cmd.Stdin = strings.NewReader(plaintext)
@ -205,12 +227,12 @@ func TestEncryptToDecrypt(t *testing.T) {
o := New()
enc, err := o.EncryptBytes(passphrase, []byte(plaintext), DigestSHA256Sum)
enc, err := o.EncryptBytes(passphrase, []byte(plaintext), BytesToKeySHA256)
if err != nil {
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 {
t.Fatalf("Test errored at decrypt: %s", err)
}
@ -227,12 +249,12 @@ func TestEncryptToDecryptWithCustomSalt(t *testing.T) {
o := New()
enc, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), DigestSHA256Sum)
enc, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), BytesToKeySHA256)
if err != nil {
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 {
t.Fatalf("Test errored at decrypt: %s", err)
}
@ -264,13 +286,19 @@ func TestEncryptToOpenSSL(t *testing.T) {
var out bytes.Buffer
cmd := exec.Command(
cmdArgs := []string{
"openssl", "aes-256-cbc",
"-base64", "-d",
"-pass", fmt.Sprintf("pass:%s", passphrase),
"-md", tc.tMdParam,
"-in", "/dev/stdin",
)
}
if tc.tPBKDF {
cmdArgs = append(cmdArgs, "-pbkdf2")
}
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
cmd.Stdout = &out
cmd.Stdin = bytes.NewReader(enc)
@ -293,12 +321,12 @@ func TestEncryptWithSaltShouldHaveSameOutput(t *testing.T) {
o := New()
enc1, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), DigestSHA256Sum)
enc1, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), BytesToKeySHA256)
if err != nil {
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 {
t.Fatalf("Test errored at encrypt: %s", err)
}
@ -334,15 +362,15 @@ func TestSaltValidation(t *testing.T) {
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")
}
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")
}
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")
}
}
@ -351,47 +379,47 @@ func TestSaltValidation(t *testing.T) {
// Benchmarks
//
func benchmarkDecrypt(ciphertext []byte, kdf DigestFunc, b *testing.B) {
func benchmarkDecrypt(ciphertext []byte, cg CredsGenerator, b *testing.B) {
passphrase := "z4yH36a6zerhfE5427ZV"
o := New()
for n := 0; n < b.N; n++ {
o.DecryptBytes(passphrase, ciphertext, kdf)
o.DecryptBytes(passphrase, ciphertext, cg)
}
}
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) {
benchmarkDecrypt([]byte("U2FsdGVkX1/Yy9kegseq2Ewd4UvjFYCpIEA1cltTA1Q="), DigestSHA1Sum, b)
benchmarkDecrypt([]byte("U2FsdGVkX1/Yy9kegseq2Ewd4UvjFYCpIEA1cltTA1Q="), BytesToKeySHA1, 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"
o := New()
salt, _ := o.GenerateSalt()
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) {
benchmarkEncrypt("hallowelt", DigestMD5Sum, b)
benchmarkEncrypt("hallowelt", BytesToKeyMD5, b)
}
func BenchmarkEncryptSHA1(b *testing.B) {
benchmarkEncrypt("hallowelt", DigestSHA1Sum, b)
benchmarkEncrypt("hallowelt", BytesToKeySHA1, b)
}
func BenchmarkEncryptSHA256(b *testing.B) {
benchmarkEncrypt("hallowelt", DigestSHA256Sum, b)
benchmarkEncrypt("hallowelt", BytesToKeySHA256, b)
}
func BenchmarkGenerateSalt(b *testing.B) {