Adds a full implementation of OpenPGP features using ProtonMail's go-crypto fork. - Implements PGP key generation, encryption, and decryption. - Exposes PGP functionality through the crypt.Service. - Adds tests for the PGP implementation.
203 lines
4.7 KiB
Go
203 lines
4.7 KiB
Go
package crypt
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"crypto/sha1"
|
|
"crypto/sha256"
|
|
"crypto/sha512"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/Snider/Enchantrix/pkg/crypt/std/lthn"
|
|
"github.com/Snider/Enchantrix/pkg/crypt/std/pgp"
|
|
"github.com/Snider/Enchantrix/pkg/crypt/std/rsa"
|
|
)
|
|
|
|
// Service is the main struct for the crypt service.
|
|
type Service struct {
|
|
rsa *rsa.Service
|
|
pgp *pgp.Service
|
|
}
|
|
|
|
// NewService creates a new crypt Service and initialises its embedded services.
|
|
func NewService() *Service {
|
|
return &Service{
|
|
rsa: rsa.NewService(),
|
|
pgp: pgp.NewService(),
|
|
}
|
|
}
|
|
|
|
// HashType defines the supported hashing algorithms.
|
|
type HashType string
|
|
|
|
const (
|
|
LTHN HashType = "lthn"
|
|
SHA512 HashType = "sha512"
|
|
SHA256 HashType = "sha256"
|
|
SHA1 HashType = "sha1"
|
|
MD5 HashType = "md5"
|
|
)
|
|
|
|
// --- Hashing ---
|
|
|
|
// IsHashAlgo checks if a string is a valid hash algorithm.
|
|
func (s *Service) IsHashAlgo(algo string) bool {
|
|
switch HashType(algo) {
|
|
case LTHN, SHA512, SHA256, SHA1, MD5:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Hash computes a hash of the payload using the specified algorithm.
|
|
func (s *Service) Hash(lib HashType, payload string) string {
|
|
switch lib {
|
|
case LTHN:
|
|
return lthn.Hash(payload)
|
|
case SHA512:
|
|
hash := sha512.Sum512([]byte(payload))
|
|
return hex.EncodeToString(hash[:])
|
|
case SHA1:
|
|
hash := sha1.Sum([]byte(payload))
|
|
return hex.EncodeToString(hash[:])
|
|
case MD5:
|
|
hash := md5.Sum([]byte(payload))
|
|
return hex.EncodeToString(hash[:])
|
|
case SHA256:
|
|
fallthrough
|
|
default:
|
|
hash := sha256.Sum256([]byte(payload))
|
|
return hex.EncodeToString(hash[:])
|
|
}
|
|
}
|
|
|
|
// --- Checksums ---
|
|
|
|
// Luhn validates a number using the Luhn algorithm.
|
|
func (s *Service) Luhn(payload string) bool {
|
|
payload = strings.ReplaceAll(payload, " ", "")
|
|
if len(payload) <= 1 {
|
|
return false
|
|
}
|
|
|
|
sum := 0
|
|
isSecond := len(payload)%2 == 0
|
|
for _, r := range payload {
|
|
digit, err := strconv.Atoi(string(r))
|
|
if err != nil {
|
|
return false // Contains non-digit
|
|
}
|
|
|
|
if isSecond {
|
|
digit = digit * 2
|
|
if digit > 9 {
|
|
digit = digit - 9
|
|
}
|
|
}
|
|
|
|
sum += digit
|
|
isSecond = !isSecond
|
|
}
|
|
return sum%10 == 0
|
|
}
|
|
|
|
// Fletcher16 computes the Fletcher-16 checksum.
|
|
func (s *Service) Fletcher16(payload string) uint16 {
|
|
data := []byte(payload)
|
|
var sum1, sum2 uint16
|
|
for _, b := range data {
|
|
sum1 = (sum1 + uint16(b)) % 255
|
|
sum2 = (sum2 + sum1) % 255
|
|
}
|
|
return (sum2 << 8) | sum1
|
|
}
|
|
|
|
// Fletcher32 computes the Fletcher-32 checksum.
|
|
func (s *Service) Fletcher32(payload string) uint32 {
|
|
data := []byte(payload)
|
|
if len(data)%2 != 0 {
|
|
data = append(data, 0)
|
|
}
|
|
|
|
var sum1, sum2 uint32
|
|
for i := 0; i < len(data); i += 2 {
|
|
val := binary.LittleEndian.Uint16(data[i : i+2])
|
|
sum1 = (sum1 + uint32(val)) % 65535
|
|
sum2 = (sum2 + sum1) % 65535
|
|
}
|
|
return (sum2 << 16) | sum1
|
|
}
|
|
|
|
// Fletcher64 computes the Fletcher-64 checksum.
|
|
func (s *Service) Fletcher64(payload string) uint64 {
|
|
data := []byte(payload)
|
|
if len(data)%4 != 0 {
|
|
padding := 4 - (len(data) % 4)
|
|
data = append(data, make([]byte, padding)...)
|
|
}
|
|
|
|
var sum1, sum2 uint64
|
|
for i := 0; i < len(data); i += 4 {
|
|
val := binary.LittleEndian.Uint32(data[i : i+4])
|
|
sum1 = (sum1 + uint64(val)) % 4294967295
|
|
sum2 = (sum2 + sum1) % 4294967295
|
|
}
|
|
return (sum2 << 32) | sum1
|
|
}
|
|
|
|
// --- RSA ---
|
|
|
|
// ensureRSA initializes the RSA service if it is not already.
|
|
func (s *Service) ensureRSA() {
|
|
if s.rsa == nil {
|
|
s.rsa = rsa.NewService()
|
|
}
|
|
}
|
|
|
|
// GenerateRSAKeyPair creates a new RSA key pair.
|
|
func (s *Service) GenerateRSAKeyPair(bits int) (publicKey, privateKey []byte, err error) {
|
|
s.ensureRSA()
|
|
return s.rsa.GenerateKeyPair(bits)
|
|
}
|
|
|
|
// EncryptRSA encrypts data with a public key.
|
|
func (s *Service) EncryptRSA(publicKey, data, label []byte) ([]byte, error) {
|
|
s.ensureRSA()
|
|
return s.rsa.Encrypt(publicKey, data, label)
|
|
}
|
|
|
|
// DecryptRSA decrypts data with a private key.
|
|
func (s *Service) DecryptRSA(privateKey, ciphertext, label []byte) ([]byte, error) {
|
|
s.ensureRSA()
|
|
return s.rsa.Decrypt(privateKey, ciphertext, label)
|
|
}
|
|
|
|
// --- PGP ---
|
|
|
|
// ensurePGP initializes the PGP service if it is not already.
|
|
func (s *Service) ensurePGP() {
|
|
if s.pgp == nil {
|
|
s.pgp = pgp.NewService()
|
|
}
|
|
}
|
|
|
|
// GeneratePGPKeyPair creates a new PGP key pair.
|
|
func (s *Service) GeneratePGPKeyPair(name, email, comment string) (publicKey, privateKey []byte, err error) {
|
|
s.ensurePGP()
|
|
return s.pgp.GenerateKeyPair(name, email, comment)
|
|
}
|
|
|
|
// EncryptPGP encrypts data with a public key.
|
|
func (s *Service) EncryptPGP(publicKey, data []byte) ([]byte, error) {
|
|
s.ensurePGP()
|
|
return s.pgp.Encrypt(publicKey, data)
|
|
}
|
|
|
|
// DecryptPGP decrypts data with a private key.
|
|
func (s *Service) DecryptPGP(privateKey, ciphertext []byte) ([]byte, error) {
|
|
s.ensurePGP()
|
|
return s.pgp.Decrypt(privateKey, ciphertext)
|
|
}
|