mirror of
https://github.com/Luzifer/go-openssl.git
synced 2024-12-20 10:51:17 +00:00
Fix linter errors, simplify tests
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
79386715e8
commit
77069a53d0
6 changed files with 317 additions and 193 deletions
174
.golangci.yml
Normal file
174
.golangci.yml
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
# Derived from https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
run:
|
||||||
|
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||||
|
timeout: 5m
|
||||||
|
# Force readonly modules usage for checking
|
||||||
|
modules-download-mode: readonly
|
||||||
|
|
||||||
|
output:
|
||||||
|
format: tab
|
||||||
|
|
||||||
|
issues:
|
||||||
|
# This disables the included exclude-list in golangci-lint as that
|
||||||
|
# list for example fully hides G304 gosec rule, errcheck, exported
|
||||||
|
# rule of revive and other errors one really wants to see.
|
||||||
|
# Smme detail: https://github.com/golangci/golangci-lint/issues/456
|
||||||
|
exclude-use-default: false
|
||||||
|
# Don't limit the number of shown issues: Report ALL of them
|
||||||
|
max-issues-per-linter: 0
|
||||||
|
max-same-issues: 0
|
||||||
|
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers [fast: true, auto-fix: false]
|
||||||
|
- bidichk # Checks for dangerous unicode character sequences [fast: true, auto-fix: false]
|
||||||
|
- bodyclose # checks whether HTTP response body is closed successfully [fast: true, auto-fix: false]
|
||||||
|
- containedctx # containedctx is a linter that detects struct contained context.Context field [fast: true, auto-fix: false]
|
||||||
|
- contextcheck # check the function whether use a non-inherited context [fast: false, auto-fix: false]
|
||||||
|
- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
|
||||||
|
- durationcheck # check for two durations multiplied together [fast: false, auto-fix: false]
|
||||||
|
- errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: false, auto-fix: false]
|
||||||
|
- errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occations, where the check for the returned error can be omitted. [fast: false, auto-fix: false]
|
||||||
|
- exportloopref # checks for pointers to enclosing loop variables [fast: true, auto-fix: false]
|
||||||
|
- forbidigo # Forbids identifiers [fast: true, auto-fix: false]
|
||||||
|
- funlen # Tool for detection of long functions [fast: true, auto-fix: false]
|
||||||
|
- gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false]
|
||||||
|
- goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
|
||||||
|
- gocritic # The most opinionated Go source code linter [fast: true, auto-fix: false]
|
||||||
|
- gocyclo # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false]
|
||||||
|
- godox # Tool for detection of FIXME, TODO and other comment keywords [fast: true, auto-fix: false]
|
||||||
|
- gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true, auto-fix: true]
|
||||||
|
- gofumpt # Gofumpt checks whether code was gofumpt-ed. [fast: true, auto-fix: true]
|
||||||
|
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports [fast: true, auto-fix: true]
|
||||||
|
- gomnd # An analyzer to detect magic numbers. [fast: true, auto-fix: false]
|
||||||
|
- gosec # Inspects source code for security problems [fast: true, auto-fix: false]
|
||||||
|
- gosimple # Linter for Go source code that specializes in simplifying a code [fast: true, auto-fix: false]
|
||||||
|
- govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: true, auto-fix: false]
|
||||||
|
- ineffassign # Detects when assignments to existing variables are not used [fast: true, auto-fix: false]
|
||||||
|
- misspell # Finds commonly misspelled English words in comments [fast: true, auto-fix: true]
|
||||||
|
- nakedret # Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false]
|
||||||
|
- nilerr # Finds the code that returns nil even if it checks that the error is not nil. [fast: false, auto-fix: false]
|
||||||
|
- nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value. [fast: false, auto-fix: false]
|
||||||
|
- noctx # noctx finds sending http request without context.Context [fast: true, auto-fix: false]
|
||||||
|
- nolintlint # Reports ill-formed or insufficient nolint directives [fast: true, auto-fix: false]
|
||||||
|
- revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. [fast: false, auto-fix: false]
|
||||||
|
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: true, auto-fix: false]
|
||||||
|
- stylecheck # Stylecheck is a replacement for golint [fast: true, auto-fix: false]
|
||||||
|
- tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17 [fast: false, auto-fix: false]
|
||||||
|
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code [fast: true, auto-fix: false]
|
||||||
|
- unconvert # Remove unnecessary type conversions [fast: true, auto-fix: false]
|
||||||
|
- unused # Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
|
||||||
|
- wastedassign # wastedassign finds wasted assignment statements. [fast: false, auto-fix: false]
|
||||||
|
- wrapcheck # Checks that errors returned from external packages are wrapped [fast: false, auto-fix: false]
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
funlen:
|
||||||
|
lines: 100
|
||||||
|
statements: 60
|
||||||
|
|
||||||
|
gocyclo:
|
||||||
|
# minimal code complexity to report, 30 by default (but we recommend 10-20)
|
||||||
|
min-complexity: 15
|
||||||
|
|
||||||
|
gomnd:
|
||||||
|
settings:
|
||||||
|
mnd:
|
||||||
|
ignored-functions: 'strconv.(?:Format|Parse)\B+'
|
||||||
|
|
||||||
|
revive:
|
||||||
|
rules:
|
||||||
|
#- name: add-constant # Suggests using constant for magic numbers and string literals
|
||||||
|
# Opinion: Makes sense for strings, not for numbers but checks numbers
|
||||||
|
#- name: argument-limit # Specifies the maximum number of arguments a function can receive | Opinion: Don't need this
|
||||||
|
- name: atomic # Check for common mistaken usages of the `sync/atomic` package
|
||||||
|
- name: banned-characters # Checks banned characters in identifiers
|
||||||
|
arguments:
|
||||||
|
- ';' # Greek question mark
|
||||||
|
- name: bare-return # Warns on bare returns
|
||||||
|
- name: blank-imports # Disallows blank imports
|
||||||
|
- name: bool-literal-in-expr # Suggests removing Boolean literals from logic expressions
|
||||||
|
- name: call-to-gc # Warns on explicit call to the garbage collector
|
||||||
|
#- name: cognitive-complexity # Sets restriction for maximum Cognitive complexity.
|
||||||
|
# There is a dedicated linter for this
|
||||||
|
- name: confusing-naming # Warns on methods with names that differ only by capitalization
|
||||||
|
- name: confusing-results # Suggests to name potentially confusing function results
|
||||||
|
- name: constant-logical-expr # Warns on constant logical expressions
|
||||||
|
- name: context-as-argument # `context.Context` should be the first argument of a function.
|
||||||
|
- name: context-keys-type # Disallows the usage of basic types in `context.WithValue`.
|
||||||
|
#- name: cyclomatic # Sets restriction for maximum Cyclomatic complexity.
|
||||||
|
# There is a dedicated linter for this
|
||||||
|
#- name: datarace # Spots potential dataraces
|
||||||
|
# Is not (yet) available?
|
||||||
|
- name: deep-exit # Looks for program exits in funcs other than `main()` or `init()`
|
||||||
|
- name: defer # Warns on some [defer gotchas](https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-iii-36a1ab3d6ef1)
|
||||||
|
- name: dot-imports # Forbids `.` imports.
|
||||||
|
- name: duplicated-imports # Looks for packages that are imported two or more times
|
||||||
|
- name: early-return # Spots if-then-else statements that can be refactored to simplify code reading
|
||||||
|
- name: empty-block # Warns on empty code blocks
|
||||||
|
- name: empty-lines # Warns when there are heading or trailing newlines in a block
|
||||||
|
- name: errorf # Should replace `errors.New(fmt.Sprintf())` with `fmt.Errorf()`
|
||||||
|
- name: error-naming # Naming of error variables.
|
||||||
|
- name: error-return # The error return parameter should be last.
|
||||||
|
- name: error-strings # Conventions around error strings.
|
||||||
|
- name: exported # Naming and commenting conventions on exported symbols.
|
||||||
|
arguments: ['sayRepetitiveInsteadOfStutters']
|
||||||
|
#- name: file-header # Header which each file should have.
|
||||||
|
# Useless without config, have no config for it
|
||||||
|
- name: flag-parameter # Warns on boolean parameters that create a control coupling
|
||||||
|
#- name: function-length # Warns on functions exceeding the statements or lines max
|
||||||
|
# There is a dedicated linter for this
|
||||||
|
#- name: function-result-limit # Specifies the maximum number of results a function can return
|
||||||
|
# Opinion: Don't need this
|
||||||
|
- name: get-return # Warns on getters that do not yield any result
|
||||||
|
- name: identical-branches # Spots if-then-else statements with identical `then` and `else` branches
|
||||||
|
- name: if-return # Redundant if when returning an error.
|
||||||
|
#- name: imports-blacklist # Disallows importing the specified packages
|
||||||
|
# Useless without config, have no config for it
|
||||||
|
- name: import-shadowing # Spots identifiers that shadow an import
|
||||||
|
- name: increment-decrement # Use `i++` and `i--` instead of `i += 1` and `i -= 1`.
|
||||||
|
- name: indent-error-flow # Prevents redundant else statements.
|
||||||
|
#- name: line-length-limit # Specifies the maximum number of characters in a lined
|
||||||
|
# There is a dedicated linter for this
|
||||||
|
#- name: max-public-structs # The maximum number of public structs in a file.
|
||||||
|
# Opinion: Don't need this
|
||||||
|
- name: modifies-parameter # Warns on assignments to function parameters
|
||||||
|
- name: modifies-value-receiver # Warns on assignments to value-passed method receivers
|
||||||
|
#- name: nested-structs # Warns on structs within structs
|
||||||
|
# Opinion: Don't need this
|
||||||
|
- name: optimize-operands-order # Checks inefficient conditional expressions
|
||||||
|
#- name: package-comments # Package commenting conventions.
|
||||||
|
# Opinion: Don't need this
|
||||||
|
- name: range # Prevents redundant variables when iterating over a collection.
|
||||||
|
- name: range-val-address # Warns if address of range value is used dangerously
|
||||||
|
- name: range-val-in-closure # Warns if range value is used in a closure dispatched as goroutine
|
||||||
|
- name: receiver-naming # Conventions around the naming of receivers.
|
||||||
|
- name: redefines-builtin-id # Warns on redefinitions of builtin identifiers
|
||||||
|
#- name: string-format # Warns on specific string literals that fail one or more user-configured regular expressions
|
||||||
|
# Useless without config, have no config for it
|
||||||
|
- name: string-of-int # Warns on suspicious casts from int to string
|
||||||
|
- name: struct-tag # Checks common struct tags like `json`,`xml`,`yaml`
|
||||||
|
- name: superfluous-else # Prevents redundant else statements (extends indent-error-flow)
|
||||||
|
- name: time-equal # Suggests to use `time.Time.Equal` instead of `==` and `!=` for equality check time.
|
||||||
|
- name: time-naming # Conventions around the naming of time variables.
|
||||||
|
- name: unconditional-recursion # Warns on function calls that will lead to (direct) infinite recursion
|
||||||
|
- name: unexported-naming # Warns on wrongly named un-exported symbols
|
||||||
|
- name: unexported-return # Warns when a public return is from unexported type.
|
||||||
|
- name: unhandled-error # Warns on unhandled errors returned by funcion calls
|
||||||
|
arguments:
|
||||||
|
- "fmt.(Fp|P)rint(f|ln|)"
|
||||||
|
- name: unnecessary-stmt # Suggests removing or simplifying unnecessary statements
|
||||||
|
- name: unreachable-code # Warns on unreachable code
|
||||||
|
- name: unused-parameter # Suggests to rename or remove unused function parameters
|
||||||
|
- name: unused-receiver # Suggests to rename or remove unused method receivers
|
||||||
|
#- name: use-any # Proposes to replace `interface{}` with its alias `any`
|
||||||
|
# Is not (yet) available?
|
||||||
|
- name: useless-break # Warns on useless `break` statements in case clauses
|
||||||
|
- name: var-declaration # Reduces redundancies around variable declaration.
|
||||||
|
- name: var-naming # Naming rules.
|
||||||
|
- name: waitgroup-by-value # Warns on functions taking sync.WaitGroup as a by-value parameter
|
||||||
|
|
||||||
|
...
|
|
@ -2,6 +2,7 @@ package openssl
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
|
// #nosec G101 -- Contains harcoded test passphrase
|
||||||
func ExampleOpenSSL_EncryptBytes() {
|
func ExampleOpenSSL_EncryptBytes() {
|
||||||
plaintext := "Hello World!"
|
plaintext := "Hello World!"
|
||||||
passphrase := "z4yH36a6zerhfE5427ZV"
|
passphrase := "z4yH36a6zerhfE5427ZV"
|
||||||
|
@ -16,6 +17,7 @@ func ExampleOpenSSL_EncryptBytes() {
|
||||||
fmt.Printf("Encrypted text: %s\n", string(enc))
|
fmt.Printf("Encrypted text: %s\n", string(enc))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #nosec G101 -- Contains harcoded test passphrase
|
||||||
func ExampleOpenSSL_DecryptBytes() {
|
func ExampleOpenSSL_DecryptBytes() {
|
||||||
opensslEncrypted := "U2FsdGVkX19ZM5qQJGe/d5A/4pccgH+arBGTp+QnWPU="
|
opensslEncrypted := "U2FsdGVkX19ZM5qQJGe/d5A/4pccgH+arBGTp+QnWPU="
|
||||||
passphrase := "z4yH36a6zerhfE5427ZV"
|
passphrase := "z4yH36a6zerhfE5427ZV"
|
||||||
|
|
57
keys.go
57
keys.go
|
@ -1,33 +1,58 @@
|
||||||
package openssl
|
package openssl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5" //#nosec G501 -- Used for OpenSSL compatibility in old KDF
|
||||||
"crypto/sha1"
|
"crypto/sha1" //#nosec G505 -- Used for OpenSSL compatibility in old KDF
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
|
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DefaultPBKDF2Iterations specifies the number of iterations to use
|
||||||
|
// in PBKDF2 key generation. This is taken from the `openssl enc`
|
||||||
|
// commands default.
|
||||||
|
//
|
||||||
|
// Taken from OpenSSL v3.1.2:
|
||||||
|
// `openssl enc --help |& grep -A1 iter`
|
||||||
const DefaultPBKDF2Iterations = 10000
|
const DefaultPBKDF2Iterations = 10000
|
||||||
|
|
||||||
|
const (
|
||||||
|
opensslKeyLength = 32
|
||||||
|
opensslIVLength = 16
|
||||||
|
)
|
||||||
|
|
||||||
// CredsGenerator are functions to derive a key and iv from a password and a salt
|
// CredsGenerator are functions to derive a key and iv from a password and a salt
|
||||||
type CredsGenerator func(password, salt []byte) (Creds, error)
|
type CredsGenerator func(password, salt []byte) (Creds, error)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
BytesToKeyMD5 = NewBytesToKeyGenerator(md5.New)
|
// BytesToKeyMD5 utilizes MD5 key-derivation (`-md md5`)
|
||||||
BytesToKeySHA1 = NewBytesToKeyGenerator(sha1.New)
|
BytesToKeyMD5 = NewBytesToKeyGenerator(md5.New)
|
||||||
|
// BytesToKeySHA1 utilizes SHA1 key-derivation (`-md sha1`)
|
||||||
|
BytesToKeySHA1 = NewBytesToKeyGenerator(sha1.New)
|
||||||
|
// BytesToKeySHA256 utilizes SHA256 key-derivation (`-md sha256`)
|
||||||
BytesToKeySHA256 = NewBytesToKeyGenerator(sha256.New)
|
BytesToKeySHA256 = NewBytesToKeyGenerator(sha256.New)
|
||||||
|
// BytesToKeySHA384 utilizes SHA384 key-derivation (`-md sha384`)
|
||||||
BytesToKeySHA384 = NewBytesToKeyGenerator(sha512.New384)
|
BytesToKeySHA384 = NewBytesToKeyGenerator(sha512.New384)
|
||||||
|
// BytesToKeySHA512 utilizes SHA512 key-derivation (`-md sha512`)
|
||||||
BytesToKeySHA512 = NewBytesToKeyGenerator(sha512.New)
|
BytesToKeySHA512 = NewBytesToKeyGenerator(sha512.New)
|
||||||
PBKDF2MD5 = NewPBKDF2Generator(md5.New, DefaultPBKDF2Iterations)
|
// PBKDF2MD5 utilizes PBKDF2 key derivation with MD5 hashing (`-pbkdf2 -md md5`)
|
||||||
PBKDF2SHA1 = NewPBKDF2Generator(sha1.New, DefaultPBKDF2Iterations)
|
PBKDF2MD5 = NewPBKDF2Generator(md5.New, DefaultPBKDF2Iterations)
|
||||||
PBKDF2SHA256 = NewPBKDF2Generator(sha256.New, DefaultPBKDF2Iterations)
|
// PBKDF2SHA1 utilizes PBKDF2 key derivation with SHA1 hashing (`-pbkdf2 -md sha1`)
|
||||||
PBKDF2SHA384 = NewPBKDF2Generator(sha512.New384, DefaultPBKDF2Iterations)
|
PBKDF2SHA1 = NewPBKDF2Generator(sha1.New, DefaultPBKDF2Iterations)
|
||||||
PBKDF2SHA512 = NewPBKDF2Generator(sha512.New, DefaultPBKDF2Iterations)
|
// PBKDF2SHA256 utilizes PBKDF2 key derivation with SHA256 hashing (`-pbkdf2 -md sha256`)
|
||||||
|
PBKDF2SHA256 = NewPBKDF2Generator(sha256.New, DefaultPBKDF2Iterations)
|
||||||
|
// PBKDF2SHA384 utilizes PBKDF2 key derivation with SHA384 hashing (`-pbkdf2 -md sha384`)
|
||||||
|
PBKDF2SHA384 = NewPBKDF2Generator(sha512.New384, DefaultPBKDF2Iterations)
|
||||||
|
// PBKDF2SHA512 utilizes PBKDF2 key derivation with SHA512 hashing (`-pbkdf2 -md sha512`)
|
||||||
|
PBKDF2SHA512 = NewPBKDF2Generator(sha512.New, DefaultPBKDF2Iterations)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// NewBytesToKeyGenerator implements the openSSLEvpBytesToKey key
|
||||||
|
// derivation functions described in the OpenSSL code as follows:
|
||||||
|
//
|
||||||
// openSSLEvpBytesToKey follows the OpenSSL (undocumented?) convention for extracting the key and IV from passphrase.
|
// openSSLEvpBytesToKey follows the OpenSSL (undocumented?) convention for extracting the key and IV from passphrase.
|
||||||
// It uses the EVP_BytesToKey() method which is basically:
|
// It uses the EVP_BytesToKey() method which is basically:
|
||||||
// D_i = HASH^count(D_(i-1) || password || salt) where || denotes concatentaion, until there are sufficient bytes available
|
// D_i = HASH^count(D_(i-1) || password || salt) where || denotes concatentaion, until there are sufficient bytes available
|
||||||
|
@ -35,14 +60,16 @@ var (
|
||||||
func NewBytesToKeyGenerator(hashFunc func() hash.Hash) CredsGenerator {
|
func NewBytesToKeyGenerator(hashFunc func() hash.Hash) CredsGenerator {
|
||||||
df := func(in []byte) []byte {
|
df := func(in []byte) []byte {
|
||||||
h := hashFunc()
|
h := hashFunc()
|
||||||
h.Write(in)
|
if _, err := h.Write(in); err != nil {
|
||||||
|
panic(fmt.Errorf("writing to hash: %w", err))
|
||||||
|
}
|
||||||
return h.Sum(nil)
|
return h.Sum(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return func(password, salt []byte) (Creds, error) {
|
return func(password, salt []byte) (Creds, error) {
|
||||||
var m []byte
|
var m []byte
|
||||||
prev := []byte{}
|
prev := []byte{}
|
||||||
for len(m) < 48 {
|
for len(m) < opensslKeyLength+opensslIVLength {
|
||||||
a := make([]byte, len(prev)+len(password)+len(salt))
|
a := make([]byte, len(prev)+len(password)+len(salt))
|
||||||
copy(a, prev)
|
copy(a, prev)
|
||||||
copy(a[len(prev):], password)
|
copy(a[len(prev):], password)
|
||||||
|
@ -51,13 +78,15 @@ func NewBytesToKeyGenerator(hashFunc func() hash.Hash) CredsGenerator {
|
||||||
prev = df(a)
|
prev = df(a)
|
||||||
m = append(m, prev...)
|
m = append(m, prev...)
|
||||||
}
|
}
|
||||||
return Creds{Key: m[:32], IV: m[32:48]}, nil
|
return Creds{Key: m[:opensslKeyLength], IV: m[opensslKeyLength : opensslKeyLength+opensslIVLength]}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewPBKDF2Generator implements a credential generator compatible
|
||||||
|
// with the OpenSSL `-pbkdf2` parameter
|
||||||
func NewPBKDF2Generator(hashFunc func() hash.Hash, iterations int) CredsGenerator {
|
func NewPBKDF2Generator(hashFunc func() hash.Hash, iterations int) CredsGenerator {
|
||||||
return func(password, salt []byte) (Creds, error) {
|
return func(password, salt []byte) (Creds, error) {
|
||||||
m := pbkdf2.Key(password, salt, iterations, 32+16, hashFunc)
|
m := pbkdf2.Key(password, salt, iterations, opensslKeyLength+opensslIVLength, hashFunc)
|
||||||
return Creds{Key: m[:32], IV: m[32:48]}, nil
|
return Creds{Key: m[:opensslKeyLength], IV: m[opensslKeyLength : opensslKeyLength+opensslIVLength]}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
26
openssl.go
26
openssl.go
|
@ -1,3 +1,5 @@
|
||||||
|
// Package openssl contains a pure Go implementation of an OpenSSL
|
||||||
|
// compatible encryption / decryption
|
||||||
package openssl
|
package openssl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -11,6 +13,8 @@ import (
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const opensslSaltLength = 8
|
||||||
|
|
||||||
// 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")
|
||||||
|
|
||||||
|
@ -68,7 +72,7 @@ func (o OpenSSL) DecryptBytes(passphrase string, encryptedBase64Data []byte, cg
|
||||||
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 {
|
||||||
return nil, fmt.Errorf("could not decode data: %s", err)
|
return nil, fmt.Errorf("decoding data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Truncate to real message length
|
// Truncate to real message length
|
||||||
|
@ -89,7 +93,7 @@ func (o OpenSSL) DecryptBytes(passphrase string, encryptedBase64Data []byte, cg
|
||||||
// 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, cg CredsGenerator) ([]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 too short")
|
||||||
}
|
}
|
||||||
saltHeader := encryptedData[:aes.BlockSize]
|
saltHeader := encryptedData[:aes.BlockSize]
|
||||||
if string(saltHeader[:8]) != o.openSSLSaltHeader {
|
if string(saltHeader[:8]) != o.openSSLSaltHeader {
|
||||||
|
@ -110,7 +114,7 @@ func (o OpenSSL) decrypt(key, iv, data []byte) ([]byte, error) {
|
||||||
}
|
}
|
||||||
c, err := aes.NewCipher(key)
|
c, err := aes.NewCipher(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("creating AES cipher: %w", err)
|
||||||
}
|
}
|
||||||
cbc := cipher.NewCBCDecrypter(c, iv)
|
cbc := cipher.NewCBCDecrypter(c, iv)
|
||||||
cbc.CryptBlocks(data[aes.BlockSize:], data[aes.BlockSize:])
|
cbc.CryptBlocks(data[aes.BlockSize:], data[aes.BlockSize:])
|
||||||
|
@ -174,7 +178,7 @@ func (o OpenSSL) encrypt(key, iv, data []byte) ([]byte, error) {
|
||||||
|
|
||||||
c, err := aes.NewCipher(key)
|
c, err := aes.NewCipher(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("creating AES cipher: %w", err)
|
||||||
}
|
}
|
||||||
cbc := cipher.NewCBCEncrypter(c, iv)
|
cbc := cipher.NewCBCEncrypter(c, iv)
|
||||||
cbc.CryptBlocks(padded[aes.BlockSize:], padded[aes.BlockSize:])
|
cbc.CryptBlocks(padded[aes.BlockSize:], padded[aes.BlockSize:])
|
||||||
|
@ -195,13 +199,13 @@ 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, cg CredsGenerator) ([]byte, error) {
|
func (o OpenSSL) EncryptBinaryBytesWithSaltAndDigestFunc(passphrase string, salt, plainData []byte, cg CredsGenerator) ([]byte, error) {
|
||||||
if len(salt) != 8 {
|
if len(salt) != opensslSaltLength {
|
||||||
return nil, ErrInvalidSalt
|
return nil, ErrInvalidSalt
|
||||||
}
|
}
|
||||||
|
|
||||||
data := make([]byte, len(plainData)+aes.BlockSize)
|
data := make([]byte, len(plainData)+aes.BlockSize)
|
||||||
copy(data[0:], o.openSSLSaltHeader)
|
copy(data[0:], o.openSSLSaltHeader)
|
||||||
copy(data[8:], salt)
|
copy(data[len(o.openSSLSaltHeader):], salt)
|
||||||
copy(data[aes.BlockSize:], plainData)
|
copy(data[aes.BlockSize:], plainData)
|
||||||
|
|
||||||
creds, err := cg([]byte(passphrase), salt)
|
creds, err := cg([]byte(passphrase), salt)
|
||||||
|
@ -218,11 +222,11 @@ func (o OpenSSL) EncryptBinaryBytesWithSaltAndDigestFunc(passphrase string, salt
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateSalt generates a random 8 byte salt
|
// GenerateSalt generates a random 8 byte salt
|
||||||
func (o OpenSSL) GenerateSalt() ([]byte, error) {
|
func (OpenSSL) GenerateSalt() ([]byte, error) {
|
||||||
salt := make([]byte, 8) // Generate an 8 byte salt
|
salt := make([]byte, opensslSaltLength) // Generate an 8 byte salt
|
||||||
_, err := io.ReadFull(rand.Reader, salt)
|
_, err := io.ReadFull(rand.Reader, salt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("reading random salt: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return salt, nil
|
return salt, nil
|
||||||
|
@ -239,7 +243,7 @@ func (o OpenSSL) MustGenerateSalt() []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
// pkcs7Pad appends padding.
|
// pkcs7Pad appends padding.
|
||||||
func (o OpenSSL) pkcs7Pad(data []byte, blocklen int) ([]byte, error) {
|
func (OpenSSL) pkcs7Pad(data []byte, blocklen int) ([]byte, error) {
|
||||||
if blocklen <= 0 {
|
if blocklen <= 0 {
|
||||||
return nil, fmt.Errorf("invalid blocklen %d", blocklen)
|
return nil, fmt.Errorf("invalid blocklen %d", blocklen)
|
||||||
}
|
}
|
||||||
|
@ -253,7 +257,7 @@ func (o OpenSSL) pkcs7Pad(data []byte, blocklen int) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// pkcs7Unpad returns slice of the original data without padding.
|
// pkcs7Unpad returns slice of the original data without padding.
|
||||||
func (o OpenSSL) pkcs7Unpad(data []byte, blocklen int) ([]byte, error) {
|
func (OpenSSL) pkcs7Unpad(data []byte, blocklen int) ([]byte, error) {
|
||||||
if blocklen <= 0 {
|
if blocklen <= 0 {
|
||||||
return nil, fmt.Errorf("invalid blocklen %d", blocklen)
|
return nil, fmt.Errorf("invalid blocklen %d", blocklen)
|
||||||
}
|
}
|
||||||
|
|
249
openssl_test.go
249
openssl_test.go
|
@ -6,6 +6,14 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testPassphrase = "z4yH36a6zerhfE5427ZV" //#nosec G101 -- Is hardcoded passphrase but only for testing purposes
|
||||||
|
testPlaintext = "hallowelt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testTable = []struct {
|
var testTable = []struct {
|
||||||
|
@ -27,72 +35,48 @@ var testTable = []struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBinaryEncryptToDecryptWithCustomSalt(t *testing.T) {
|
func TestBinaryEncryptToDecryptWithCustomSalt(t *testing.T) {
|
||||||
plaintext := "hallowelt"
|
|
||||||
passphrase := "z4yH36a6zerhfE5427ZV"
|
|
||||||
salt := []byte("saltsalt")
|
salt := []byte("saltsalt")
|
||||||
|
|
||||||
o := New()
|
o := New()
|
||||||
|
|
||||||
enc, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), BytesToKeySHA256)
|
enc, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(testPassphrase, salt, []byte(testPlaintext), BytesToKeySHA256)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("Test errored at encrypt: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dec, err := o.DecryptBinaryBytes(passphrase, enc, BytesToKeySHA256)
|
dec, err := o.DecryptBinaryBytes(testPassphrase, enc, BytesToKeySHA256)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("Test errored at decrypt: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(dec) != plaintext {
|
assert.Equal(t, testPlaintext, string(dec))
|
||||||
t.Errorf("Decrypted text did not match input.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBinaryEncryptToDecrypt(t *testing.T) {
|
func TestBinaryEncryptToDecrypt(t *testing.T) {
|
||||||
plaintext := "hallowelt"
|
|
||||||
passphrase := "z4yH36a6zerhfE5427ZV"
|
|
||||||
|
|
||||||
o := New()
|
o := New()
|
||||||
|
|
||||||
enc, err := o.EncryptBinaryBytes(passphrase, []byte(plaintext), BytesToKeySHA256)
|
enc, err := o.EncryptBinaryBytes(testPassphrase, []byte(testPlaintext), BytesToKeySHA256)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("Test errored at encrypt: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dec, err := o.DecryptBinaryBytes(passphrase, enc, BytesToKeySHA256)
|
dec, err := o.DecryptBinaryBytes(testPassphrase, enc, BytesToKeySHA256)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("Test errored at decrypt: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(dec) != plaintext {
|
assert.Equal(t, testPlaintext, string(dec))
|
||||||
t.Errorf("Decrypted text did not match input.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBinaryEncryptToOpenSSL(t *testing.T) {
|
func TestBinaryEncryptToOpenSSL(t *testing.T) {
|
||||||
plaintext := "hallowelt"
|
|
||||||
passphrase := "z4yH36a6zerhfE5427ZV"
|
|
||||||
|
|
||||||
o := New()
|
o := New()
|
||||||
|
|
||||||
for _, tc := range testTable {
|
for _, tc := range testTable {
|
||||||
t.Run(tc.tName, func(t *testing.T) {
|
t.Run(tc.tName, func(t *testing.T) {
|
||||||
salt, err := o.GenerateSalt()
|
salt, err := o.GenerateSalt()
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("Failed to generate salt: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
enc, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), tc.tMdFunc)
|
enc, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(testPassphrase, salt, []byte(testPlaintext), tc.tMdFunc)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("Test errored at encrypt: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
||||||
cmdArgs := []string{
|
cmdArgs := []string{
|
||||||
"openssl", "aes-256-cbc",
|
"openssl", "aes-256-cbc",
|
||||||
"-d",
|
"-d",
|
||||||
"-pass", fmt.Sprintf("pass:%s", passphrase),
|
"-pass", fmt.Sprintf("pass:%s", testPassphrase),
|
||||||
"-md", tc.tMdParam,
|
"-md", tc.tMdParam,
|
||||||
"-in", "/dev/stdin",
|
"-in", "/dev/stdin",
|
||||||
}
|
}
|
||||||
|
@ -101,20 +85,16 @@ func TestBinaryEncryptToOpenSSL(t *testing.T) {
|
||||||
cmdArgs = append(cmdArgs, "-pbkdf2")
|
cmdArgs = append(cmdArgs, "-pbkdf2")
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) //#nosec:G204 -- Hardcoded tests, this is fine
|
||||||
|
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
cmd.Stdout = &out
|
cmd.Stdout = &out
|
||||||
cmd.Stdin = bytes.NewBuffer(enc)
|
cmd.Stdin = bytes.NewBuffer(enc)
|
||||||
|
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Errorf("OpenSSL errored: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if out.String() != plaintext {
|
assert.Equal(t, testPlaintext, out.String())
|
||||||
t.Errorf("OpenSSL output did not match input.\nOutput was: %s", out.String())
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,24 +107,15 @@ func TestBinaryEncryptWithSaltShouldHaveSameOutput(t *testing.T) {
|
||||||
o := New()
|
o := New()
|
||||||
|
|
||||||
enc1, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), BytesToKeySHA256)
|
enc1, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), BytesToKeySHA256)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("Test errored at encrypt: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
enc2, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), BytesToKeySHA256)
|
enc2, err := o.EncryptBinaryBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), BytesToKeySHA256)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("Test errored at encrypt: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(enc1) != string(enc2) {
|
assert.Equal(t, enc1, enc2)
|
||||||
t.Errorf("Encrypted outputs are not same.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDecryptBinaryFromString(t *testing.T) {
|
func TestDecryptBinaryFromString(t *testing.T) {
|
||||||
plaintext := "hallowelt"
|
|
||||||
passphrase := "z4yH36a6zerhfE5427ZV"
|
|
||||||
|
|
||||||
o := New()
|
o := New()
|
||||||
|
|
||||||
for _, tc := range testTable {
|
for _, tc := range testTable {
|
||||||
|
@ -153,7 +124,7 @@ func TestDecryptBinaryFromString(t *testing.T) {
|
||||||
|
|
||||||
cmdArgs := []string{
|
cmdArgs := []string{
|
||||||
"openssl", "aes-256-cbc",
|
"openssl", "aes-256-cbc",
|
||||||
"-pass", fmt.Sprintf("pass:%s", passphrase),
|
"-pass", fmt.Sprintf("pass:%s", testPassphrase),
|
||||||
"-md", tc.tMdParam,
|
"-md", tc.tMdParam,
|
||||||
"-in", "/dev/stdin",
|
"-in", "/dev/stdin",
|
||||||
}
|
}
|
||||||
|
@ -162,31 +133,23 @@ func TestDecryptBinaryFromString(t *testing.T) {
|
||||||
cmdArgs = append(cmdArgs, "-pbkdf2")
|
cmdArgs = append(cmdArgs, "-pbkdf2")
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) //#nosec:G204 -- Hardcoded tests, this is fine
|
||||||
cmd.Stdout = &out
|
cmd.Stdout = &out
|
||||||
cmd.Stdin = strings.NewReader(plaintext)
|
cmd.Stdin = strings.NewReader(testPlaintext)
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
require.NoError(t, cmd.Run())
|
||||||
t.Fatalf("Running openssl CLI failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := o.DecryptBinaryBytes(passphrase, out.Bytes(), tc.tMdFunc)
|
data, err := o.DecryptBinaryBytes(testPassphrase, out.Bytes(), tc.tMdFunc)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("Decryption failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(data) != plaintext {
|
if !assert.Equal(t, testPlaintext, string(data)) {
|
||||||
t.Logf("Data: %s\nPlaintext: %s", string(data), plaintext)
|
t.Logf("Data: %s\nPlaintext: %s", string(data), testPlaintext)
|
||||||
t.Errorf("Decryption output did not equal expected output.")
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDecryptFromString(t *testing.T) {
|
func TestDecryptFromString(t *testing.T) {
|
||||||
plaintext := "hallowelt"
|
|
||||||
passphrase := "z4yH36a6zerhfE5427ZV"
|
|
||||||
|
|
||||||
o := New()
|
o := New()
|
||||||
|
|
||||||
for _, tc := range testTable {
|
for _, tc := range testTable {
|
||||||
|
@ -196,7 +159,7 @@ func TestDecryptFromString(t *testing.T) {
|
||||||
cmdArgs := []string{
|
cmdArgs := []string{
|
||||||
"openssl", "aes-256-cbc",
|
"openssl", "aes-256-cbc",
|
||||||
"-base64",
|
"-base64",
|
||||||
"-pass", fmt.Sprintf("pass:%s", passphrase),
|
"-pass", fmt.Sprintf("pass:%s", testPassphrase),
|
||||||
"-md", tc.tMdParam,
|
"-md", tc.tMdParam,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,87 +167,58 @@ func TestDecryptFromString(t *testing.T) {
|
||||||
cmdArgs = append(cmdArgs, "-pbkdf2")
|
cmdArgs = append(cmdArgs, "-pbkdf2")
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) //#nosec:G204 -- Hardcoded tests, this is fine
|
||||||
cmd.Stdout = &out
|
cmd.Stdout = &out
|
||||||
cmd.Stdin = strings.NewReader(plaintext)
|
cmd.Stdin = strings.NewReader(testPlaintext)
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
require.NoError(t, cmd.Run())
|
||||||
t.Fatalf("Running openssl CLI failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := o.DecryptBytes(passphrase, out.Bytes(), tc.tMdFunc)
|
data, err := o.DecryptBytes(testPassphrase, out.Bytes(), tc.tMdFunc)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("Decryption failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(data) != plaintext {
|
if !assert.Equal(t, testPlaintext, string(data)) {
|
||||||
t.Logf("Data: %s\nPlaintext: %s", string(data), plaintext)
|
t.Logf("Data: %s\nPlaintext: %s", string(data), testPlaintext)
|
||||||
t.Errorf("Decryption output did not equal expected output.")
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEncryptToDecrypt(t *testing.T) {
|
func TestEncryptToDecrypt(t *testing.T) {
|
||||||
plaintext := "hallowelt"
|
|
||||||
passphrase := "z4yH36a6zerhfE5427ZV"
|
|
||||||
|
|
||||||
o := New()
|
o := New()
|
||||||
|
|
||||||
enc, err := o.EncryptBytes(passphrase, []byte(plaintext), BytesToKeySHA256)
|
enc, err := o.EncryptBytes(testPassphrase, []byte(testPlaintext), BytesToKeySHA256)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("Test errored at encrypt: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dec, err := o.DecryptBytes(passphrase, enc, BytesToKeySHA256)
|
dec, err := o.DecryptBytes(testPassphrase, enc, BytesToKeySHA256)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("Test errored at decrypt: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(dec) != plaintext {
|
assert.Equal(t, testPlaintext, string(dec))
|
||||||
t.Errorf("Decrypted text did not match input.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEncryptToDecryptWithCustomSalt(t *testing.T) {
|
func TestEncryptToDecryptWithCustomSalt(t *testing.T) {
|
||||||
plaintext := "hallowelt"
|
|
||||||
passphrase := "z4yH36a6zerhfE5427ZV"
|
|
||||||
salt := []byte("saltsalt")
|
salt := []byte("saltsalt")
|
||||||
|
|
||||||
o := New()
|
o := New()
|
||||||
|
|
||||||
enc, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), BytesToKeySHA256)
|
enc, err := o.EncryptBytesWithSaltAndDigestFunc(testPassphrase, salt, []byte(testPlaintext), BytesToKeySHA256)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("Test errored at encrypt: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dec, err := o.DecryptBytes(passphrase, enc, BytesToKeySHA256)
|
dec, err := o.DecryptBytes(testPassphrase, enc, BytesToKeySHA256)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("Test errored at decrypt: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(dec) != plaintext {
|
assert.Equal(t, testPlaintext, string(dec))
|
||||||
t.Errorf("Decrypted text did not match input.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEncryptToOpenSSL(t *testing.T) {
|
func TestEncryptToOpenSSL(t *testing.T) {
|
||||||
plaintext := "hallowelt"
|
|
||||||
passphrase := "z4yH36a6zerhfE5427ZV"
|
|
||||||
|
|
||||||
for _, tc := range testTable {
|
for _, tc := range testTable {
|
||||||
t.Run(tc.tName, func(t *testing.T) {
|
t.Run(tc.tName, func(t *testing.T) {
|
||||||
o := New()
|
o := New()
|
||||||
|
|
||||||
salt, err := o.GenerateSalt()
|
salt, err := o.GenerateSalt()
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("Failed to generate salt: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
enc, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), tc.tMdFunc)
|
enc, err := o.EncryptBytesWithSaltAndDigestFunc(testPassphrase, salt, []byte(testPlaintext), tc.tMdFunc)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("Test errored at encrypt (%s): %s", tc.tMdParam, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
enc = append(enc, '\n')
|
enc = append(enc, '\n')
|
||||||
|
|
||||||
|
@ -293,7 +227,7 @@ func TestEncryptToOpenSSL(t *testing.T) {
|
||||||
cmdArgs := []string{
|
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", testPassphrase),
|
||||||
"-md", tc.tMdParam,
|
"-md", tc.tMdParam,
|
||||||
"-in", "/dev/stdin",
|
"-in", "/dev/stdin",
|
||||||
}
|
}
|
||||||
|
@ -302,18 +236,13 @@ func TestEncryptToOpenSSL(t *testing.T) {
|
||||||
cmdArgs = append(cmdArgs, "-pbkdf2")
|
cmdArgs = append(cmdArgs, "-pbkdf2")
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) //#nosec:G204 -- Hardcoded tests, this is fine
|
||||||
cmd.Stdout = &out
|
cmd.Stdout = &out
|
||||||
cmd.Stdin = bytes.NewReader(enc)
|
cmd.Stdin = bytes.NewReader(enc)
|
||||||
|
|
||||||
err = cmd.Run()
|
require.NoError(t, cmd.Run())
|
||||||
if err != nil {
|
|
||||||
t.Errorf("OpenSSL errored (%s): %s", tc.tMdParam, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if out.String() != plaintext {
|
assert.Equal(t, testPlaintext, out.String())
|
||||||
t.Errorf("OpenSSL output did not match input.\nOutput was (%s): %s", tc.tMdParam, out.String())
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -326,18 +255,12 @@ func TestEncryptWithSaltShouldHaveSameOutput(t *testing.T) {
|
||||||
o := New()
|
o := New()
|
||||||
|
|
||||||
enc1, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), BytesToKeySHA256)
|
enc1, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), BytesToKeySHA256)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("Test errored at encrypt: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
enc2, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), BytesToKeySHA256)
|
enc2, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, salt, []byte(plaintext), BytesToKeySHA256)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("Test errored at encrypt: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(enc1) != string(enc2) {
|
assert.Equal(t, enc1, enc2)
|
||||||
t.Errorf("Encrypted outputs are not same.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateSalt(t *testing.T) {
|
func TestGenerateSalt(t *testing.T) {
|
||||||
|
@ -347,36 +270,27 @@ func TestGenerateSalt(t *testing.T) {
|
||||||
|
|
||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
salt, err := o.GenerateSalt()
|
salt, err := o.GenerateSalt()
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("Failed to generate salt: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ks := range knownSalts {
|
for _, ks := range knownSalts {
|
||||||
if bytes.Equal(ks, salt) {
|
assert.NotEqual(t, ks, salt)
|
||||||
t.Errorf("Duplicate salt detected")
|
|
||||||
}
|
|
||||||
knownSalts = append(knownSalts, salt)
|
knownSalts = append(knownSalts, salt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSaltValidation(t *testing.T) {
|
func TestSaltValidation(t *testing.T) {
|
||||||
plaintext := "hallowelt"
|
var err error
|
||||||
passphrase := "z4yH36a6zerhfE5427ZV"
|
|
||||||
|
|
||||||
o := New()
|
o := New()
|
||||||
|
|
||||||
if _, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, []byte("12345"), []byte(plaintext), BytesToKeySHA256); err != ErrInvalidSalt {
|
_, err = o.EncryptBytesWithSaltAndDigestFunc(testPassphrase, []byte("12345"), []byte(testPlaintext), BytesToKeySHA256)
|
||||||
t.Errorf("5-character salt was accepted, needs to have 8 character")
|
assert.ErrorIs(t, err, ErrInvalidSalt)
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, []byte("1234567890"), []byte(plaintext), BytesToKeySHA256); err != ErrInvalidSalt {
|
_, err = o.EncryptBytesWithSaltAndDigestFunc(testPassphrase, []byte("1234567890"), []byte(testPlaintext), BytesToKeySHA256)
|
||||||
t.Errorf("10-character salt was accepted, needs to have 8 character")
|
assert.ErrorIs(t, err, ErrInvalidSalt)
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := o.EncryptBytesWithSaltAndDigestFunc(passphrase, []byte{0xcb, 0xd5, 0x1a, 0x3, 0x84, 0xba, 0xa8, 0xc8}, []byte(plaintext), BytesToKeySHA256); err == ErrInvalidSalt {
|
_, err = o.EncryptBytesWithSaltAndDigestFunc(testPassphrase, []byte{0xcb, 0xd5, 0x1a, 0x3, 0x84, 0xba, 0xa8, 0xc8}, []byte(testPlaintext), BytesToKeySHA256)
|
||||||
t.Errorf("Salt with 8 byte unprintable characters was not accepted")
|
assert.NoError(t, err)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -384,11 +298,11 @@ func TestSaltValidation(t *testing.T) {
|
||||||
//
|
//
|
||||||
|
|
||||||
func benchmarkDecrypt(ciphertext []byte, cg CredsGenerator, b *testing.B) {
|
func benchmarkDecrypt(ciphertext []byte, cg CredsGenerator, b *testing.B) {
|
||||||
passphrase := "z4yH36a6zerhfE5427ZV"
|
|
||||||
o := New()
|
o := New()
|
||||||
|
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
o.DecryptBytes(passphrase, ciphertext, cg)
|
_, err := o.DecryptBytes(testPassphrase, ciphertext, cg)
|
||||||
|
require.NoError(b, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,30 +319,31 @@ func BenchmarkDecryptSHA256(b *testing.B) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func benchmarkEncrypt(plaintext string, cg CredsGenerator, b *testing.B) {
|
func benchmarkEncrypt(plaintext string, cg CredsGenerator, b *testing.B) {
|
||||||
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), cg)
|
_, err := o.EncryptBytesWithSaltAndDigestFunc(testPassphrase, salt, []byte(plaintext), cg)
|
||||||
|
require.NoError(b, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkEncryptMD5(b *testing.B) {
|
func BenchmarkEncryptMD5(b *testing.B) {
|
||||||
benchmarkEncrypt("hallowelt", BytesToKeyMD5, b)
|
benchmarkEncrypt(testPlaintext, BytesToKeyMD5, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkEncryptSHA1(b *testing.B) {
|
func BenchmarkEncryptSHA1(b *testing.B) {
|
||||||
benchmarkEncrypt("hallowelt", BytesToKeySHA1, b)
|
benchmarkEncrypt(testPlaintext, BytesToKeySHA1, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkEncryptSHA256(b *testing.B) {
|
func BenchmarkEncryptSHA256(b *testing.B) {
|
||||||
benchmarkEncrypt("hallowelt", BytesToKeySHA256, b)
|
benchmarkEncrypt(testPlaintext, BytesToKeySHA256, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkGenerateSalt(b *testing.B) {
|
func BenchmarkGenerateSalt(b *testing.B) {
|
||||||
o := New()
|
o := New()
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
o.GenerateSalt()
|
_, err := o.GenerateSalt()
|
||||||
|
require.NoError(b, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,7 +187,7 @@ func (e *EncryptWriter) initMode() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
salt := make([]byte, 8) // Generate an 8 byte salt
|
salt := make([]byte, opensslSaltLength) // Generate an 8 byte salt
|
||||||
_, err := io.ReadFull(rand.Reader, salt)
|
_, err := io.ReadFull(rand.Reader, salt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("read salt failed: %w", err)
|
return fmt.Errorf("read salt failed: %w", err)
|
||||||
|
|
Loading…
Reference in a new issue