Module secret
This commit is contained in:
parent
58d52fa057
commit
67532c3acc
2
go.mod
2
go.mod
|
@ -1,3 +1,5 @@
|
||||||
module gitea.zaclys.com/bvaudour/gob
|
module gitea.zaclys.com/bvaudour/gob
|
||||||
|
|
||||||
go 1.21.1
|
go 1.21.1
|
||||||
|
|
||||||
|
require golang.org/x/crypto v0.13.0 // indirect
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||||
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
|
@ -0,0 +1,29 @@
|
||||||
|
package secret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bcrypt permet de calculer un hash en utilisant l’algorithme Bcrypt.
|
||||||
|
type Bcrypt struct {
|
||||||
|
cost int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bcrypt initialise une fonction de hashage de type Bcrypt et de coût cost.
|
||||||
|
func NewBcrypt(cost int) Bcrypt {
|
||||||
|
return Bcrypt{cost: cost}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash retourne le hash des bytes d’entrée.
|
||||||
|
func (h Bcrypt) Hash(data []byte) []byte {
|
||||||
|
hashed, _ := bcrypt.GenerateFromPassword(data, h.cost)
|
||||||
|
|
||||||
|
return hashed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare vérifie qu’une donnée d’entrée a bien pour hash la valeur hashed.
|
||||||
|
func (h Bcrypt) Compare(data []byte, hashed []byte) bool {
|
||||||
|
err := bcrypt.CompareHashAndPassword(hashed, data)
|
||||||
|
|
||||||
|
return err == nil
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
package secret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
|
||||||
|
. "gitea.zaclys.com/bvaudour/gob/option"
|
||||||
|
"gitea.zaclys.com/bvaudour/gob/random"
|
||||||
|
)
|
||||||
|
|
||||||
|
func errAlgorithm(algorithm string) error {
|
||||||
|
return fmt.Errorf("unknown algorithm: %s", algorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hasher est un utilitaire permettant de hasher une chaîne et de vérifier qu’une chaîne correspond bien à un hashage.
|
||||||
|
type Hasher interface {
|
||||||
|
Hash(data []byte) (hashed []byte)
|
||||||
|
Compare(data []byte, hashed []byte) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type hasher struct {
|
||||||
|
h hash.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h hasher) Hash(data []byte) []byte {
|
||||||
|
h.h.Write(data)
|
||||||
|
return h.h.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h hasher) Compare(data []byte, hashed []byte) bool {
|
||||||
|
return string(h.Hash(data)) == string(hashed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashResult est le résultat d’un hash.
|
||||||
|
type HashResult[T ~[]byte | ~string] struct {
|
||||||
|
Hash T
|
||||||
|
Salt T
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashOf(data []byte, saltLen int, h Hasher) (result HashResult[[]byte]) {
|
||||||
|
l := len(data)
|
||||||
|
in := make([]byte, l+saltLen)
|
||||||
|
|
||||||
|
result.Salt = random.BytesKey(saltLen, random.All)
|
||||||
|
copy(in[:l], data)
|
||||||
|
copy(in[l:], result.Salt)
|
||||||
|
result.Hash = h.Hash(in)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashOf génère le hash et un sel aléatoire à une chaîne donnée.
|
||||||
|
func HashOf[T ~[]byte | ~string](data T, saltLen int, h Hasher) (result HashResult[T]) {
|
||||||
|
rb := hashOf([]byte(data), saltLen, h)
|
||||||
|
result.Hash, result.Salt = T(rb.Hash), T(rb.Salt)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
MD5 = "md5"
|
||||||
|
SHA1 = "sha1"
|
||||||
|
SHA256 = "sha256"
|
||||||
|
SHA512 = "sha512"
|
||||||
|
BCRYPT = "bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
hashers = map[string]Hasher{
|
||||||
|
MD5: hasher{md5.New()},
|
||||||
|
SHA1: hasher{sha1.New()},
|
||||||
|
SHA256: hasher{sha256.New()},
|
||||||
|
SHA512: hasher{sha512.New()},
|
||||||
|
BCRYPT: NewBcrypt(14),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// AvailableHashAlgorithms retourne la liste des noms des algorithmes de hachage supportés.
|
||||||
|
func AvailableHashAlgorithms() (out []string) {
|
||||||
|
for h := range hashers {
|
||||||
|
out = append(out, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsHashAlgorithmAvailable vérifie si le nom fournit en entrée fait partie des algorithmes de hachage supportés.
|
||||||
|
func IsHashAlgorithmAvailable(name string) bool {
|
||||||
|
_, exists := hashers[name]
|
||||||
|
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHashAlgorithm ajoute un algorithme de hashage à disposition.
|
||||||
|
func AddHashAlgorithm(name string, hasher Hasher) {
|
||||||
|
hashers[name] = hasher
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash chiffre la donnée d’entrée en utilisant l’algorithme demandé.
|
||||||
|
// Si une longueur de sel est fournie, le hachage est créé à partir
|
||||||
|
// d’un sel défini aléatoirement.
|
||||||
|
// Les algorithmes supportés par défaut sont :
|
||||||
|
// - md5
|
||||||
|
// - sha1
|
||||||
|
// - sha256
|
||||||
|
// - sha512
|
||||||
|
// - bcrypt
|
||||||
|
func Hash[T ~[]byte | ~string](data T, algorithm string, saltLen ...int) (result Result[HashResult[T]]) {
|
||||||
|
h, ok := hashers[algorithm]
|
||||||
|
if !ok {
|
||||||
|
return Err[HashResult[T]](errAlgorithm(algorithm))
|
||||||
|
}
|
||||||
|
|
||||||
|
sl := 0
|
||||||
|
if len(saltLen) > 0 && saltLen[0] > 0 {
|
||||||
|
sl = saltLen[0]
|
||||||
|
}
|
||||||
|
r := HashOf(data, sl, h)
|
||||||
|
|
||||||
|
return Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckHash vérifie le hash d’une donnée d’entrée avec un algorithme de hashage.
|
||||||
|
// Si le hash a été généré avec du sel, la chaîne doit être la concaténation
|
||||||
|
// de la chaîne d’origine et du sel.
|
||||||
|
func CheckHash[T ~[]byte | ~string](data T, hash HashResult[T], algorithm string) bool {
|
||||||
|
h, ok := hashers[algorithm]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ld, ls := len(data), len(hash.Salt)
|
||||||
|
in := make([]byte, ld+ls)
|
||||||
|
copy(in[:ld], []byte(data))
|
||||||
|
copy(in[ld:], []byte(hash.Salt))
|
||||||
|
|
||||||
|
return h.Compare(in, []byte(hash.Hash))
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
package secret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
. "gitea.zaclys.com/bvaudour/gob/option"
|
||||||
|
"golang.org/x/crypto/blowfish"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AES = "aes"
|
||||||
|
BLOWFISH = "blowfish"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CipherFunc func([]byte) (cipher.Block, error)
|
||||||
|
|
||||||
|
var (
|
||||||
|
encoders = map[string]CipherFunc{
|
||||||
|
AES: aes.NewCipher,
|
||||||
|
BLOWFISH: func(key []byte) (c cipher.Block, err error) {
|
||||||
|
c, err = blowfish.NewCipher(key)
|
||||||
|
return
|
||||||
|
},
|
||||||
|
}
|
||||||
|
blockSize = map[string]int{
|
||||||
|
AES: aes.BlockSize,
|
||||||
|
BLOWFISH: blowfish.BlockSize,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func addBase64Padding(value string) string {
|
||||||
|
m := len(value) % 4
|
||||||
|
if m != 0 {
|
||||||
|
value += strings.Repeat("=", 4-m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeToBase64[T ~[]byte | ~string](value []byte) T {
|
||||||
|
encoded := strings.Replace(base64.URLEncoding.EncodeToString(value), "=", "", -1)
|
||||||
|
return T(encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeFromBase64[T ~[]byte | ~string](value T) Result[[]byte] {
|
||||||
|
decoded, err := base64.URLEncoding.DecodeString(addBase64Padding(string(value)))
|
||||||
|
if err != nil {
|
||||||
|
return Err[[]byte](err)
|
||||||
|
}
|
||||||
|
return Ok([]byte(decoded))
|
||||||
|
}
|
||||||
|
|
||||||
|
func pad(src []byte, bs int) []byte {
|
||||||
|
padding := bs - len(src)%bs
|
||||||
|
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||||
|
return append(src, padtext...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unpad(src []byte) Result[[]byte] {
|
||||||
|
length := len(src)
|
||||||
|
unpadding := int(src[length-1])
|
||||||
|
|
||||||
|
if unpadding > length {
|
||||||
|
return Err[[]byte](errors.New("unpad error. This could happen when incorrect encryption key is used"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(src[:(length - unpadding)])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt chiffre une donnée d’entrée (avec une clé aléatoire) selon
|
||||||
|
// l’algorithme demandé. Les algorithmes supportés sont :
|
||||||
|
// - blowfish
|
||||||
|
// - aes
|
||||||
|
func Encrypt[T ~[]byte | ~string](data, key T, algorithm string) Result[T] {
|
||||||
|
c, ok := encoders[algorithm]
|
||||||
|
if !ok {
|
||||||
|
return Err[T](errAlgorithm(algorithm))
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := c([]byte(key))
|
||||||
|
if err != nil {
|
||||||
|
return Err[T](err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bs := blockSize[algorithm]
|
||||||
|
msg := pad([]byte(data), bs)
|
||||||
|
cipherText := make([]byte, bs+len(msg))
|
||||||
|
iv := cipherText[:bs]
|
||||||
|
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||||
|
return Err[T](err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfb := cipher.NewCFBEncrypter(block, iv)
|
||||||
|
cfb.XORKeyStream(cipherText[bs:], msg)
|
||||||
|
|
||||||
|
return Ok(encodeToBase64[T](cipherText))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypte déchiffre une donnée d’entrée (avec une clé aléatoire) selon
|
||||||
|
// l’algorithme demandé. Les algorithmes supportés sont :
|
||||||
|
// - blowfish
|
||||||
|
// - aes
|
||||||
|
func Decrypt[T ~[]byte | ~string](encrypted, key T, algorithm string) Result[T] {
|
||||||
|
c, ok := encoders[algorithm]
|
||||||
|
if !ok {
|
||||||
|
return Err[T](errAlgorithm(algorithm))
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := c([]byte(key))
|
||||||
|
if err != nil {
|
||||||
|
return Err[T](err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded := decodeFromBase64(encrypted)
|
||||||
|
if err, ok := decoded.Err(); ok {
|
||||||
|
return Err[T](err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dec, _ := decoded.Ok()
|
||||||
|
bs := blockSize[algorithm]
|
||||||
|
|
||||||
|
if (len(dec) % bs) != 0 {
|
||||||
|
return Err[T](errors.New("blocksize must be multiple of data length"))
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := dec[:bs]
|
||||||
|
msg := dec[bs:]
|
||||||
|
|
||||||
|
cfb := cipher.NewCFBDecrypter(block, iv)
|
||||||
|
cfb.XORKeyStream(msg, msg)
|
||||||
|
|
||||||
|
unpadMsg := unpad(msg)
|
||||||
|
if err, ok := unpadMsg.Err(); ok {
|
||||||
|
return Err[T](err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, _ := unpadMsg.Ok()
|
||||||
|
return Ok(T(result))
|
||||||
|
}
|
Loading…
Reference in New Issue