gnarl_wire.mx raw
1 package hamcrypto
2
3 // Gnarl wire format for authenticated encrypted packets.
4 // 64-byte header (10 nonce + 27 identity + 27 auth tag) + ciphertext.
5
6 import (
7 "crypto/sha256"
8 "errors"
9 "golang.org/x/crypto/chacha20"
10 )
11
12 const (
13 GnarlNonceLen = 10
14 GnarlHeaderLen = GnarlNonceLen + GnarlMidBytes + GnarlMidBytes
15 )
16
17 type GnarlPacket struct {
18 Nonce [GnarlNonceLen]byte
19 Identity GnarlMid
20 AuthTag GnarlMid
21 Ciphertext []byte
22 }
23
24 func GnarlSeal(secret Hamadryad, identity GnarlMid, nonce [GnarlNonceLen]byte, plaintext []byte) (pkt *GnarlPacket) {
25 macKey, cipher := gnarlCipherAndMAC(secret, nonce)
26
27 ciphertext := []byte{:int32(len(plaintext))}
28 cipher.XORKeyStream(ciphertext, plaintext)
29
30 authTag := gnarlAuthTag(macKey, nonce, identity, ciphertext)
31
32 return &GnarlPacket{
33 Nonce: nonce,
34 Identity: identity,
35 AuthTag: authTag,
36 Ciphertext: ciphertext,
37 }
38 }
39
40 func GnarlOpen(secret Hamadryad, pkt *GnarlPacket) (plaintext []byte, err error) {
41 if pkt == nil {
42 return nil, errors.New("gnarl: nil packet")
43 }
44
45 macKey, cipher := gnarlCipherAndMAC(secret, pkt.Nonce)
46
47 expected := gnarlAuthTag(macKey, pkt.Nonce, pkt.Identity, pkt.Ciphertext)
48 if expected != pkt.AuthTag {
49 return nil, errors.New("gnarl: authentication failed")
50 }
51
52 plaintext = []byte{:int32(len(pkt.Ciphertext))}
53 cipher.XORKeyStream(plaintext, pkt.Ciphertext)
54 return plaintext, nil
55 }
56
57 func MarshalGnarlPacket(pkt *GnarlPacket) (buf []byte) {
58 buf = []byte{:int32(GnarlHeaderLen + int32(len(pkt.Ciphertext)))}
59 copy(buf[0:GnarlNonceLen], pkt.Nonce[:])
60 copy(buf[GnarlNonceLen:GnarlNonceLen+GnarlMidBytes], pkt.Identity[:])
61 copy(buf[GnarlNonceLen+GnarlMidBytes:GnarlHeaderLen], pkt.AuthTag[:])
62 copy(buf[GnarlHeaderLen:], pkt.Ciphertext)
63 return buf
64 }
65
66 func UnmarshalGnarlPacket(data []byte) (pkt *GnarlPacket, err error) {
67 if int32(len(data)) < GnarlHeaderLen {
68 return nil, errors.New("gnarl: packet too short")
69 }
70
71 pkt = &GnarlPacket{}
72 copy(pkt.Nonce[:], data[0:GnarlNonceLen])
73 copy(pkt.Identity[:], data[GnarlNonceLen:GnarlNonceLen+GnarlMidBytes])
74 copy(pkt.AuthTag[:], data[GnarlNonceLen+GnarlMidBytes:GnarlHeaderLen])
75 pkt.Ciphertext = []byte{:int32(len(data)) - GnarlHeaderLen}
76 copy(pkt.Ciphertext, data[GnarlHeaderLen:])
77 return pkt, nil
78 }
79
80 func gnarlCipherAndMAC(secret Hamadryad, nonce [GnarlNonceLen]byte) (macKey Hamadryad, cipher *chacha20.Cipher) {
81 keyHash := Hash([]byte("gnarl-chacha20-key") | secret[:])
82 var key [32]byte
83 copy(key[:], keyHash[:32])
84
85 var chachaNonce [12]byte
86 copy(chachaNonce[:GnarlNonceLen], nonce[:])
87
88 cipher, _ = chacha20.NewUnauthenticatedCipher(key[:], chachaNonce[:])
89
90 var macBlock [64]byte
91 cipher.XORKeyStream(macBlock[:], macBlock[:])
92 copy(macKey[:], macBlock[:HamBytes])
93
94 return macKey, cipher
95 }
96
97 func gnarlAuthTag(macKey Hamadryad, nonce [GnarlNonceLen]byte, identity GnarlMid, ciphertext []byte) (tag GnarlMid) {
98 ctHash := GHash(ciphertext)
99
100 var tagInput []byte
101 tagInput = tagInput | macKey[:]
102 tagInput = tagInput | nonce[:]
103 tagInput = tagInput | identity[:]
104 tagInput = tagInput | ctHash[:]
105 return GMid(tagInput)
106 }
107
108 // GnarlSchnorrChallenge computes the Fiat-Shamir challenge using SHA-256
109 // truncated to 27 bytes.
110 func GnarlSchnorrChallenge(data []byte) (result [GnarlMidBytes]byte) {
111 h := sha256.Sum256(data)
112 copy(result[:], h[:GnarlMidBytes])
113 return result
114 }
115
116 // GnarlNonceFromCounter creates a nonce from a 64-bit counter and 16-bit epoch.
117 func GnarlNonceFromCounter(epoch uint16, counter uint64) (nonce [GnarlNonceLen]byte) {
118 nonce[0] = byte(counter)
119 nonce[1] = byte(counter >> 8)
120 nonce[2] = byte(counter >> 16)
121 nonce[3] = byte(counter >> 24)
122 nonce[4] = byte(counter >> 32)
123 nonce[5] = byte(counter >> 40)
124 nonce[6] = byte(counter >> 48)
125 nonce[7] = byte(counter >> 56)
126 nonce[8] = byte(epoch)
127 nonce[9] = byte(epoch >> 8)
128 return nonce
129 }
130