ChaCha20 with GoChacha Cipher is a stream cipher which uses a 256-bit key and a 64-bit nonce [paper]. Currently AES has a virtual monopoly on secret key encryption. There would be major problems, though, if this was cracked. Along with this AES has been shown to be weak around cache-collision attacks. Google thus propose ChaCha20 as an alternative, and actively use it within TLS connections. Currently it is three times faster than software-enabled AES, and is not sensitive to timing attacks. It operates by creating a key stream which is then X-ORed with the plaintext. It has been standardised with RFC 7539: |
Test vector
The following is test vectors taken from here:
KEY: 00000000000000000000000000000000000000000000000000000000 00000000 NONCE: 0000000000000000 KEYSTREAM: 76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc 8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11c c387b669
Theory
ChaCha20 and Salsa take a 256-bit key (or a 128-bit version) and a 32-bit nonce This creates a key stream, which is then XORed with the plaintext stream. In software, it is more than three times faster than AES, and is well suited to lower-powered devices and in real-time communications.
ChaCha operates on 32-bit bits with a key of 256 bits (\(K = (k_0,k_1,k_2,k_3,k_4,k_5,k_6,k_7)\). This output blocks of 512-bits for the key stream (\(Z\), and which is EX-ORed with the plaintext stream.
The state of the encryption is stored with 16 32-bit word values within a 4x4 matrix:
\(\begin{bmatrix} x_{0} & x_{1} & x_{2} & x_{3} \\ x_{4} & x_{5} & x_{6} & x_{7} \\ x_{8} & x_{9} & x_{10} & x_{11} \\ x_{12} & x_{13} & x_{14} & x_{15} \\ \end{bmatrix}\)
The initial state contains 16 32-bit values with constant values (0x61707865, 0x3320646e, 0x79622d32, 0x6b206574) the key (\(k_0,k_1,k_2,k_3,k_4,k_5,k_6,k_7\)), the counter (\(c_0\)) and the nonce (\(n_0, n_1, n_2, n_3\)):
\(\begin{bmatrix} 0x61707865 & 0x3320646e & 0x79622d32 & 0x6b206574 \\ k_{0} & k_{1} & k_{2} & k_{3} \\ k_{4} & k_{5} & k_{6} & k_{7} \\ c_{0} & n_{0} & n_{1} & n_{2} \\ \end{bmatrix}\)
The counter thus has 32-bits (1 x 32 bits), and the nonce has 96-bits (3 x 32 bits). ChaCha then defines a quarter round as:
QR(a,b,c,d)
and where this is defined as:
a = a + b
d = d ⊕ a
d = (d)<<16
c = c + d
b = b ⊕ c
b = (b)<<12
a = a + b
d = d ⊕ a
d = (d)<<8
c = c + d
b = b ⊕ c
b = (b)<<7
There are then 20 rounds (10 for column rounds and 10 for diagonal rounds):
X is created with K, c and n y ← X for i ← 0 to 9 do /* Column Round */ (x0, x4, x8, x12) ← QR(x0, x4, x8, x12) (x5, x9, x13, x1) ← QR(x5, x9, x13, x1) (x10, x14, x2, x6) ← QR(x10, x14, x2, x6) (x15, x3, x7, x11) ← QR(x15, x3, x7, x11) /* Diagonal Round */ (x0, x5, x10, x15) ← QR(x0, x5, x10, x15) (x1, x6, x11, x12) ← QR(x1, x6, x11, x12) (x2, x7, x8, x13) ← QR(x2, x7, x8, x13) (x3, x4, x9, x14) ← QR(x3, x4, x9, x14) end for Z ← X + y
Z is the resultant key stream.
With RFC7905 we see the definition for ChaCha20-Poly1305 Cipher Suites for Transport Layer Security (TLS):
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = {0xCC, 0xA8} TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = {0xCC, 0xA9} TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = {0xCC, 0xAA} TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 = {0xCC, 0xAB} TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 = {0xCC, 0xAC} TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256 = {0xCC, 0xAD} TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256 = {0xCC, 0xAE}
Coding
The following provides the code:
package main import ( "fmt" "golang.org/x/crypto/chacha20poly1305" "crypto/sha256" "os" ) func main() { pass:="Hello" msg:="Pass" argCount := len(os.Args[1:]) if (argCount>0) {msg= string(os.Args[1])} if (argCount>1) {pass= string(os.Args[2])} key := sha256.Sum256([]byte(pass)) aead, _ := chacha20poly1305.NewX(key[:]) if (pass=="") { a:=make([]byte, 32) copy(key[:32],a[:32]) aead, _ = chacha20poly1305.NewX(a) } if (msg=="") { a:=make([]byte, 32) msg=string(a) } nonce := make([]byte, chacha20poly1305.NonceSizeX) ciphertext := aead.Seal(nil, nonce, []byte(msg), nil) plaintext, _ := aead.Open(nil, nonce, ciphertext, nil) fmt.Printf("Message:\t%s\n", msg) fmt.Printf("Passphrase:\t%s\n", pass) fmt.Printf("\nKey:\t%x\n", key) fmt.Printf("Nonce:\t%x\n", nonce) fmt.Printf("\nCipher stream:\t%x\n", ciphertext) fmt.Printf("Plain text:\t%s\n", plaintext) }
A sample run is:
Message: Testing 123 Passphrase: Bill Key: e51783b4d7688ffba51a35d8c9f04041606c0d6fb00bb306fba0f2dcb7e1f890 Nonce: 000000000000000000000000000000000000000000000000 Cipher stream: ae3bea89fa24d4969e31d04457d1c13f7a033266d3a2d453bbd4ad Plain text: Testing 123
If we use a key of "000...00" and a message of zero bytes, we can see the test key stream of zeros:
Message: Passphrase: Key: 0000000000000000000000000000000000000000000000000000000000000000 Nonce: 000000000000000000000000000000000000000000000000 Cipher stream: 789e9689e5208d7fd9e1f3c5b5341f48ef18a13e418998addadd97a3693a987f943da98353b75592311f4e23c0710078 Plain text: