1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
// Package identity manages Ed25519 cryptographic identities
package identity
import (
"crypto/ed25519"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"golang.org/x/crypto/scrypt"
"golang.org/x/crypto/nacl/secretbox"
)
// Identity represents a cryptographic identity
type Identity struct {
PublicKey ed25519.PublicKey
PrivateKey ed25519.PrivateKey
}
// encryptedIdentity is the encrypted on-disk format
type encryptedIdentity struct {
Nonce [24]byte `json:"nonce"`
Ciphertext []byte `json:"ciphertext"`
Salt []byte `json:"salt"`
}
// NewIdentity generates a new Ed25519 identity
func NewIdentity() (*Identity, error) {
pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, fmt.Errorf("failed to generate keypair: %w", err)
}
return &Identity{
PublicKey: pubKey,
PrivateKey: privKey,
}, nil
}
// LoadIdentity loads an encrypted identity from disk
func LoadIdentity(path string, passphrase []byte) (*Identity, error) {
// Read encrypted file
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read identity file: %w", err)
}
// Parse JSON
var encrypted encryptedIdentity
if err := json.Unmarshal(data, &encrypted); err != nil {
return nil, fmt.Errorf("failed to parse identity file: %w", err)
}
// Derive encryption key from passphrase
key, err := scrypt.Key(passphrase, encrypted.Salt, 32768, 8, 1, 32)
if err != nil {
return nil, fmt.Errorf("failed to derive key: %w", err)
}
// Decrypt private key
var keyArray [32]byte
copy(keyArray[:], key)
plaintext, ok := secretbox.Open(nil, encrypted.Ciphertext, &encrypted.Nonce, &keyArray)
if !ok {
return nil, fmt.Errorf("decryption failed: wrong passphrase or corrupted file")
}
// Parse private key
if len(plaintext) != ed25519.PrivateKeySize {
return nil, fmt.Errorf("invalid private key size")
}
privKey := ed25519.PrivateKey(plaintext)
pubKey := privKey.Public().(ed25519.PublicKey)
return &Identity{
PublicKey: pubKey,
PrivateKey: privKey,
}, nil
}
// Save encrypts and saves the identity to disk
func (id *Identity) Save(path string, passphrase []byte) error {
// Generate random salt
salt := make([]byte, 32)
if _, err := rand.Read(salt); err != nil {
return fmt.Errorf("failed to generate salt: %w", err)
}
// Derive encryption key
key, err := scrypt.Key(passphrase, salt, 32768, 8, 1, 32)
if err != nil {
return fmt.Errorf("failed to derive key: %w", err)
}
// Generate random nonce
var nonce [24]byte
if _, err := rand.Read(nonce[:]); err != nil {
return fmt.Errorf("failed to generate nonce: %w", err)
}
// Encrypt private key
var keyArray [32]byte
copy(keyArray[:], key)
ciphertext := secretbox.Seal(nil, id.PrivateKey, &nonce, &keyArray)
// Create encrypted structure
encrypted := encryptedIdentity{
Nonce: nonce,
Ciphertext: ciphertext,
Salt: salt,
}
// Marshal to JSON
data, err := json.MarshalIndent(encrypted, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal identity: %w", err)
}
// Write to file with secure permissions
if err := os.WriteFile(path, data, 0600); err != nil {
return fmt.Errorf("failed to write identity file: %w", err)
}
return nil
}
// GetPublicKeyHex returns the public key as hex string
func (id *Identity) GetPublicKeyHex() string {
return hex.EncodeToString(id.PublicKey)
}
// GetPrivateKeyHex returns the private key as hex string (use with caution!)
func (id *Identity) GetPrivateKeyHex() string {
return hex.EncodeToString(id.PrivateKey)
}
// Sign signs a message with the private key
func (id *Identity) Sign(message []byte) []byte {
return ed25519.Sign(id.PrivateKey, message)
}
// Verify verifies a signature
func (id *Identity) Verify(message, signature []byte) bool {
return ed25519.Verify(id.PublicKey, message, signature)
}
// SecureWipe zeros out sensitive key material from memory
func (id *Identity) SecureWipe() {
// Zero out private key
for i := range id.PrivateKey {
id.PrivateKey[i] = 0
}
// Public key is not sensitive, but wipe it anyway
for i := range id.PublicKey {
id.PublicKey[i] = 0
}
}
// String returns a safe string representation
func (id *Identity) String() string {
return fmt.Sprintf("Identity(pubkey=%s...)", id.GetPublicKeyHex()[:16])
}
|