feat: decrypt Jasypt values in .env and Spring files (#80)
Co-authored-by: Karl-Einar Bergstrøm <karl-einarb@outlook.com>
This commit was merged in pull request #80.
This commit is contained in:
1 parent
96395cb6a2
commit
17da9eaa32
13 files changed
+381
-29
No files matched your search
@@ -0,0 +1,67 @@
|
||||
// Package jasypt decrypts values encrypted with Jasypt the way
|
||||
// jasypt-spring-boot 3.0 and later does by default: ENC(...) around the
|
||||
// base64 of a random salt, a random IV and the ciphertext, with
|
||||
// PBEWITHHMACSHA512ANDAES_256 (PBKDF2-HMAC-SHA512, 1000 iterations,
|
||||
// AES-256-CBC).
|
||||
package jasypt
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/pbkdf2"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const iterations = 1000
|
||||
|
||||
// ErrDecrypt is a value that doesn't decrypt with the password: a wrong
|
||||
// password, or one encrypted with another algorithm.
|
||||
var ErrDecrypt = errors.New("doesn't decrypt: a wrong password, or not Jasypt's default algorithm (PBEWITHHMACSHA512ANDAES_256)")
|
||||
|
||||
// Is reports whether v is a Jasypt value, ENC(...).
|
||||
func Is(v string) bool {
|
||||
return len(v) > len("ENC()") && strings.HasPrefix(v, "ENC(") && strings.HasSuffix(v, ")")
|
||||
}
|
||||
|
||||
// Decrypt decrypts an ENC(...) value.
|
||||
func Decrypt(v, password string) (string, error) {
|
||||
b, err := base64.StdEncoding.DecodeString(strings.TrimSuffix(strings.TrimPrefix(v, "ENC("), ")"))
|
||||
if err != nil {
|
||||
return "", errors.New("not Jasypt: ENC(...) isn't base64")
|
||||
}
|
||||
const size = aes.BlockSize // the salt's and the IV's too
|
||||
if len(b) < 3*size || len(b)%size != 0 {
|
||||
return "", ErrDecrypt
|
||||
}
|
||||
salt, iv, data := b[:size], b[size:2*size], b[2*size:]
|
||||
key, err := pbkdf2.Key(sha512.New, password, salt, iterations, 32)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
plain := make([]byte, len(data))
|
||||
cipher.NewCBCDecrypter(block, iv).CryptBlocks(plain, data)
|
||||
// A wrong password decrypts to noise: its padding is almost never
|
||||
// valid, and the rest rarely UTF-8.
|
||||
n := int(plain[len(plain)-1])
|
||||
if n == 0 || n > size {
|
||||
return "", ErrDecrypt
|
||||
}
|
||||
for _, c := range plain[len(plain)-n:] {
|
||||
if int(c) != n {
|
||||
return "", ErrDecrypt
|
||||
}
|
||||
}
|
||||
plain = plain[:len(plain)-n]
|
||||
if !utf8.Valid(plain) {
|
||||
return "", ErrDecrypt
|
||||
}
|
||||
return string(plain), nil
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package jasypt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Encrypted by Jasypt 1.9.3 itself, set up as jasypt-spring-boot's default
|
||||
// encryptor, with the password "test-password".
|
||||
var vectors = map[string]string{
|
||||
"ENC(5IQUKT4B7j38TnZJ+BlWqo/zSkKHTjl4XRJyaQEHgkCy4r00avLt4dWqFB74X0Zw)": "hunter2",
|
||||
"ENC(w4pm2JVZcbG5R+Ud9uKHg8VhJ/0aAK7sFXtNBv+Vo450SAchTQtfhw0TnIJ9gN+D0VFbwqJzfXU369coTJz2rw==)": "blåbærsyltetøy ✓",
|
||||
}
|
||||
|
||||
func TestDecrypt(t *testing.T) {
|
||||
for enc, want := range vectors {
|
||||
if !Is(enc) {
|
||||
t.Errorf("%s: not Jasypt", enc)
|
||||
}
|
||||
if got, err := Decrypt(enc, "test-password"); err != nil || got != want {
|
||||
t.Errorf("%s: %q %v, want %q", enc, got, err, want)
|
||||
}
|
||||
if _, err := Decrypt(enc, "wrong"); !errors.Is(err, ErrDecrypt) {
|
||||
t.Errorf("%s with a wrong password: %v", enc, err)
|
||||
}
|
||||
}
|
||||
for _, v := range []string{"ENC(not base64!)", "ENC(aGVsbG8=)"} {
|
||||
if _, err := Decrypt(v, "test-password"); err == nil {
|
||||
t.Errorf("%s: no error", v)
|
||||
}
|
||||
}
|
||||
for _, v := range []string{"ENC()", "hunter2", "ENC[AES256_GCM,data:x]", "${DB_PASSWORD}"} {
|
||||
if Is(v) {
|
||||
t.Errorf("%s: Jasypt", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user