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