3 Commits

Author SHA1 Message Date
6a9269fa9c added hmac validation (#1)
All checks were successful
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 1m5s
Reviewed-on: #1
Co-authored-by: Ron Rise <ron@siteworxpro.com>
Co-committed-by: Ron Rise <ron@siteworxpro.com>
2026-02-06 22:45:41 +00:00
9f58437d0e Update dependencies in go.mod and go.sum to latest versions
All checks were successful
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 2m45s
2025-08-30 23:06:24 -04:00
eb00dc5165 update golang
Some checks failed
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Failing after 9s
2025-08-30 23:05:08 -04:00
4 changed files with 70 additions and 4 deletions

View File

@@ -1,14 +1,14 @@
on: on:
push: push:
branches: branches:
- "*" - "**"
name: 🧪 ✨ Unit Tests Workflow name: 🧪 ✨ Unit Tests Workflow
jobs: jobs:
test-go: test-go:
env: env:
GO_VERSION: '1.24.3' GO_VERSION: '1.25.0'
name: 🔍 🐹 Go Tests name: 🔍 🐹 Go Tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

View File

@@ -4,15 +4,20 @@ import (
"bytes" "bytes"
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/hmac"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/sha256"
"crypto/subtle" "crypto/subtle"
"fmt" "fmt"
"os" "os"
) )
const hmacKey = "::HMAC::"
type EncryptedFile struct { type EncryptedFile struct {
ciphertext []byte ciphertext []byte
hmac []byte
plainText []byte plainText []byte
nonce []byte nonce []byte
privatePem []byte privatePem []byte
@@ -25,7 +30,13 @@ type EncryptedFile struct {
func (f *EncryptedFile) packFile() []byte { func (f *EncryptedFile) packFile() []byte {
file := append(f.nonce, f.ciphertext...) file := append(f.nonce, f.ciphertext...)
return append(file, f.symmetricKeyEnc...) file = append(file, f.symmetricKeyEnc...)
if len(f.hmac) > 0 {
file = append(file, []byte(hmacKey)...)
file = append(file, f.hmac...)
}
return file
} }
func (f *EncryptedFile) EncryptFile() error { func (f *EncryptedFile) EncryptFile() error {
@@ -48,9 +59,29 @@ func (f *EncryptedFile) EncryptFile() error {
cbc.CryptBlocks(ciphertext, plaintextP) cbc.CryptBlocks(ciphertext, plaintextP)
f.ciphertext = ciphertext f.ciphertext = ciphertext
mac, err := f.generateHmac()
if err != nil {
return err
}
f.hmac = mac
return nil return nil
} }
func (f *EncryptedFile) generateHmac() ([]byte, error) {
if len(f.symmetricKey) == 0 {
return nil, fmt.Errorf("symmetric key is not set")
}
mac := hmac.New(sha256.New, f.symmetricKey)
mac.Write(f.nonce)
mac.Write(f.ciphertext)
f.hmac = mac.Sum(nil)
return f.hmac, nil
}
func (f *EncryptedFile) OsReadPlainTextFile(path string) error { func (f *EncryptedFile) OsReadPlainTextFile(path string) error {
plaintext, err := os.ReadFile(path) plaintext, err := os.ReadFile(path)
if err != nil { if err != nil {
@@ -84,7 +115,21 @@ func (f *EncryptedFile) WriteDecryptedFileToDisk(filePath string) error {
func (f *EncryptedFile) unpackFileAndDecrypt(packedFile []byte) error { func (f *EncryptedFile) unpackFileAndDecrypt(packedFile []byte) error {
keyLen := f.privateKey.Size() keyLen := f.privateKey.Size()
minReqLen := aes.BlockSize + keyLen + len(hmacKey)
if len(packedFile) < minReqLen {
return fmt.Errorf("packed file is too short to be valid")
}
if bytes.Contains(packedFile, []byte(hmacKey)) {
parts := bytes.SplitN(packedFile, []byte(hmacKey), 2)
packedFile, f.hmac = parts[0], parts[1]
}
lenWithoutKey := len(packedFile) - keyLen lenWithoutKey := len(packedFile) - keyLen
if lenWithoutKey < aes.BlockSize {
return fmt.Errorf("packed file is too short to contain valid nonce and ciphertext")
}
packedFile, f.symmetricKeyEnc = packedFile[0:lenWithoutKey], packedFile[lenWithoutKey:] packedFile, f.symmetricKeyEnc = packedFile[0:lenWithoutKey], packedFile[lenWithoutKey:]
@@ -93,10 +138,22 @@ func (f *EncryptedFile) unpackFileAndDecrypt(packedFile []byte) error {
return err return err
} }
if len(f.hmac) > 0 {
mac, err := f.generateHmac()
if err != nil {
return err
}
if !hmac.Equal(mac, f.hmac) {
return fmt.Errorf("hmac verification failed")
}
}
a, err := aes.NewCipher(f.symmetricKey) a, err := aes.NewCipher(f.symmetricKey)
if err != nil { if err != nil {
return err return err
} }
f.nonce, f.ciphertext = packedFile[0:aes.BlockSize], packedFile[aes.BlockSize:] f.nonce, f.ciphertext = packedFile[0:aes.BlockSize], packedFile[aes.BlockSize:]
cbc := cipher.NewCBCDecrypter(a, f.nonce) cbc := cipher.NewCBCDecrypter(a, f.nonce)

View File

@@ -7,6 +7,7 @@ import (
"crypto/sha512" "crypto/sha512"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"fmt"
"os" "os"
) )
@@ -85,6 +86,10 @@ func (f *EncryptedFile) GenerateSymmetricKey() error {
func (f *EncryptedFile) ParsePublicPem() error { func (f *EncryptedFile) ParsePublicPem() error {
pemKeyBin, _ := pem.Decode(f.PublicPem) pemKeyBin, _ := pem.Decode(f.PublicPem)
if pemKeyBin == nil {
return fmt.Errorf("failed to parse PEM block containing the public key")
}
if bytes.Contains(f.PublicPem, []byte("-----BEGIN PUBLIC KEY-----")) { if bytes.Contains(f.PublicPem, []byte("-----BEGIN PUBLIC KEY-----")) {
key, err := x509.ParsePKIXPublicKey(pemKeyBin.Bytes) key, err := x509.ParsePKIXPublicKey(pemKeyBin.Bytes)
if err != nil { if err != nil {
@@ -109,6 +114,10 @@ func (f *EncryptedFile) ParsePublicPem() error {
func (f *EncryptedFile) ParsePrivatePem() error { func (f *EncryptedFile) ParsePrivatePem() error {
pemKeyBin, _ := pem.Decode(f.privatePem) pemKeyBin, _ := pem.Decode(f.privatePem)
if pemKeyBin == nil {
return fmt.Errorf("failed to parse PEM block containing the private key")
}
if bytes.Contains(f.privatePem, []byte("-----BEGIN PRIVATE KEY-----")) { if bytes.Contains(f.privatePem, []byte("-----BEGIN PRIVATE KEY-----")) {
key, err := x509.ParsePKCS8PrivateKey(pemKeyBin.Bytes) key, err := x509.ParsePKCS8PrivateKey(pemKeyBin.Bytes)
if err != nil { if err != nil {

2
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/siteworxpro/rsa-file-encryption module github.com/siteworxpro/rsa-file-encryption
go 1.24.6 go 1.25.0
require ( require (
github.com/charmbracelet/bubbles v0.21.0 github.com/charmbracelet/bubbles v0.21.0