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:
parent
3eef5a5be8
commit
0d93c15cab
8 changed files with 287 additions and 119 deletions
|
@ -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:
|
||||
|
|
12
README.md
12
README.md
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
6
go.mod
|
@ -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
58
keys.go
Normal 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
106
keys_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
112
openssl.go
112
openssl.go
|
@ -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)
|
||||
|
|
102
openssl_test.go
102
openssl_test.go
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue