Module secret

This commit is contained in:
Benjamin VAUDOUR 2023-09-23 22:15:37 +02:00
parent 58d52fa057
commit 67532c3acc
5 changed files with 321 additions and 0 deletions

2
go.mod
View File

@ -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

2
go.sum Normal file
View File

@ -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=

29
secret/bcrypt.go Normal file
View File

@ -0,0 +1,29 @@
package secret
import (
"golang.org/x/crypto/bcrypt"
)
// Bcrypt permet de calculer un hash en utilisant lalgorithme 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 dentrée.
func (h Bcrypt) Hash(data []byte) []byte {
hashed, _ := bcrypt.GenerateFromPassword(data, h.cost)
return hashed
}
// Compare vérifie quune donnée dentré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
}

142
secret/hash.go Normal file
View File

@ -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 quune 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 dun 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 dentrée en utilisant lalgorithme demandé.
// Si une longueur de sel est fournie, le hachage est créé à partir
// dun 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 dune donnée dentré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 dorigine 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))
}

146
secret/symetric.go Normal file
View File

@ -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 dentrée (avec une clé aléatoire) selon
// lalgorithme 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 dentrée (avec une clé aléatoire) selon
// lalgorithme 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))
}