basic common app packages
This commit is contained in:
59
crypto/argon2.go
Normal file
59
crypto/argon2.go
Normal 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
27
crypto/argon2_test.go
Normal 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
102
crypto/ed25519.go
Normal 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
19
crypto/ed25519_test.go
Normal 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
96
crypto/encryption.go
Normal 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
35
crypto/encryption_test.go
Normal 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
27
crypto/hash.go
Normal 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
25
crypto/hash_test.go
Normal 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
24
crypto/random.go
Normal 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
26
crypto/random_test.go
Normal 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)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user