1 // SPDX-License-Identifier: BSD-2-Clause
2 /* LibTomCrypt, modular cryptographic library -- Tom St Denis
3 *
4 * LibTomCrypt is a library that provides various cryptographic
5 * algorithms in a highly modular and flexible manner.
6 *
7 * The library is free for all purposes without any express
8 * guarantee it works.
9 */
10
11 /* the idea of re-keying loosely follows the approach used in:
12 * http://bxr.su/OpenBSD/lib/libc/crypt/arc4random.c
13 */
14
15 #include "tomcrypt_private.h"
16
17 #ifdef LTC_CHACHA20_PRNG
18
19 const struct ltc_prng_descriptor chacha20_prng_desc =
20 {
21 "chacha20",
22 40,
23 &chacha20_prng_start,
24 &chacha20_prng_add_entropy,
25 &chacha20_prng_ready,
26 &chacha20_prng_read,
27 &chacha20_prng_done,
28 &chacha20_prng_export,
29 &chacha20_prng_import,
30 &chacha20_prng_test
31 };
32
33 /**
34 Start the PRNG
35 @param prng The PRNG state to initialize
36 @return CRYPT_OK if successful
37 */
chacha20_prng_start(prng_state * prng)38 int chacha20_prng_start(prng_state *prng)
39 {
40 LTC_ARGCHK(prng != NULL);
41 prng->ready = 0;
42 XMEMSET(&prng->u.chacha.ent, 0, sizeof(prng->u.chacha.ent));
43 prng->u.chacha.idx = 0;
44 LTC_MUTEX_INIT(&prng->lock)
45 return CRYPT_OK;
46 }
47
48 /**
49 Add entropy to the PRNG state
50 @param in The data to add
51 @param inlen Length of the data to add
52 @param prng PRNG state to update
53 @return CRYPT_OK if successful
54 */
chacha20_prng_add_entropy(const unsigned char * in,unsigned long inlen,prng_state * prng)55 int chacha20_prng_add_entropy(const unsigned char *in, unsigned long inlen, prng_state *prng)
56 {
57 unsigned char buf[40];
58 unsigned long i;
59 int err;
60
61 LTC_ARGCHK(prng != NULL);
62 LTC_ARGCHK(in != NULL);
63 LTC_ARGCHK(inlen > 0);
64
65 LTC_MUTEX_LOCK(&prng->lock);
66 if (prng->ready) {
67 /* chacha20_prng_ready() was already called, do "rekey" operation */
68 if ((err = chacha_keystream(&prng->u.chacha.s, buf, sizeof(buf))) != CRYPT_OK) goto LBL_UNLOCK;
69 for(i = 0; i < inlen; i++) buf[i % sizeof(buf)] ^= in[i];
70 /* key 32 bytes, 20 rounds */
71 if ((err = chacha_setup(&prng->u.chacha.s, buf, 32, 20)) != CRYPT_OK) goto LBL_UNLOCK;
72 /* iv 8 bytes */
73 if ((err = chacha_ivctr64(&prng->u.chacha.s, buf + 32, 8, 0)) != CRYPT_OK) goto LBL_UNLOCK;
74 /* clear KEY + IV */
75 zeromem(buf, sizeof(buf));
76 }
77 else {
78 /* chacha20_prng_ready() was not called yet, add entropy to ent buffer */
79 while (inlen--) prng->u.chacha.ent[prng->u.chacha.idx++ % sizeof(prng->u.chacha.ent)] ^= *in++;
80 }
81 err = CRYPT_OK;
82 LBL_UNLOCK:
83 LTC_MUTEX_UNLOCK(&prng->lock);
84 return err;
85 }
86
87 /**
88 Make the PRNG ready to read from
89 @param prng The PRNG to make active
90 @return CRYPT_OK if successful
91 */
chacha20_prng_ready(prng_state * prng)92 int chacha20_prng_ready(prng_state *prng)
93 {
94 int err;
95
96 LTC_ARGCHK(prng != NULL);
97
98 LTC_MUTEX_LOCK(&prng->lock);
99 if (prng->ready) { err = CRYPT_OK; goto LBL_UNLOCK; }
100 /* key 32 bytes, 20 rounds */
101 if ((err = chacha_setup(&prng->u.chacha.s, prng->u.chacha.ent, 32, 20)) != CRYPT_OK) goto LBL_UNLOCK;
102 /* iv 8 bytes */
103 if ((err = chacha_ivctr64(&prng->u.chacha.s, prng->u.chacha.ent + 32, 8, 0)) != CRYPT_OK) goto LBL_UNLOCK;
104 XMEMSET(&prng->u.chacha.ent, 0, sizeof(prng->u.chacha.ent));
105 prng->u.chacha.idx = 0;
106 prng->ready = 1;
107 LBL_UNLOCK:
108 LTC_MUTEX_UNLOCK(&prng->lock);
109 return err;
110 }
111
112 /**
113 Read from the PRNG
114 @param out Destination
115 @param outlen Length of output
116 @param prng The active PRNG to read from
117 @return Number of octets read
118 */
chacha20_prng_read(unsigned char * out,unsigned long outlen,prng_state * prng)119 unsigned long chacha20_prng_read(unsigned char *out, unsigned long outlen, prng_state *prng)
120 {
121 if (outlen == 0 || prng == NULL || out == NULL) return 0;
122 LTC_MUTEX_LOCK(&prng->lock);
123 if (!prng->ready) { outlen = 0; goto LBL_UNLOCK; }
124 if (chacha_keystream(&prng->u.chacha.s, out, outlen) != CRYPT_OK) outlen = 0;
125 LBL_UNLOCK:
126 LTC_MUTEX_UNLOCK(&prng->lock);
127 return outlen;
128 }
129
130 /**
131 Terminate the PRNG
132 @param prng The PRNG to terminate
133 @return CRYPT_OK if successful
134 */
chacha20_prng_done(prng_state * prng)135 int chacha20_prng_done(prng_state *prng)
136 {
137 int err;
138 LTC_ARGCHK(prng != NULL);
139 LTC_MUTEX_LOCK(&prng->lock);
140 prng->ready = 0;
141 err = chacha_done(&prng->u.chacha.s);
142 LTC_MUTEX_UNLOCK(&prng->lock);
143 LTC_MUTEX_DESTROY(&prng->lock);
144 return err;
145 }
146
147 /**
148 Export the PRNG state
149 @param out [out] Destination
150 @param outlen [in/out] Max size and resulting size of the state
151 @param prng The PRNG to export
152 @return CRYPT_OK if successful
153 */
_LTC_PRNG_EXPORT(chacha20_prng)154 _LTC_PRNG_EXPORT(chacha20_prng)
155
156 /**
157 Import a PRNG state
158 @param in The PRNG state
159 @param inlen Size of the state
160 @param prng The PRNG to import
161 @return CRYPT_OK if successful
162 */
163 int chacha20_prng_import(const unsigned char *in, unsigned long inlen, prng_state *prng)
164 {
165 int err;
166
167 LTC_ARGCHK(prng != NULL);
168 LTC_ARGCHK(in != NULL);
169 if (inlen < (unsigned long)chacha20_prng_desc.export_size) return CRYPT_INVALID_ARG;
170
171 if ((err = chacha20_prng_start(prng)) != CRYPT_OK) return err;
172 if ((err = chacha20_prng_add_entropy(in, inlen, prng)) != CRYPT_OK) return err;
173 return CRYPT_OK;
174 }
175
176 /**
177 PRNG self-test
178 @return CRYPT_OK if successful, CRYPT_NOP if self-testing has been disabled
179 */
chacha20_prng_test(void)180 int chacha20_prng_test(void)
181 {
182 #ifndef LTC_TEST
183 return CRYPT_NOP;
184 #else
185 prng_state st;
186 unsigned char en[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
187 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
188 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e,
189 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
190 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32 };
191 unsigned char dmp[300];
192 unsigned long dmplen = sizeof(dmp);
193 unsigned char out[500];
194 unsigned char t1[] = { 0x59, 0xB2, 0x26, 0x95, 0x2B, 0x01, 0x8F, 0x05, 0xBE, 0xD8 };
195 unsigned char t2[] = { 0x47, 0xC9, 0x0D, 0x03, 0xE4, 0x75, 0x34, 0x27, 0xBD, 0xDE };
196 unsigned char t3[] = { 0xBC, 0xFA, 0xEF, 0x59, 0x37, 0x7F, 0x1A, 0x91, 0x1A, 0xA6 };
197 int err;
198
199 if ((err = chacha20_prng_start(&st)) != CRYPT_OK) return err;
200 /* add entropy to uninitialized prng */
201 if ((err = chacha20_prng_add_entropy(en, sizeof(en), &st)) != CRYPT_OK) return err;
202 if ((err = chacha20_prng_ready(&st)) != CRYPT_OK) return err;
203 if (chacha20_prng_read(out, 10, &st) != 10) return CRYPT_ERROR_READPRNG; /* 10 bytes for testing */
204 if (compare_testvector(out, 10, t1, sizeof(t1), "CHACHA-PRNG", 1)) return CRYPT_FAIL_TESTVECTOR;
205 if (chacha20_prng_read(out, 500, &st) != 500) return CRYPT_ERROR_READPRNG; /* skip 500 bytes */
206 /* add entropy to already initialized prng */
207 if ((err = chacha20_prng_add_entropy(en, sizeof(en), &st)) != CRYPT_OK) return err;
208 if (chacha20_prng_read(out, 500, &st) != 500) return CRYPT_ERROR_READPRNG; /* skip 500 bytes */
209 if ((err = chacha20_prng_export(dmp, &dmplen, &st)) != CRYPT_OK) return err;
210 if (chacha20_prng_read(out, 500, &st) != 500) return CRYPT_ERROR_READPRNG; /* skip 500 bytes */
211 if (chacha20_prng_read(out, 10, &st) != 10) return CRYPT_ERROR_READPRNG; /* 10 bytes for testing */
212 if (compare_testvector(out, 10, t2, sizeof(t2), "CHACHA-PRNG", 2)) return CRYPT_FAIL_TESTVECTOR;
213 if ((err = chacha20_prng_done(&st)) != CRYPT_OK) return err;
214 if ((err = chacha20_prng_import(dmp, dmplen, &st)) != CRYPT_OK) return err;
215 if ((err = chacha20_prng_ready(&st)) != CRYPT_OK) return err;
216 if (chacha20_prng_read(out, 500, &st) != 500) return CRYPT_ERROR_READPRNG; /* skip 500 bytes */
217 if (chacha20_prng_read(out, 10, &st) != 10) return CRYPT_ERROR_READPRNG; /* 10 bytes for testing */
218 if (compare_testvector(out, 10, t3, sizeof(t3), "CHACHA-PRNG", 3)) return CRYPT_FAIL_TESTVECTOR;
219 if ((err = chacha20_prng_done(&st)) != CRYPT_OK) return err;
220
221 return CRYPT_OK;
222 #endif
223 }
224
225 #endif
226
227 /* ref: $Format:%D$ */
228 /* git commit: $Format:%H$ */
229 /* commit time: $Format:%ai$ */
230