basic common app packages

This commit is contained in:
2025-06-16 22:19:00 +05:30
parent 8654f21b62
commit 0240ec154e
49 changed files with 5481 additions and 232 deletions

59
crypto/argon2.go Normal file
View File

@@ -0,0 +1,59 @@
// Copyright 2024 Patial Tech (Ankit Patial).
// All rights reserved.
package crypto
import (
"bytes"
"encoding/base64"
"golang.org/x/crypto/argon2"
)
const (
a2KeyMem = 19456 // 1024*19
a2KeyLen = 32
)
func argon2Hash(pwd, salt []byte) []byte {
// https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
// m=19456 (19 MiB), t=2, p=1
return argon2.IDKey(pwd, salt, 2, a2KeyMem, 1, a2KeyLen)
}
// PasswordHash using Argon2id
//
// https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id
func PasswordHash(pwd string) (hash, salt string, err error) {
var sl []byte
sl, err = RandomBytes(a2KeyLen)
if err != nil {
return
}
// Generate hash
h := argon2Hash([]byte(pwd), sl)
hash = base64.StdEncoding.EncodeToString(h)
salt = base64.StdEncoding.EncodeToString(sl)
return
}
func ComparePasswordHash(pwd, hash, salt string) (bool, error) {
var h, s []byte
var err error
if h, err = base64.StdEncoding.DecodeString(hash); err != nil {
return false, err
}
if s, err = base64.StdEncoding.DecodeString(salt); err != nil {
return false, err
}
// Generate hash for comparison.
ph := argon2Hash([]byte(pwd), s)
// Compare the generated hash with the stored hash.
// If they don't match return error.
return bytes.Equal(h, ph), nil
}

27
crypto/argon2_test.go Normal file
View File

@@ -0,0 +1,27 @@
// Copyright 2024 Patial Tech (Ankit Patial).
// All rights reserved.
package crypto
import "testing"
func TestPasswordHash(t *testing.T) {
pwd := "MY Bingo pwd"
hash, salt, err := PasswordHash(pwd)
if err != nil {
t.Error(err)
return
}
if hash == "" || salt == "" {
t.Error("either hash or password is empty")
return
}
if ok, err := ComparePasswordHash(pwd, string(hash), string(salt)); err != nil {
t.Error(err)
} else if !ok {
t.Error("supposed to match")
}
}

102
crypto/ed25519.go Normal file
View File

@@ -0,0 +1,102 @@
// Copyright 2024 Patial Tech (Ankit Patial).
// All rights reserved.
package crypto
import (
"crypto/ed25519"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"errors"
)
// NewPersistedED25519 public & private keys
func NewPersistedED25519() ([]byte, []byte, error) {
pub, prv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
// marshal public key
pubB, err := marshalEdPublicKey(pub)
if err != nil {
return nil, nil, err
}
// marshal private key
prvB, err := marshalEdPrivateKey(prv)
if err != nil {
return nil, nil, err
}
// done
return pubB, prvB, nil
}
func marshalEdPrivateKey(key ed25519.PrivateKey) ([]byte, error) {
b, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return nil, err
}
enc := pem.EncodeToMemory(
&pem.Block{
Type: "PRIVATE KEY",
Bytes: b,
},
)
return enc, nil
}
func ParseEdPrivateKey(d []byte) (ed25519.PrivateKey, error) {
block, _ := pem.Decode(d)
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
b, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
switch pub := b.(type) {
case ed25519.PrivateKey:
return pub, nil
default:
return nil, errors.New("key type is not RSA")
}
}
func marshalEdPublicKey(key ed25519.PublicKey) ([]byte, error) {
b, err := x509.MarshalPKIXPublicKey(key)
if err != nil {
return nil, err
}
enc := pem.EncodeToMemory(
&pem.Block{
Type: "PUBLIC KEY",
Bytes: b,
},
)
return enc, nil
}
func ParseEdPublicKey(d []byte) (ed25519.PublicKey, error) {
block, _ := pem.Decode(d)
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
b, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
switch pub := b.(type) {
case ed25519.PublicKey:
return pub, nil
default:
return nil, errors.New("key type is not RSA")
}
}

19
crypto/ed25519_test.go Normal file
View File

@@ -0,0 +1,19 @@
// Copyright 2024 Patial Tech (Ankit Patial).
// All rights reserved.
package crypto
import (
"testing"
)
func TestGenerate(t *testing.T) {
public, private, err := NewPersistedED25519()
if err != nil {
t.Error(err)
} else {
println(string(public))
println(string(private))
}
}

96
crypto/encryption.go Normal file
View File

@@ -0,0 +1,96 @@
package crypto
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
)
// NewEncryptionKey will generate new key as base64 encoded string
//
// should be of 16, 24 or 32 bytes in length
func NewEncryptionKey(size uint8) (string, error) {
b, err := RandomBytes(size)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(b), nil
}
func decodeEncryptionKey(str string) ([]byte, error) {
return base64.StdEncoding.DecodeString(str)
}
// Encrypt text using the encyption key
func Encrypt(key, text string) (string, error) {
b, err := decodeEncryptionKey(key)
if err != nil {
return "", fmt.Errorf("decrypted base64 string: %w", err)
}
gcm, err := getGCM(b)
if err != nil {
return "", err
}
// create a nonce. Nonce should be from GCM
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
// encrypt the data using gcm.Seal
ciphertext := gcm.Seal(nonce, nonce, []byte(text), nil) // prefix nonce to the ciphertext
return fmt.Sprintf("%x", ciphertext), nil
}
// Decrypt text using the encryption key
func Decrypt(key, text string) (string, error) {
b, err := decodeEncryptionKey(key)
if err != nil {
return "", fmt.Errorf("decrypted base64 string: %w", err)
}
// create a new Cipher Block from the key
gcm, err := getGCM(b)
if err != nil {
return "", err
}
nonceSize := gcm.NonceSize()
enc, err := hex.DecodeString(text)
if err != nil {
return "", err
}
nonce, ciphertext := enc[:nonceSize], enc[nonceSize:]
// decrypt
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}
return string(plaintext), nil
}
func getGCM(key []byte) (gcm cipher.AEAD, err error) {
// cipher block from the key
b, err := aes.NewCipher(key)
if err != nil {
return gcm, err
}
// GCM - https://en.wikipedia.org/wiki/Galois/Counter_Mode
// https://golang.org/pkg/crypto/cipher/#NewGCM
gcm, err = cipher.NewGCM(b)
if err != nil {
return gcm, err
}
return gcm, nil
}

35
crypto/encryption_test.go Normal file
View File

@@ -0,0 +1,35 @@
package crypto
import (
"fmt"
"testing"
)
func TestNewEncryptionKey(t *testing.T) {
if key, err := NewEncryptionKey(32); err != nil {
t.Error(err)
} else {
fmt.Println(key)
}
}
func TestEncryptDecrypt(t *testing.T) {
text := "Hellp World!"
key := "Laumw6mvWwrc8Q1SZQYCdiVr/dDBpjDdpaI6v4WvWSw="
// crypt
crypted, err := Encrypt(key, text)
if err != nil {
t.Errorf("Encrypt failed: %v", err)
}
decrypted, err := Decrypt(key, crypted)
if err != nil {
t.Errorf("Decrypt failed: %v", err)
}
if string(decrypted) != text {
t.Errorf("Decrypted text does not match original")
}
}

27
crypto/hash.go Normal file
View File

@@ -0,0 +1,27 @@
package crypto
import (
"crypto/md5"
"crypto/sha256"
"encoding/hex"
)
// MD5 is generally faster to compute than SHA-256
func MD5(b []byte) string {
hash := md5.Sum(b)
return hex.EncodeToString(hash[:])
}
/*
func MD5Int(b []byte) uint64 {
n := new(big.Int)
n.SetString(MD5(b), 16)
return n.Uint64()
}
*/
// SHA256 Sum256 is generally preferred over md5.hash due to its superior security and resistance to collision attacks
func SHA256(b []byte) string {
hash := sha256.Sum256(b)
return hex.EncodeToString(hash[:])
}

25
crypto/hash_test.go Normal file
View File

@@ -0,0 +1,25 @@
package crypto
import "testing"
func TestMD5(t *testing.T) {
b := []byte("hello there")
h := MD5(b)
if h == "" {
t.Error("failed to hasg")
return
}
println(h)
}
func TestSha256(t *testing.T) {
b := []byte("hello there")
h := SHA256(b)
if h == "" {
t.Error("failed to hasg")
return
}
println(h)
}

24
crypto/random.go Normal file
View File

@@ -0,0 +1,24 @@
// Copyright 2024 Patial Tech (Ankit Patial).
// All rights reserved.
package crypto
import (
"crypto/rand"
"encoding/hex"
)
// RandomBytes generates totally random bytes, each byte is generated by [0,255] range
func RandomBytes(length uint8) ([]byte, error) {
buf := make([]byte, length)
_, err := rand.Read(buf)
return buf, err
}
func RandomBytesHex(length uint8) (string, error) {
if b, err := RandomBytes(length); err != nil {
return "", err
} else {
return hex.EncodeToString(b), nil
}
}

26
crypto/random_test.go Normal file
View File

@@ -0,0 +1,26 @@
// Copyright 2024 Patial Tech (Ankit Patial).
// All rights reserved.
package crypto
import (
"encoding/base64"
"fmt"
"testing"
)
func TestRandomBytes(t *testing.T) {
if key, err := RandomBytes(32); err != nil {
t.Error(err)
} else {
fmt.Println(base64.StdEncoding.EncodeToString(key))
}
}
func TestRandomBytesHex(t *testing.T) {
if key, err := RandomBytesHex(32); err != nil {
t.Error(err)
} else {
fmt.Println(key)
}
}