ctr.go raw

   1  package crypto
   2  
   3  import "golang.org/x/crypto/chacha20"
   4  
   5  // CTR implements counter mode encryption using ChaCha20.
   6  //
   7  // A shared secret (Hamadryad) is hashed once to derive a 32-byte ChaCha20
   8  // key. This is a one-time key-derivation step using SWIFFT's proven
   9  // collision/preimage resistance -- the hash is NOT used as a PRF. All
  10  // keystream generation is performed by ChaCha20.
  11  //
  12  // Random access: ChaCha20's SetCounter enables seeking in encrypted streams.
  13  
  14  // BlockSize is the number of keystream bytes produced per ChaCha20 block counter.
  15  const BlockSize = 64
  16  
  17  // CTRStream holds the state for ChaCha20 stream encryption/decryption.
  18  type CTRStream struct {
  19  	chachaKey   [chacha20.KeySize]byte
  20  	chachaNonce [chacha20.NonceSize]byte
  21  }
  22  
  23  // newCipher creates a fresh ChaCha20 cipher from the stream's key/nonce.
  24  func (s *CTRStream) newCipher() *chacha20.Cipher {
  25  	c, _ := chacha20.NewUnauthenticatedCipher(s.chachaKey[:], s.chachaNonce[:])
  26  	return c
  27  }
  28  
  29  // deriveChaCha20Key derives a 32-byte ChaCha20 key from Hamadryad key material.
  30  func deriveChaCha20Key(keyMaterial Hamadryad) [chacha20.KeySize]byte {
  31  	keyHash := Hash(append([]byte("hamadryad-chacha20-key-v1"), keyMaterial[:]...))
  32  	var key [chacha20.KeySize]byte
  33  	copy(key[:], keyHash[:chacha20.KeySize])
  34  	return key
  35  }
  36  
  37  // deriveChaCha20Nonce derives a 12-byte ChaCha20 nonce from Hamadryad nonce material.
  38  func deriveChaCha20Nonce(nonceMaterial Hamadryad) [chacha20.NonceSize]byte {
  39  	nonceHash := Hash(append([]byte("hamadryad-chacha20-nonce-v1"), nonceMaterial[:]...))
  40  	var nonce [chacha20.NonceSize]byte
  41  	copy(nonce[:], nonceHash[:chacha20.NonceSize])
  42  	return nonce
  43  }
  44  
  45  // NewCTRStreamFromSecret creates a CTR stream using a shared secret as the key.
  46  // The nonce should be unique per message for a given secret.
  47  func NewCTRStreamFromSecret(secret Hamadryad, nonce []byte) *CTRStream {
  48  	nonceHash := Hash(nonce)
  49  	return &CTRStream{
  50  		chachaKey:   deriveChaCha20Key(secret),
  51  		chachaNonce: deriveChaCha20Nonce(nonceHash),
  52  	}
  53  }
  54  
  55  // Encrypt encrypts plaintext using the ChaCha20 keystream.
  56  func (s *CTRStream) Encrypt(plaintext []byte) []byte {
  57  	return s.xor(plaintext)
  58  }
  59  
  60  // Decrypt decrypts ciphertext using the ChaCha20 keystream.
  61  // Decryption is identical to encryption (XOR is self-inverse).
  62  func (s *CTRStream) Decrypt(ciphertext []byte) []byte {
  63  	return s.xor(ciphertext)
  64  }
  65  
  66  // KeystreamBlock generates the keystream block for a given counter.
  67  // Each call is independent -- blocks can be generated in any order.
  68  func (s *CTRStream) KeystreamBlock(counter uint64) []byte {
  69  	c := s.newCipher()
  70  	c.SetCounter(uint32(counter))
  71  	block := make([]byte, BlockSize)
  72  	c.XORKeyStream(block, block) // XOR zeros = raw keystream
  73  	return block
  74  }
  75  
  76  // EncryptCTRAt encrypts a single block at the given byte offset.
  77  // This enables random-access encryption of large streams.
  78  func (s *CTRStream) EncryptCTRAt(plaintext []byte, offset uint64) []byte {
  79  	c := s.newCipher()
  80  	blockIdx := uint32(offset / uint64(BlockSize))
  81  	blockOff := int(offset % uint64(BlockSize))
  82  	c.SetCounter(blockIdx)
  83  
  84  	// Discard partial block prefix to reach the target offset.
  85  	if blockOff > 0 {
  86  		discard := make([]byte, blockOff)
  87  		c.XORKeyStream(discard, discard)
  88  	}
  89  
  90  	result := make([]byte, len(plaintext))
  91  	c.XORKeyStream(result, plaintext)
  92  	return result
  93  }
  94  
  95  // xor XORs data with the ChaCha20 keystream.
  96  func (s *CTRStream) xor(data []byte) []byte {
  97  	c := s.newCipher()
  98  	result := make([]byte, len(data))
  99  	c.XORKeyStream(result, data)
 100  	return result
 101  }
 102