aes/chacha20

main
Robert 5 months ago
parent 033acae279
commit b586d1e8ee
  1. 2
      .gitignore
  2. 5
      README
  3. 17
      chaKey.go
  4. 147
      client.go
  5. 3
      default.yaml
  6. 5
      go.mod
  7. 3
      go.sum
  8. 157
      keygen.go
  9. 315
      server.go

2
.gitignore vendored

@ -1,2 +1,4 @@
*.db
vaultClient
config.yaml
*.pem

@ -5,8 +5,9 @@ go mod download golang.org/x/term
go mod tidy
go run keygen.go
nano server.go
REPLACE old key with new key!
go run chaKey.go
nano default.yaml
cp default.yaml config.yaml
sudo apt install gcc
CGO_ENABLED=1 go build -o cliVault server.go

@ -0,0 +1,17 @@
package main
import (
"crypto/rand"
"fmt"
"golang.org/x/crypto/chacha20poly1305"
)
func main() {
// Generate or use a pre-shared key (in real code, this should be properly managed)
key := make([]byte, chacha20poly1305.KeySize)
if _, err := rand.Read(key); err != nil {
// handle error
return
}
fmt.Printf("%x", key)
}

@ -1,7 +1,10 @@
package main
import (
"crypto/sha256"
"crypto/rand"
"golang.org/x/crypto/chacha20poly1305"
"encoding/base64"
"encoding/gob"
"encoding/hex"
"fmt"
@ -9,9 +12,23 @@ import (
"time"
"golang.org/x/term"
"syscall"
"flag"
"os"
"log"
"bytes"
"io/ioutil"
"gopkg.in/yaml.v2"
)
var ChaKey = []byte("")
type Config struct {
Auth struct {
ChaKey string `yaml:"ChaKey"`
} `yaml:"auth"`
}
type Request struct {
KeyPwd string
Username string
Password string
Operation string
@ -23,6 +40,7 @@ type Request struct {
type Response struct {
Message string
Enc string
}
func generateNonce() (string, error) {
@ -34,7 +52,106 @@ func generateNonce() (string, error) {
return hex.EncodeToString(b), nil
}
func getKey()([]byte) {
// Hash it to 32 bytes using SHA-256
hashedKey := sha256.Sum256(ChaKey)
theKey := hashedKey[:] // Convert [32]byte to []byte
return theKey
}
func chEnc(p string)(string) {
// Create cipher
aead, err := chacha20poly1305.NewX(getKey())
if err != nil {
log.Fatalf("ChaKey enc error")
return ""
}
// Create nonce
nonce := make([]byte, aead.NonceSize())
if _, err := rand.Read(nonce); err != nil {
log.Fatalf("Cha Nonce Error")
return ""
}
// Encode with gob first
var buf bytes.Buffer
tempEnc := gob.NewEncoder(&buf)
if err := tempEnc.Encode(p); err != nil {
log.Fatalf("gob encode first error")
return ""
}
// Encrypt the encoded data
encrypted := aead.Seal(nil, nonce, buf.Bytes(), nil)
// Send nonce + encrypted data
fullMessage := append(nonce, encrypted...)
encoded := base64.StdEncoding.EncodeToString(fullMessage)
return encoded
}
func chDec(eText string)(string) {
if eText == "" {
return ""
}
// Decoding from base64
decoded, err := base64.StdEncoding.DecodeString(eText)
if err != nil {
log.Fatal("Base64 decoding error:", err)
}
// Create cipher instance (XChaCha20-Poly1305 for longer nonces)
aead, err := chacha20poly1305.NewX(getKey())
if err != nil {
log.Fatalf("Client: ChaKey failed")
return ""
}
encryptedMsg := decoded
// Decrypt: Split nonce and ciphertext
decryptedNonce := encryptedMsg[:aead.NonceSize()]
decryptedCiphertext := encryptedMsg[aead.NonceSize():]
decrypted, err := aead.Open(nil, decryptedNonce, decryptedCiphertext, nil)
if err != nil {
log.Fatalf("Client decryption failed")
return ""
}
return string(decrypted)
}
func main() {
configFilePtr := flag.String("config", "config.yaml", "Path to the YAML configuration file")
flag.Parse()
if *configFilePtr == "" {
fmt.Println("Please specify a configuration file using -config")
flag.Usage()
os.Exit(1)
}
// Read the config file
yamlFile, err := ioutil.ReadFile(*configFilePtr)
if err != nil {
log.Fatalf("Error reading YAML file: %v\n", err)
}
// Parse the YAML
var config Config
err = yaml.Unmarshal(yamlFile, &config)
if err != nil {
log.Fatalf("Error parsing YAML file: %v\n", err)
}
// Print the ChaKey
if config.Auth.ChaKey == "" {
fmt.Println("Warning: ChaKey not found in configuration file")
}
ChaKey = []byte(config.Auth.ChaKey)
var host string
fmt.Print("Enter host or IP: ")
fmt.Scanln(&host)
@ -44,6 +161,14 @@ func main() {
panic(err)
}
defer conn.Close()
fmt.Print("Key Passphrase: ")
byteKeyPwd, err := term.ReadPassword(int(syscall.Stdin))
fmt.Println()
if err != nil {
panic(err)
}
keypassphrase := string(byteKeyPwd)
var username, op, site, vaultData string
fmt.Print("Username: ")
@ -74,12 +199,20 @@ func main() {
panic(err)
}
var myPwd string
if vaultData == "" {
myPwd = ""
} else {
myPwd = chEnc(vaultData)
}
req := Request{
Username: username,
Password: password,
KeyPwd: chEnc(keypassphrase),
Username: chEnc(username),
Password: chEnc(password),
Operation: op,
Site: site,
VaultData: vaultData,
Site: chEnc(site),
VaultData: myPwd,
Timestamp: time.Now().Unix(),
Nonce: nonce,
}
@ -98,5 +231,9 @@ func main() {
return
}
fmt.Println("Server response:", res.Message)
if res.Message != "" {
fmt.Println("Server response:", res.Message)
} else {
fmt.Println("Server response:", chDec(res.Enc))
}
}

@ -0,0 +1,3 @@
auth:
ChaKey: "b107568bf716da40f5f17fea0e6608816020118d2c10b488ef9777b3d626126f"
PEM: "encrypted_aes_key.pem"

@ -8,4 +8,7 @@ require (
golang.org/x/term v0.32.0
)
require golang.org/x/sys v0.33.0 // indirect
require (
golang.org/x/sys v0.33.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

@ -6,3 +6,6 @@ golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

@ -1,25 +1,152 @@
package main
import (
"crypto/rand"
"encoding/hex"
"fmt"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/pem"
"errors"
"fmt"
"os"
"flag"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/crypto/scrypt"
)
func generateKey() ([]byte, error) {
key := make([]byte, 16)
_, err := rand.Read(key)
if err != nil {
return nil, err
func main() {
keyPtr := flag.String("keyfile", "encrypted_aes_key.pem", "Enter keyfile")
flag.Parse()
if *keyPtr == "" {
fmt.Print("Please enter a keyfile")
return
}
return key, nil
// Generate a new AES-256 key
key, err := generateAESKey(32) // 32 bytes = 256 bits
if err != nil {
fmt.Printf("Error generating AES key: %v\n", err)
return
}
// Prompt for passphrase
print("Enter passphrase: ")
pass, _ := terminal.ReadPassword(0) // Hides input
password := string(pass)
// Encrypt and PEM encode the key
pemBlock, err := encryptKeyToPEM(key, password)
if err != nil {
fmt.Printf("Error encrypting key: %v\n", err)
return
}
// Save to file
err = os.WriteFile(*keyPtr, pem.EncodeToMemory(pemBlock), 0600)
if err != nil {
fmt.Printf("Error writing PEM file: %v\n", err)
return
}
fmt.Printf("Successfully generated and password-protected AES key in %s", *keyPtr)
}
func main() {
key, err := generateKey()
if err != nil {
panic(err)
}
// generateAESKey creates a new random AES key of specified size
func generateAESKey(size int) ([]byte, error) {
key := make([]byte, size)
_, err := rand.Read(key)
if err != nil {
return nil, err
}
return key, nil
}
// encryptKeyToPEM encrypts the key with a password and returns a PEM block
func encryptKeyToPEM(key []byte, password string) (*pem.Block, error) {
// Convert password to suitable encryption key
salt := make([]byte, 16)
if _, err := rand.Read(salt); err != nil {
return nil, err
}
// Use scrypt for key derivation (better than PBKDF2)
// N: CPU/memory cost, r: block size, p: parallelization
encryptionKey, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32)
if err != nil {
return nil, err
}
// Generate a random IV
iv := make([]byte, aes.BlockSize)
if _, err := rand.Read(iv); err != nil {
return nil, err
}
// Encrypt the key
block, err := aes.NewCipher(encryptionKey)
if err != nil {
return nil, err
}
encrypted := make([]byte, len(key))
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(encrypted, key)
// Create PEM block
pemBlock := &pem.Block{
Type: "ENCRYPTED AES KEY",
Headers: map[string]string{
"Key-Derivation": "scrypt",
"SALT": fmt.Sprintf("%x", salt),
"IV": fmt.Sprintf("%x", iv),
"Key-Size": fmt.Sprintf("%d", len(key)*8),
},
Bytes: encrypted,
}
return pemBlock, nil
}
// hexStringToBytes converts a hex string to bytes
func hexStringToBytes(hexStr string) ([]byte, error) {
if len(hexStr)%2 != 0 {
return nil, errors.New("hex string length must be even")
}
bytes := make([]byte, len(hexStr)/2)
for i := 0; i < len(hexStr); i += 2 {
b, err := hexByte(hexStr[i], hexStr[i+1])
if err != nil {
return nil, err
}
bytes[i/2] = b
}
return bytes, nil
}
// hexByte converts two hex characters to a byte
func hexByte(a, b byte) (byte, error) {
high, err := hexDigitToByte(a)
if err != nil {
return 0, err
}
low, err := hexDigitToByte(b)
if err != nil {
return 0, err
}
return (high << 4) | low, nil
}
fmt.Println("Generated 32-byte key (hex):", hex.EncodeToString(key))
// hexDigitToByte converts a single hex character to its byte value
func hexDigitToByte(c byte) (byte, error) {
switch {
case '0' <= c && c <= '9':
return c - '0', nil
case 'a' <= c && c <= 'f':
return c - 'a' + 10, nil
case 'A' <= c && c <= 'F':
return c - 'A' + 10, nil
default:
return 0, fmt.Errorf("invalid hex digit %c", c)
}
}

@ -2,20 +2,42 @@ package main
import (
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/scrypt"
"crypto/sha256"
"crypto/rand"
"crypto/aes"
"crypto/cipher"
"database/sql"
"encoding/base64"
"encoding/pem"
"encoding/gob"
"fmt"
"net"
"sync"
"time"
"bytes"
"errors"
"flag"
"os"
"log"
"io/ioutil"
"gopkg.in/yaml.v2"
_ "github.com/mattn/go-sqlite3"
)
const AllowRegistration = true // Disable after users are added!
var key = []byte("2f5680a7fb57ce83e8e83dbb1114ee31") // 32 bytes
var ChaKey = []byte("")
type Config struct {
Auth struct {
ChaKey string `yaml:"ChaKey"`
PEM string `yaml:"PEM"`
} `yaml:"auth"`
}
var config Config
var nonceStore = struct {
sync.Mutex
@ -24,7 +46,9 @@ var nonceStore = struct {
const nonceExpiry = 30 * time.Second
var key []byte
type Request struct {
KeyPwd string
Username string
Password string
Operation string
@ -36,6 +60,7 @@ type Request struct {
type Response struct {
Message string
Enc string
}
var ipAttempts = struct {
@ -81,6 +106,106 @@ func isValidNonce(nonce string, timestamp int64) bool {
return true
}
// decryptPEMToKey decrypts a PEM block with the given password
func decryptPEMToKey(filename string, password string) ([]byte, error) {
// Read the entire file
pemData, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("failed to read PEM file: %v", err)
}
// Decode the PEM block
pemBlock, _ := pem.Decode(pemData)
if pemBlock == nil {
return nil, errors.New("failed to decode PEM block")
}
// Check if the PEM block is encrypted
if pemBlock.Headers["Key-Derivation"] == "" {
return nil, errors.New("PEM block is not password-protected")
}
// Get salt and IV from headers
salt, err := hexStringToBytes(pemBlock.Headers["SALT"])
if err != nil {
return nil, fmt.Errorf("invalid salt: %v", err)
}
iv, err := hexStringToBytes(pemBlock.Headers["IV"])
if err != nil {
return nil, fmt.Errorf("invalid IV: %v", err)
}
// Derive encryption key
var encryptionKey []byte
switch pemBlock.Headers["Key-Derivation"] {
case "scrypt":
encryptionKey, err = scrypt.Key([]byte(password), salt, 32768, 8, 1, 32)
//case "pbkdf2":
//encryptionKey = pbkdf2.Key([]byte(password), salt, 10000, 32, sha256.New)
default:
return nil, errors.New("unsupported key derivation function")
}
if err != nil {
return nil, err
}
// Decrypt the key
block, err := aes.NewCipher(encryptionKey)
if err != nil {
return nil, err
}
decrypted := make([]byte, len(pemBlock.Bytes))
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(decrypted, pemBlock.Bytes)
return decrypted, nil
}
// hexStringToBytes converts a hex string to bytes
func hexStringToBytes(hexStr string) ([]byte, error) {
if len(hexStr)%2 != 0 {
return nil, errors.New("hex string length must be even")
}
bytes := make([]byte, len(hexStr)/2)
for i := 0; i < len(hexStr); i += 2 {
b, err := hexByte(hexStr[i], hexStr[i+1])
if err != nil {
return nil, err
}
bytes[i/2] = b
}
return bytes, nil
}
// hexByte converts two hex characters to a byte
func hexByte(a, b byte) (byte, error) {
high, err := hexDigitToByte(a)
if err != nil {
return 0, err
}
low, err := hexDigitToByte(b)
if err != nil {
return 0, err
}
return (high << 4) | low, nil
}
// hexDigitToByte converts a single hex character to its byte value
func hexDigitToByte(c byte) (byte, error) {
switch {
case '0' <= c && c <= '9':
return c - '0', nil
case 'a' <= c && c <= 'f':
return c - 'a' + 10, nil
case 'A' <= c && c <= 'F':
return c - 'A' + 10, nil
default:
return 0, fmt.Errorf("invalid hex digit %c", c)
}
}
func hashPassword(pw string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(pw), 12)
return string(bytes), err
@ -121,94 +246,232 @@ func decrypt(data []byte) (string, error) {
return string(plaintext), nil
}
func getKey()([]byte) {
// Hash it to 32 bytes using SHA-256
hashedKey := sha256.Sum256(ChaKey)
theKey := hashedKey[:] // Convert [32]byte to []byte
return theKey
}
func chEnc(pwd string)(string, error) {
// Create cipher
aead, err := chacha20poly1305.NewX(getKey())
if err != nil {
return "", err
}
// Create nonce
nonce := make([]byte, aead.NonceSize())
if _, err := rand.Read(nonce); err != nil {
return "", err
}
// Encode with gob first
var buf bytes.Buffer
tempEnc := gob.NewEncoder(&buf)
if err := tempEnc.Encode(pwd); err != nil {
return "", err
}
// Encrypt the encoded data
encrypted := aead.Seal(nil, nonce, buf.Bytes(), nil)
// Send nonce + encrypted data
fullMessage := append(nonce, encrypted...)
encoded := base64.StdEncoding.EncodeToString(fullMessage)
return encoded, nil
}
func chDec(eText string)(string) {
if eText == "" {
log.Fatalf("Error: Blank")
}
// Decoding from base64
decoded, err := base64.StdEncoding.DecodeString(eText)
if err != nil {
log.Fatalf("Error: Base64 decode")
}
// Create cipher instance (XChaCha20-Poly1305 for longer nonces)
aead, err := chacha20poly1305.NewX(getKey())
if err != nil {
log.Fatalf("Error: Cha20 key")
}
encryptedMsg := decoded
// Decrypt: Split nonce and ciphertext
decryptedNonce := encryptedMsg[:aead.NonceSize()]
decryptedCiphertext := encryptedMsg[aead.NonceSize():]
decrypted, err := aead.Open(nil, decryptedNonce, decryptedCiphertext, nil)
if err != nil {
log.Fatalf("Error: aead open")
}
return string(decrypted)
}
func handleConnection(conn net.Conn, db *sql.DB) {
defer conn.Close()
dec := gob.NewDecoder(conn)
enc := gob.NewEncoder(conn)
remoteAddr := conn.RemoteAddr().String()
var req Request
if err := dec.Decode(&req); err != nil {
return
}
username := chDec(req.Username)
keypwd := chDec(req.KeyPwd)
password := chDec(req.Password)
if username == "" {
enc.Encode(Response{Message: "Required username, blank found!", Enc: ""})
return
}
if keypwd == "" {
enc.Encode(Response{Message: "Required key passphrase, blank found!", Enc: ""})
return
}
if password == "" {
enc.Encode(Response{Message: "Required user password, blank found!", Enc: ""})
return
}
k, err := decryptPEMToKey(config.Auth.PEM, keypwd)
if err != nil {
enc.Encode(Response{Message: "Unable to decode data!", Enc: ""})
return
}
key = k
if isRateLimited(remoteAddr) {
enc.Encode(Response{"Too many requests. Try later."})
enc.Encode(Response{Message: "Too many requests. Try later.", Enc: ""})
return
}
if !isValidNonce(req.Nonce, req.Timestamp) {
enc.Encode(Response{"Invalid or replayed request"})
enc.Encode(Response{Message: "Invalid or replayed request", Enc: ""})
return
}
if req.Operation == "register" {
if AllowRegistration == false {
enc.Encode(Response{"Registration Disabled!"})
enc.Encode(Response{Message: "Registration Disabled!", Enc: ""})
return
}
hashed, err := hashPassword(req.Password)
hashed, err := hashPassword(password)
if err != nil {
enc.Encode(Response{"Failed to hash password"})
enc.Encode(Response{Message: "Failed to hash password", Enc: ""})
return
}
_, err = db.Exec("INSERT INTO users (username, password) VALUES (?, ?)", req.Username, hashed)
_, err = db.Exec("INSERT INTO users (username, password) VALUES (?, ?)", username, hashed)
if err != nil {
enc.Encode(Response{"Registration failed (user exists?)"})
enc.Encode(Response{Message: "Registration failed (user exists?)", Enc: ""})
} else {
enc.Encode(Response{"Registration successful"})
enc.Encode(Response{Message: "Registration successful", Enc: ""})
}
return
}
site := chDec(req.Site)
if site == "" {
enc.Encode(Response{Message: "Required site name, blank found!", Enc: ""})
return
}
// Authenticate all other operations
var storedHash string
err := db.QueryRow("SELECT password FROM users WHERE username = ?", req.Username).Scan(&storedHash)
if err != nil || checkPassword(storedHash, req.Password) != nil {
enc.Encode(Response{"Authentication failed"})
err = db.QueryRow("SELECT password FROM users WHERE username = ?", username).Scan(&storedHash)
if err != nil || checkPassword(storedHash, password) != nil {
enc.Encode(Response{Message: "Authentication failed", Enc: ""})
return
}
switch req.Operation {
case "store":
encrypted, err := encrypt(req.VaultData)
vaultdata := chDec(req.VaultData)
encrypted, err := encrypt(vaultdata)
if err != nil {
enc.Encode(Response{"Encryption failed"})
enc.Encode(Response{Message: "Encryption failed", Enc: ""})
return
}
_, err = db.Exec("INSERT OR REPLACE INTO accounts (user, site, password) VALUES (?, ?, ?)",
req.Username, req.Site, encrypted)
username, site, encrypted)
if err != nil {
enc.Encode(Response{"Database error"})
enc.Encode(Response{Message: "Database error", Enc: ""})
return
}
enc.Encode(Response{"Password stored successfully"})
enc.Encode(Response{Message: "Password stored successfully", Enc: ""})
case "get":
var encrypted []byte
err := db.QueryRow("SELECT password FROM accounts WHERE user = ? AND site = ?", req.Username, req.Site).Scan(&encrypted)
err := db.QueryRow("SELECT password FROM accounts WHERE user = ? AND site = ?", username, site).Scan(&encrypted)
if err == sql.ErrNoRows {
enc.Encode(Response{"Site not found"})
enc.Encode(Response{Message: "Site not found", Enc: ""})
return
} else if err != nil {
enc.Encode(Response{"Database error"})
enc.Encode(Response{Message: "Database error", Enc: ""})
return
}
pwd, err := decrypt(encrypted)
if err != nil {
enc.Encode(Response{"Decryption failed"})
enc.Encode(Response{Message: "Decryption failed", Enc: ""})
return
}
enc.Encode(Response{pwd})
ePwd, err := chEnc(pwd)
if err != nil {
enc.Encode(Response{Message: "Encryption failed", Enc: ""})
return
}
enc.Encode(Response{Message: "", Enc: ePwd})
default:
enc.Encode(Response{"Unknown operation"})
enc.Encode(Response{Message: "Unknown operation", Enc: ""})
}
}
func main() {
db, err := sql.Open("sqlite3", "vault.db")
vaultPtr := flag.String("dbfile", "vault.db", "Enter dbfile")
configFilePtr := flag.String("config", "config.yaml", "Path to the YAML configuration file")
flag.Parse()
if *vaultPtr == "" {
fmt.Println("Please specify a db file using -dbfile")
flag.Usage()
os.Exit(1)
}
if *configFilePtr == "" {
fmt.Println("Please specify a configuration file using -config")
flag.Usage()
os.Exit(1)
}
// Read the config file
yamlFile, err := ioutil.ReadFile(*configFilePtr)
if err != nil {
log.Fatalf("Error reading YAML file: %v\n", err)
}
// Parse the YAML
err = yaml.Unmarshal(yamlFile, &config)
if err != nil {
log.Fatalf("Error parsing YAML file: %v\n", err)
}
// Print the ChaKey
if config.Auth.ChaKey == "" {
fmt.Println("Warning: ChaKey not found in configuration file")
}
ChaKey = []byte(config.Auth.ChaKey)
if config.Auth.PEM == "" {
fmt.Println("Warning: PEM file not found in configuration file")
}
db, err := sql.Open("sqlite3", *vaultPtr)
if err != nil {
panic(err)
}

Loading…
Cancel
Save