1
+ package main
2
+
3
+ import (
4
+ "crypto/rand"
5
+ "crypto/sha3"
6
+ "encoding/hex"
7
+ "flag"
8
+ "fmt"
9
+ "golang.org/x/crypto/argon2"
10
+ "golang.org/x/crypto/chacha20"
11
+ "os"
12
+ )
13
+
14
+ type esub struct {
15
+ key string
16
+ subject string
17
+ }
18
+
19
+ func (e * esub ) deriveKey () []byte {
20
+ // Argon2id parameters (adjust time/memory/threads as needed)
21
+ salt := []byte ("fixed-salt-1234" ) // Use a unique, constant salt (or randomize & store it)
22
+ key := argon2 .IDKey (
23
+ []byte (e .key ),
24
+ salt ,
25
+ 3 , // iterations
26
+ 64 * 1024 , // 64MB memory
27
+ 4 , // threads
28
+ 32 , // output key length (32 bytes for ChaCha20)
29
+ )
30
+ return key
31
+ }
32
+
33
+ func (e * esub ) esubtest () bool {
34
+ if len (e .subject ) != 48 { // 48 hex chars = 24 bytes
35
+ return false
36
+ }
37
+
38
+ esubBytes , err := hex .DecodeString (e .subject )
39
+ if err != nil || len (esubBytes ) != 24 {
40
+ return false
41
+ }
42
+
43
+ nonce := esubBytes [:12 ]
44
+ receivedCiphertext := esubBytes [12 :]
45
+
46
+ key := e .deriveKey ()
47
+ cipher , err := chacha20 .NewUnauthenticatedCipher (key , nonce )
48
+ if err != nil {
49
+ return false
50
+ }
51
+
52
+ // Hash "text" with SHA-256 (better than RIPEMD-160)
53
+ textHash := sha3 .Sum256 ([]byte ("text" ))
54
+ expectedCiphertext := make ([]byte , 12 )
55
+ cipher .XORKeyStream (expectedCiphertext , textHash [:12 ])
56
+
57
+ return hex .EncodeToString (expectedCiphertext ) == hex .EncodeToString (receivedCiphertext )
58
+ }
59
+
60
+ func (e * esub ) esubgen () string {
61
+ nonce := make ([]byte , 12 )
62
+ if _ , err := rand .Read (nonce ); err != nil {
63
+ panic (err )
64
+ }
65
+
66
+ key := e .deriveKey ()
67
+ cipher , err := chacha20 .NewUnauthenticatedCipher (key , nonce )
68
+ if err != nil {
69
+ panic (err )
70
+ }
71
+
72
+ textHash := sha3 .Sum256 ([]byte ("text" ))
73
+ ciphertext := make ([]byte , 12 )
74
+ cipher .XORKeyStream (ciphertext , textHash [:12 ])
75
+
76
+ return hex .EncodeToString (append (nonce , ciphertext ... ))
77
+ }
78
+
79
+ func main () {
80
+ flag .Parse ()
81
+ cmdargs := flag .Args ()
82
+ switch len (cmdargs ) {
83
+ case 1 :
84
+ e := new (esub )
85
+ e .key = cmdargs [0 ]
86
+ fmt .Println (e .esubgen ())
87
+ case 2 :
88
+ e := new (esub )
89
+ e .key = cmdargs [0 ]
90
+ e .subject = cmdargs [1 ]
91
+ if ! e .esubtest () {
92
+ fmt .Println ("Fail: esub not generated with this key" )
93
+ os .Exit (1 )
94
+ }
95
+ fmt .Println ("Validated: esub is valid for this key" )
96
+ default :
97
+ fmt .Println ("Usage: esub <key> [subject]" )
98
+ os .Exit (2 )
99
+ }
100
+ }
0 commit comments