1
0
Fork 0
mirror of https://github.com/Luzifer/go-openssl.git synced 2024-10-18 06:14:21 +00:00

Breaking: Fix race condition with guessing messagedigest

- This removes messagedigest guessing as it might lead to race
  conditions during decryption when an md5 digest works as a
  valid key for the sha256 encrypted data
- This removes deprecated functions (the previous point introduces
  a breaking change so this does not introduce another one)

fixes #10

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2018-11-02 12:37:30 +01:00
parent 5d2bdfffee
commit 8b7b49d583
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
4 changed files with 87 additions and 115 deletions

View file

@ -8,14 +8,30 @@
`go-openssl` is a small library wrapping the `crypto/aes` functions in a way the output is compatible to OpenSSL / CryptoJS. For all encryption / decryption processes AES256 is used so this library will not be able to decrypt messages generated with other than `openssl aes-256-cbc`. If you're using CryptoJS to process the data you also need to use AES256 on that side.
## OpenSSL 1.1.0c compatibility
## Version support
For this library only the latest major version is supported. All prior major versions should no longer be used.
The versioning is following [SemVer](https://semver.org/) which means upgrading to a newer major version will break your code!
## OpenSSL compatibility
### 1.1.0c
Starting with `v2.0.0` `go-openssl` generates the encryption keys using `sha256sum` algorithm. This is the default introduced in OpenSSL 1.1.0c. When encrypting data you can choose which digest method to use and therefore also continue to use `md5sum`. When decrypting OpenSSL encrypted data `md5sum`, `sha1sum` and `sha256sum` are supported.
### 1.1.1
The PBKDF2 key derivation is not yet supported.
## Installation
```
```bash
# Get the latest version
go get github.com/Luzifer/go-openssl
# OR get a specific version
go get gopkg.in/Luzifer/go-openssl.v3
```
## Usage example

View file

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

View file

@ -14,6 +14,9 @@ import (
"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")
@ -37,23 +40,37 @@ func New() *OpenSSL {
}
}
// DecryptString decrypts a string that was encrypted using OpenSSL and AES-256-CBC
func (o OpenSSL) DecryptString(passphrase, encryptedBase64String string) ([]byte, error) {
return o.DecryptBytes(passphrase, []byte(encryptedBase64String))
// 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)
}
var hashFuncList = []DigestFunc{DigestSHA256Sum, DigestMD5Sum, DigestSHA1Sum}
// DigestSHA1Sum uses SHA1 digest to create the key
func DigestSHA1Sum(data []byte) []byte {
h := sha1.New()
h.Write(data)
return h.Sum(nil)
}
func (o OpenSSL) decodeWithPassphrase(passphrase string, data []byte, salt []byte, hashFunc DigestFunc) ([]byte, error) {
creds, err := o.extractOpenSSLCreds([]byte(passphrase), salt, hashFunc)
if err != nil {
return nil, err
}
return o.decrypt(creds.key, creds.iv, data)
// 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
func (o OpenSSL) DecryptBytes(passphrase string, encryptedBase64Data []byte) ([]byte, error) {
// 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) {
data := make([]byte, base64.StdEncoding.DecodedLen(len(encryptedBase64Data)))
n, err := base64.StdEncoding.Decode(data, encryptedBase64Data)
if err != nil {
@ -72,15 +89,11 @@ func (o OpenSSL) DecryptBytes(passphrase string, encryptedBase64Data []byte) ([]
}
salt := saltHeader[8:]
tmp := make([]byte, len(data))
for _, f := range hashFuncList {
copy(tmp, data)
result, err := o.decodeWithPassphrase(passphrase, tmp, salt, f)
if err == nil {
return result, nil
}
creds, err := o.extractOpenSSLCreds([]byte(passphrase), salt, kdf)
if err != nil {
return nil, err
}
return nil, err
return o.decrypt(creds.key, creds.iv, data)
}
func (o OpenSSL) decrypt(key, iv, data []byte) ([]byte, error) {
@ -103,55 +116,13 @@ func (o OpenSSL) decrypt(key, iv, data []byte) ([]byte, error) {
// EncryptBytes encrypts a slice of bytes in a manner compatible to OpenSSL encryption
// functions using AES-256-CBC as encryption algorithm. This function generates
// a random salt on every execution.
func (o OpenSSL) EncryptBytes(passphrase string, plainData []byte) ([]byte, error) {
func (o OpenSSL) EncryptBytes(passphrase string, plainData []byte, kdf DigestFunc) ([]byte, error) {
salt, err := o.GenerateSalt()
if err != nil {
return nil, err
}
return o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, plainData, DigestSHA256Sum)
}
// EncryptString encrypts a string in a manner compatible to OpenSSL encryption
// functions using AES-256-CBC as encryption algorithm. This function generates
// a random salt on every execution.
func (o OpenSSL) EncryptString(passphrase, plaintextString string) ([]byte, error) {
salt, err := o.GenerateSalt()
if err != nil {
return nil, err
}
return o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintextString), DigestSHA256Sum)
}
// EncryptStringWithSalt encrypts a string in a manner compatible to OpenSSL
// encryption functions using AES-256-CBC as encryption algorithm. The salt
// needs to be passed in here which ensures the same result on every execution
// on cost of a much weaker encryption as with EncryptString.
//
// The salt passed into this function needs to have exactly 8 byte.
//
// If you don't have a good reason to use this, please don't! For more information
// see this: https://en.wikipedia.org/wiki/Salt_(cryptography)#Common_mistakes
//
// Deprecated: Use EncryptBytesWithSaltAndDigestFunc instead.
func (o OpenSSL) EncryptStringWithSalt(passphrase string, salt []byte, plaintextString string) ([]byte, error) {
return o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintextString), DigestSHA256Sum)
}
// EncryptBytesWithSalt encrypts a slice of bytes in a manner compatible to OpenSSL
// encryption functions using AES-256-CBC as encryption algorithm. The salt
// needs to be passed in here which ensures the same result on every execution
// on cost of a much weaker encryption as with EncryptString.
//
// The salt passed into this function needs to have exactly 8 byte.
//
// If you don't have a good reason to use this, please don't! For more information
// see this: https://en.wikipedia.org/wiki/Salt_(cryptography)#Common_mistakes
//
// Deprecated: Use EncryptBytesWithSaltAndDigestFunc instead.
func (o OpenSSL) EncryptBytesWithSalt(passphrase string, salt, plainData []byte) ([]byte, error) {
return o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, plainData, DigestSHA256Sum)
return o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, plainData, kdf)
}
// EncryptBytesWithSaltAndDigestFunc encrypts a slice of bytes in a manner compatible to OpenSSL
@ -189,17 +160,6 @@ func (o OpenSSL) EncryptBytesWithSaltAndDigestFunc(passphrase string, salt, plai
return []byte(base64.StdEncoding.EncodeToString(enc)), nil
}
// GenerateSalt generates a random 8 byte salt
func (o OpenSSL) GenerateSalt() ([]byte, error) {
salt := make([]byte, 8) // Generate an 8 byte salt
_, err := io.ReadFull(rand.Reader, salt)
if err != nil {
return nil, err
}
return salt, nil
}
func (o OpenSSL) encrypt(key, iv, data []byte) ([]byte, error) {
padded, err := o.pkcs7Pad(data, aes.BlockSize)
if err != nil {
@ -238,28 +198,25 @@ func (o OpenSSL) hash(prev, password, salt []byte, hashFunc DigestFunc) []byte {
return hashFunc(a)
}
// DigestFunc are functions to create a key from the passphrase
type DigestFunc func([]byte) []byte
// GenerateSalt generates a random 8 byte salt
func (o OpenSSL) GenerateSalt() ([]byte, error) {
salt := make([]byte, 8) // Generate an 8 byte salt
_, err := io.ReadFull(rand.Reader, salt)
if err != nil {
return nil, err
}
// 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)
return salt, 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)
// MustGenerateSalt is a wrapper around GenerateSalt which will panic on an error.
// This allows you to use this function as a parameter to EncryptBytesWithSaltAndDigestFunc
func (o OpenSSL) MustGenerateSalt() []byte {
s, err := o.GenerateSalt()
if err != nil {
panic(err)
}
return s
}
// pkcs7Pad appends padding.

View file

@ -16,7 +16,7 @@ func TestDecryptFromStringMD5(t *testing.T) {
o := New()
data, err := o.DecryptString(passphrase, opensslEncrypted)
data, err := o.DecryptBytes(passphrase, []byte(opensslEncrypted), DigestMD5Sum)
if err != nil {
t.Fatalf("Test errored: %s", err)
@ -36,7 +36,7 @@ func TestDecryptFromStringSHA1(t *testing.T) {
o := New()
data, err := o.DecryptString(passphrase, opensslEncrypted)
data, err := o.DecryptBytes(passphrase, []byte(opensslEncrypted), DigestSHA1Sum)
if err != nil {
t.Fatalf("Test errored: %s", err)
@ -56,7 +56,7 @@ func TestDecryptFromStringSHA256(t *testing.T) {
o := New()
data, err := o.DecryptString(passphrase, opensslEncrypted)
data, err := o.DecryptBytes(passphrase, []byte(opensslEncrypted), DigestSHA256Sum)
if err != nil {
t.Fatalf("Test errored: %s", err)
@ -73,12 +73,12 @@ func TestEncryptToDecrypt(t *testing.T) {
o := New()
enc, err := o.EncryptString(passphrase, plaintext)
enc, err := o.EncryptBytes(passphrase, []byte(plaintext), DigestSHA256Sum)
if err != nil {
t.Fatalf("Test errored at encrypt: %s", err)
}
dec, err := o.DecryptString(passphrase, string(enc))
dec, err := o.DecryptBytes(passphrase, enc, DigestSHA256Sum)
if err != nil {
t.Fatalf("Test errored at decrypt: %s", err)
}
@ -95,12 +95,12 @@ func TestEncryptToDecryptWithCustomSalt(t *testing.T) {
o := New()
enc, err := o.EncryptStringWithSalt(passphrase, salt, plaintext)
enc, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), DigestSHA256Sum)
if err != nil {
t.Fatalf("Test errored at encrypt: %s", err)
}
dec, err := o.DecryptString(passphrase, string(enc))
dec, err := o.DecryptBytes(passphrase, enc, DigestSHA256Sum)
if err != nil {
t.Fatalf("Test errored at decrypt: %s", err)
}
@ -117,12 +117,12 @@ func TestEncryptWithSaltShouldHaveSameOutput(t *testing.T) {
o := New()
enc1, err := o.EncryptStringWithSalt(passphrase, salt, plaintext)
enc1, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), DigestSHA256Sum)
if err != nil {
t.Fatalf("Test errored at encrypt: %s", err)
}
enc2, err := o.EncryptStringWithSalt(passphrase, salt, plaintext)
enc2, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), DigestSHA256Sum)
if err != nil {
t.Fatalf("Test errored at encrypt: %s", err)
}
@ -160,7 +160,6 @@ func TestEncryptToOpenSSL(t *testing.T) {
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &out
err = cmd.Run()
if err != nil {
@ -199,38 +198,38 @@ func TestSaltValidation(t *testing.T) {
o := New()
if _, err := o.EncryptStringWithSalt(passphrase, []byte("12345"), plaintext); err != ErrInvalidSalt {
if _, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, []byte("12345"), []byte(plaintext), DigestSHA256Sum); err != ErrInvalidSalt {
t.Errorf("5-character salt was accepted, needs to have 8 character")
}
if _, err := o.EncryptStringWithSalt(passphrase, []byte("1234567890"), plaintext); err != ErrInvalidSalt {
if _, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, []byte("1234567890"), []byte(plaintext), DigestSHA256Sum); err != ErrInvalidSalt {
t.Errorf("10-character salt was accepted, needs to have 8 character")
}
if _, err := o.EncryptStringWithSalt(passphrase, []byte{0xcb, 0xd5, 0x1a, 0x3, 0x84, 0xba, 0xa8, 0xc8}, plaintext); err == ErrInvalidSalt {
if _, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, []byte{0xcb, 0xd5, 0x1a, 0x3, 0x84, 0xba, 0xa8, 0xc8}, []byte(plaintext), DigestSHA256Sum); err == ErrInvalidSalt {
t.Errorf("Salt with 8 byte unprintable characters was not accepted")
}
}
func benchmarkDecrypt(ciphertext string, b *testing.B) {
func benchmarkDecrypt(ciphertext []byte, kdf DigestFunc, b *testing.B) {
passphrase := "z4yH36a6zerhfE5427ZV"
o := New()
for n := 0; n < b.N; n++ {
o.DecryptString(passphrase, ciphertext)
o.DecryptBytes(passphrase, ciphertext, kdf)
}
}
func BenchmarkDecryptMD5(b *testing.B) {
benchmarkDecrypt("U2FsdGVkX19ZM5qQJGe/d5A/4pccgH+arBGTp+QnWPU=", b)
benchmarkDecrypt([]byte("U2FsdGVkX19ZM5qQJGe/d5A/4pccgH+arBGTp+QnWPU="), DigestMD5Sum, b)
}
func BenchmarkDecryptSHA1(b *testing.B) {
benchmarkDecrypt("U2FsdGVkX1/Yy9kegseq2Ewd4UvjFYCpIEA1cltTA1Q=", b)
benchmarkDecrypt([]byte("U2FsdGVkX1/Yy9kegseq2Ewd4UvjFYCpIEA1cltTA1Q="), DigestSHA1Sum, b)
}
func BenchmarkDecryptSHA256(b *testing.B) {
benchmarkDecrypt("U2FsdGVkX1+O68d7BO9ibP8nB5+xtb/27IHlyjJWpl8=", b)
benchmarkDecrypt([]byte("U2FsdGVkX1+O68d7BO9ibP8nB5+xtb/27IHlyjJWpl8="), DigestSHA256Sum, b)
}
func benchmarkEncrypt(plaintext string, hashFunc DigestFunc, b *testing.B) {