ctr.mx raw

   1  package hamcrypto
   2  
   3  // CTR implements counter mode encryption using ChaCha20.
   4  
   5  import "golang.org/x/crypto/chacha20"
   6  
   7  const BlockSize = 64
   8  
   9  type CTRStream struct {
  10  	chachaKey   [32]byte
  11  	chachaNonce [12]byte
  12  }
  13  
  14  func (s *CTRStream) newCipher() (c *chacha20.Cipher) {
  15  	c, _ = chacha20.NewUnauthenticatedCipher(s.chachaKey[:], s.chachaNonce[:])
  16  	return c
  17  }
  18  
  19  func deriveChaCha20Key(keyMaterial Hamadryad) (key [32]byte) {
  20  	keyHash := Hash([]byte("hamadryad-chacha20-key-v1") | keyMaterial[:])
  21  	copy(key[:], keyHash[:32])
  22  	return key
  23  }
  24  
  25  func deriveChaCha20Nonce(nonceMaterial Hamadryad) (nonce [12]byte) {
  26  	nonceHash := Hash([]byte("hamadryad-chacha20-nonce-v1") | nonceMaterial[:])
  27  	copy(nonce[:], nonceHash[:12])
  28  	return nonce
  29  }
  30  
  31  // NewCTRStreamFromSecret creates a CTR stream from a shared secret.
  32  func NewCTRStreamFromSecret(secret Hamadryad, nonce []byte) (s *CTRStream) {
  33  	nonceHash := Hash(nonce)
  34  	return &CTRStream{
  35  		chachaKey:   deriveChaCha20Key(secret),
  36  		chachaNonce: deriveChaCha20Nonce(nonceHash),
  37  	}
  38  }
  39  
  40  func (s *CTRStream) Encrypt(plaintext []byte) (ciphertext []byte) {
  41  	return s.xor(plaintext)
  42  }
  43  
  44  func (s *CTRStream) Decrypt(ciphertext []byte) (plaintext []byte) {
  45  	return s.xor(ciphertext)
  46  }
  47  
  48  func (s *CTRStream) KeystreamBlock(counter uint64) (block []byte) {
  49  	c := s.newCipher()
  50  	c.SetCounter(uint32(counter))
  51  	block = []byte{:BlockSize}
  52  	c.XORKeyStream(block, block)
  53  	return block
  54  }
  55  
  56  func (s *CTRStream) EncryptCTRAt(plaintext []byte, offset uint64) (result []byte) {
  57  	c := s.newCipher()
  58  	blockIdx := uint32(offset / uint64(BlockSize))
  59  	blockOff := int32(offset % uint64(BlockSize))
  60  	c.SetCounter(blockIdx)
  61  
  62  	if blockOff > 0 {
  63  		discard := []byte{:blockOff}
  64  		c.XORKeyStream(discard, discard)
  65  	}
  66  
  67  	result = []byte{:int32(len(plaintext))}
  68  	c.XORKeyStream(result, plaintext)
  69  	return result
  70  }
  71  
  72  func (s *CTRStream) xor(data []byte) (result []byte) {
  73  	c := s.newCipher()
  74  	result = []byte{:int32(len(data))}
  75  	c.XORKeyStream(result, data)
  76  	return result
  77  }
  78