restructure project, add claudemd
This commit is contained in:
228
go/security/crypt.go
Normal file
228
go/security/crypt.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcutil/base58"
|
||||
"github.com/minio/highwayhash"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// dataHashKey is NOT used for hashing passwords or securing session data over
|
||||
// the wire. It is ONLY used for quick, non-security-sensitive file and string
|
||||
// hashes (HighwayHash needs a fixed 32-byte key). Kept in the framework because
|
||||
// the value must stay stable across builds and is identical in every app.
|
||||
const dataHashKey = "01234567890123456789012345678901"
|
||||
|
||||
////////////////////////////////
|
||||
// Encoding Wrappers
|
||||
////////////////////////////////
|
||||
|
||||
func EncodeBase64(in []byte) string {
|
||||
return base64.StdEncoding.EncodeToString(in)
|
||||
}
|
||||
|
||||
func DecodeBase64(in string) []byte {
|
||||
out, _ := base64.StdEncoding.DecodeString(in)
|
||||
return out
|
||||
}
|
||||
|
||||
func EncodeBase58(in []byte) string {
|
||||
return base58.Encode(in)
|
||||
}
|
||||
|
||||
func DecodeBase58(in string) []byte {
|
||||
return base58.Decode(in)
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// HASH FUNCTIONS
|
||||
////////////////////////////////
|
||||
|
||||
// Hash with SHA512 and output a Base58 string
|
||||
func SHA512_58(in string) string {
|
||||
hasher := sha512.New()
|
||||
|
||||
hasher.Write([]byte(in))
|
||||
|
||||
hashBytes := hasher.Sum(nil)
|
||||
|
||||
hashString := base58.Encode(hashBytes)
|
||||
|
||||
return hashString
|
||||
}
|
||||
|
||||
func HighwayHash58(in string) (string, error) {
|
||||
key := []byte(dataHashKey)
|
||||
|
||||
hasher, err := highwayhash.New(key)
|
||||
if err != nil {
|
||||
log.Println("Error generating hasher.")
|
||||
return "", err
|
||||
}
|
||||
|
||||
hasher.Write([]byte(in))
|
||||
|
||||
hash := hasher.Sum(nil)
|
||||
|
||||
encodedData := base58.Encode(hash)
|
||||
|
||||
return encodedData, nil
|
||||
}
|
||||
|
||||
func HighwayHash(in string) (string, error) {
|
||||
key := []byte(dataHashKey)
|
||||
|
||||
hasher, err := highwayhash.New(key)
|
||||
if err != nil {
|
||||
log.Println("Error generating hasher.")
|
||||
return "", err
|
||||
}
|
||||
|
||||
hasher.Write([]byte(in))
|
||||
|
||||
hash := hasher.Sum(nil)
|
||||
|
||||
return base64.StdEncoding.EncodeToString(hash), nil
|
||||
}
|
||||
|
||||
// Hash password using bcrypt
|
||||
func HashPassword(password string) (string, error) {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
// Compare password with hash using bcrypt
|
||||
func ComparePasswords(password string, hash string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func RandBase58String(entropyBytes int) string {
|
||||
b := make([]byte, entropyBytes)
|
||||
rand.Read(b)
|
||||
return base58.Encode(b)
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Serialization FUNCTIONS
|
||||
////////////////////////////////
|
||||
|
||||
func GobSerialize[T any](data *T) ([]byte, error) {
|
||||
gob.Register(time.Time{})
|
||||
gob.Register(uuid.UUID{})
|
||||
|
||||
b := bytes.Buffer{}
|
||||
e := gob.NewEncoder(&b)
|
||||
err := e.Encode(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func GobDeserialize[T any](data []byte) (*T, error) {
|
||||
dest := new(T)
|
||||
|
||||
b := bytes.Buffer{}
|
||||
b.Write(data)
|
||||
|
||||
gob.Register(time.Time{})
|
||||
gob.Register(uuid.UUID{})
|
||||
d := gob.NewDecoder(&b)
|
||||
err := d.Decode(dest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dest, nil
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Encryption FUNCTIONS
|
||||
////////////////////////////////
|
||||
|
||||
// AES Encrypt
|
||||
func EncryptSecret(data []byte, passKey string) ([]byte, error) {
|
||||
key := make([]byte, 32)
|
||||
copy(key, passKey)
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encryptedData := gcm.Seal(nonce, nonce, data, nil)
|
||||
|
||||
return encryptedData, nil
|
||||
}
|
||||
|
||||
// AES Decrypt
|
||||
func DecryptSecret(encryptedData []byte, passKey string) ([]byte, error) {
|
||||
key := make([]byte, 32)
|
||||
copy(key, passKey)
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonceSize := gcm.NonceSize()
|
||||
if len(encryptedData) < nonceSize {
|
||||
return nil, fmt.Errorf("ciphertext too short")
|
||||
}
|
||||
nonce, encryptedData := encryptedData[:nonceSize], encryptedData[nonceSize:]
|
||||
|
||||
decryptedData, err := gcm.Open(nil, nonce, encryptedData, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decryptedData, nil
|
||||
}
|
||||
|
||||
func EncryptData[T any](data *T, key string) ([]byte, error) {
|
||||
serialized, err := GobSerialize(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return EncryptSecret(serialized, key)
|
||||
}
|
||||
|
||||
func DecryptData[T any](data []byte, key string) (*T, error) {
|
||||
decrypted, err := DecryptSecret(data, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return GobDeserialize[T](decrypted)
|
||||
}
|
||||
9
go/security/crypt_test.go
Normal file
9
go/security/crypt_test.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package security
|
||||
|
||||
import "testing"
|
||||
|
||||
func _BenchmarkDecrypt(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
DecryptSecret([]byte("njniYY9+R8kAxUuoI6p+A0AvDfVwtKVKe7FU7q7eW4IlLF1v4hLF14Fwizsddqh54EjiBB2XwD6g07c2Ovd0p8AehEuZgA8vD1N+3zSKKg+ZDVsc/MS+6iNQYK+ARNYHrqreaB2qiJP260Le3YR3xDY/u7n+JN58FxNf2J1DMvBUXD812d7r3ING4TBTkzcCJFXql+TvzUdC1qnhdrz/AOBo919rP2+yodQRTgBsZPiSb0DCZ9nnuwT9t99ORwn8v3AelyzwBOcxiYSlP07WDQE45o962E+GONiA09q8lBIBV6wT5bgZ3GAOdNNJFPrhSUqhblDB8/16Z1NwhS/lHyQUyjGxwt3zsC3axVCNQ6t4AJr8wEyVnoLb"), "password")
|
||||
}
|
||||
}
|
||||
11
go/security/init.go
Normal file
11
go/security/init.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package security
|
||||
|
||||
import "github.com/microcosm-cc/bluemonday"
|
||||
|
||||
var SanitizationPolicy *bluemonday.Policy
|
||||
|
||||
func Init() {
|
||||
SanitizationPolicy = bluemonday.UGCPolicy()
|
||||
SanitizationPolicy.AllowElements("svg", "path")
|
||||
SanitizationPolicy.AllowAttrs("xmlns", "height", "width", "fill", "stroke", "d")
|
||||
}
|
||||
37
go/security/random.go
Normal file
37
go/security/random.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcutil/base58"
|
||||
)
|
||||
|
||||
func GenerateRandomKeyBase64(bytes int) (string, error) {
|
||||
if bytes <= 0 {
|
||||
return "", fmt.Errorf("key size must be positive")
|
||||
}
|
||||
|
||||
key := make([]byte, bytes)
|
||||
_, err := rand.Read(key)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate random key: %w", err)
|
||||
}
|
||||
|
||||
return base64.StdEncoding.EncodeToString(key), nil
|
||||
}
|
||||
|
||||
func GenerateRandomKeyBase58(bytes int) (string, error) {
|
||||
if bytes <= 0 {
|
||||
return "", fmt.Errorf("key size must be positive")
|
||||
}
|
||||
|
||||
key := make([]byte, bytes)
|
||||
_, err := rand.Read(key)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate random key: %w", err)
|
||||
}
|
||||
|
||||
return base58.Encode(key), nil
|
||||
}
|
||||
172
go/security/random_test.go
Normal file
172
go/security/random_test.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcutil/base58"
|
||||
)
|
||||
|
||||
func TestGenerateRandomKeyBase64(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
n int
|
||||
wantError bool
|
||||
}{
|
||||
{
|
||||
name: "valid 16 bytes",
|
||||
n: 16,
|
||||
wantError: false,
|
||||
},
|
||||
{
|
||||
name: "valid 32 bytes",
|
||||
n: 32,
|
||||
wantError: false,
|
||||
},
|
||||
{
|
||||
name: "valid 64 bytes",
|
||||
n: 64,
|
||||
wantError: false,
|
||||
},
|
||||
{
|
||||
name: "invalid zero bytes",
|
||||
n: 0,
|
||||
wantError: true,
|
||||
},
|
||||
{
|
||||
name: "invalid negative bytes",
|
||||
n: -1,
|
||||
wantError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := GenerateRandomKeyBase64(tt.n)
|
||||
|
||||
if tt.wantError {
|
||||
if err == nil {
|
||||
t.Error("expected error but got none")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify result is not empty
|
||||
if result == "" {
|
||||
t.Error("expected non-empty result")
|
||||
}
|
||||
|
||||
// Verify result is valid base64
|
||||
decoded, err := base64.StdEncoding.DecodeString(result)
|
||||
if err != nil {
|
||||
t.Errorf("result is not valid base64: %v", err)
|
||||
}
|
||||
|
||||
// Verify decoded length matches input
|
||||
if len(decoded) != tt.n {
|
||||
t.Errorf("expected decoded length %d, got %d", tt.n, len(decoded))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Test randomness - two calls should produce different results
|
||||
t.Run("randomness check", func(t *testing.T) {
|
||||
result1, err1 := GenerateRandomKeyBase64(32)
|
||||
result2, err2 := GenerateRandomKeyBase64(32)
|
||||
|
||||
if err1 != nil || err2 != nil {
|
||||
t.Fatalf("unexpected errors: %v, %v", err1, err2)
|
||||
}
|
||||
|
||||
if result1 == result2 {
|
||||
t.Error("expected different random keys, got identical results")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGenerateRandomKeyBase58(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
n int
|
||||
wantError bool
|
||||
}{
|
||||
{
|
||||
name: "valid 16 bytes",
|
||||
n: 16,
|
||||
wantError: false,
|
||||
},
|
||||
{
|
||||
name: "valid 32 bytes",
|
||||
n: 32,
|
||||
wantError: false,
|
||||
},
|
||||
{
|
||||
name: "valid 64 bytes",
|
||||
n: 64,
|
||||
wantError: false,
|
||||
},
|
||||
{
|
||||
name: "invalid zero bytes",
|
||||
n: 0,
|
||||
wantError: true,
|
||||
},
|
||||
{
|
||||
name: "invalid negative bytes",
|
||||
n: -1,
|
||||
wantError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := GenerateRandomKeyBase58(tt.n)
|
||||
|
||||
if tt.wantError {
|
||||
if err == nil {
|
||||
t.Error("expected error but got none")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify result is not empty
|
||||
if result == "" {
|
||||
t.Error("expected non-empty result")
|
||||
}
|
||||
|
||||
// Verify result is valid base58 by decoding
|
||||
decoded := base58.Decode(result)
|
||||
if len(decoded) == 0 {
|
||||
t.Error("result is not valid base58")
|
||||
}
|
||||
|
||||
// Verify decoded length matches input
|
||||
if len(decoded) != tt.n {
|
||||
t.Errorf("expected decoded length %d, got %d", tt.n, len(decoded))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Test randomness - two calls should produce different results
|
||||
t.Run("randomness check", func(t *testing.T) {
|
||||
result1, err1 := GenerateRandomKeyBase58(32)
|
||||
result2, err2 := GenerateRandomKeyBase58(32)
|
||||
|
||||
if err1 != nil || err2 != nil {
|
||||
t.Fatalf("unexpected errors: %v, %v", err1, err2)
|
||||
}
|
||||
|
||||
if result1 == result2 {
|
||||
t.Error("expected different random keys, got identical results")
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user