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