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