File: openssl-enc.c

package info (click to toggle)
libtomcrypt 1.18.2-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 9,472 kB
  • sloc: ansic: 62,544; python: 370; perl: 330; sh: 260; java: 177; makefile: 155
file content (397 lines) | stat: -rw-r--r-- 12,413 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
/* LibTomCrypt, modular cryptographic library -- Tom St Denis
 *
 * LibTomCrypt is a library that provides various cryptographic
 * algorithms in a highly modular and flexible manner.
 *
 * The library is free for all purposes without any express
 * guarantee it works.
 */

/*
 * Demo to do the rough equivalent of:
 *
 *    openssl enc -aes-256-cbc -pass pass:foobar -in infile -out outfile -p
 *
 * Compilation:
 *
 *    $(CC) -I /path/to/headers -L .../libs \
 *          -o openssl-enc \
 *          openssl-enc.c -ltomcrypt
 *
 * Usage:
 *
 *    ./openssl-enc <enc|dec> infile outfile "passphrase" [salt]
 *
 * If provided, the salt must be EXACTLY a 16-char hex string.
 *
 * Demo is an example of:
 *
 * - (When decrypting) yanking salt out of the OpenSSL "Salted__..." header
 * - OpenSSL-compatible key derivation (in OpenSSL's modified PKCS#5v1 approach)
 * - Grabbing an Initialization Vector from the key generator
 * - Performing simple block encryption using AES
 * - PKCS#7-type padding (which hopefully can get ripped out of this demo and
 *   made a libtomcrypt thing someday).
 *
 * This program is free for all purposes without any express guarantee it
 * works. If you really want to see a license here, assume the WTFPL :-)
 *
 * BJ Black, bblack@barracuda.com, https://wjblack.com
 *
 * BUGS:
 *       Passing a password on a command line is a HORRIBLE idea.  Don't use
 *       this program for serious work!
 */

#include <tomcrypt.h>

#ifndef LTC_RIJNDAEL
#error Cannot compile this demo; Rijndael (AES) required
#endif
#ifndef LTC_CBC_MODE
#error Cannot compile this demo; CBC mode required
#endif
#ifndef LTC_PKCS_5
#error Cannot compile this demo; PKCS5 required
#endif
#ifndef LTC_RNG_GET_BYTES
#error Cannot compile this demo; random generator required
#endif
#ifndef LTC_MD5
#error Cannot compile this demo; MD5 required
#endif

/* OpenSSL by default only runs one hash round */
#define OPENSSL_ITERATIONS 1
/* Use aes-256-cbc, so 256 bits of key, 128 of IV */
#define KEY_LENGTH (256>>3)
#define IV_LENGTH (128>>3)
/* PKCS#5v1 requires exactly an 8-byte salt */
#define SALT_LENGTH 8
/* The header OpenSSL puts on an encrypted file */
static char salt_header[] = { 'S', 'a', 'l', 't', 'e', 'd', '_', '_' };

#include <errno.h>
#include <stdio.h>
#include <string.h>

/* A simple way to handle the possibility that a block may increase in size
   after padding. */
union paddable {
   unsigned char unpad[1024];
   unsigned char pad[1024+MAXBLOCKSIZE];
};

/*
 * Print usage and exit with a bad status (and perror() if any errno).
 *
 * Input:        argv[0] and the error string
 * Output:       <no return>
 * Side Effects: print messages and barf (does exit(3))
 */
void barf(const char *pname, const char *err)
{
   printf("Usage: %s <enc|dec> infile outfile passphrase [salt]\n", pname);
   printf("\n");
   printf("       # encrypts infile->outfile, random salt\n");
   printf("       %s enc infile outfile \"passphrase\"\n", pname);
   printf("\n");
   printf("       # encrypts infile->outfile, salt from cmdline\n");
   printf("       %s enc infile outfile pass 0123456789abcdef\n", pname);
   printf("\n");
   printf("       # decrypts infile->outfile, pulls salt from infile\n");
   printf("       %s dec infile outfile pass\n", pname);
   printf("\n");
   printf("       # decrypts infile->outfile, salt specified\n");
   printf("       # (don't try to read the salt from infile)\n");
   printf("       %s dec infile outfile pass 0123456789abcdef"
          "\n", pname);
   printf("\n");
   printf("Application Error: %s\n", err);
   if(errno)
      perror("     System Error");
   exit(-1);
}

/*
 * Parse a salt value passed in on the cmdline.
 *
 * Input:        string passed in and a buf to put it in (exactly 8 bytes!)
 * Output:       CRYPT_OK if parsed OK, CRYPT_ERROR if not
 * Side Effects: none
 */
int parse_hex_salt(unsigned char *in, unsigned char *out)
{
   int idx;
   for(idx=0; idx<SALT_LENGTH; idx++)
      if(sscanf((char*)in+idx*2, "%02hhx", out+idx) != 1)
         return CRYPT_ERROR;
   return CRYPT_OK;
}

/*
 * Parse the Salted__[+8 bytes] from an OpenSSL-compatible file header.
 *
 * Input:        file to read from and a to put the salt in (exactly 8 bytes!)
 * Output:       CRYPT_OK if parsed OK, CRYPT_ERROR if not
 * Side Effects: infile's read pointer += 16
 */
int parse_openssl_header(FILE *in, unsigned char *out)
{
   unsigned char tmp[SALT_LENGTH];
   if(fread(tmp, 1, sizeof(tmp), in) != sizeof(tmp))
      return CRYPT_ERROR;
   if(memcmp(tmp, salt_header, sizeof(tmp)))
      return CRYPT_ERROR;
   if(fread(tmp, 1, sizeof(tmp), in) != sizeof(tmp))
      return CRYPT_ERROR;
   memcpy(out, tmp, sizeof(tmp));
   return CRYPT_OK;
}

/*
 * Dump a hexed stream of bytes (convenience func).
 *
 * Input:        buf to read from, length
 * Output:       none
 * Side Effects: bytes printed as a hex blob, no lf at the end
 */
void dump_bytes(unsigned char *in, unsigned long len)
{
   unsigned long idx;
   for(idx=0; idx<len; idx++)
      printf("%02hhX", *(in+idx));
}

/*
 * Pad or unpad a message using PKCS#7 padding.
 * Padding will add 1-(blocksize) bytes and unpadding will remove that amount.
 * Set is_padding to 1 to pad, 0 to unpad.
 *
 * Input:        paddable buffer, size read, block length of cipher, mode
 * Output:       number of bytes after padding resp. after unpadding
 * Side Effects: none
 */
size_t pkcs7_pad(union paddable *buf, size_t nb, int block_length,
                 int is_padding)
{
   unsigned char padval;
   off_t idx;

   if(is_padding) {
      /* We are PADDING this block (and therefore adding bytes) */
      /* The pad value in PKCS#7 is the number of bytes remaining in
         the block, so for a 16-byte block and 3 bytes left, it's
         0x030303.  In the oddball case where nb is an exact multiple
         multiple of block_length, set the padval to blocksize (i.e.
         add one full block) */
      padval = (unsigned char) (block_length - (nb % block_length));
      padval = padval ? padval : block_length;

      memset(buf->pad+nb, padval, padval);
      return nb+padval;
   } else {
      /* We are UNPADDING this block (and removing bytes)
         We really just need to verify that the pad bytes are correct,
         so start at the end of the string and work backwards. */

      /* Figure out what the padlength should be by looking at the
         last byte */
      idx = nb-1;
      padval = buf->pad[idx];

      /* padval must be nonzero and <= block length */
      if(padval <= 0 || padval > block_length)
         return 0;

      /* First byte's accounted for; do the rest */
      idx--;

      while(idx >= (off_t)(nb-padval))
         if(buf->pad[idx] != padval)
            return 0;
         else
            idx--;

      /* If we got here, the pad checked out, so return a smaller
         number of bytes than nb (basically where we left off+1) */
      return idx+1;
   }
}

/*
 * Perform an encrypt/decrypt operation to/from files using AES+CBC+PKCS7 pad.
 * Set encrypt to 1 to encrypt, 0 to decrypt.
 *
 * Input:        in/out files, key, iv, and mode
 * Output:       CRYPT_OK if no error
 * Side Effects: bytes slurped from infile, pushed to outfile, fds updated.
 */
int do_crypt(FILE *infd, FILE *outfd, unsigned char *key, unsigned char *iv,
             int encrypt)
{
   union paddable inbuf, outbuf;
   int cipher, ret;
   symmetric_CBC cbc;
   size_t nb;

   /* Register your cipher! */
   cipher = register_cipher(&aes_desc);
   if(cipher == -1)
      return CRYPT_INVALID_CIPHER;

   /* Start a CBC session with cipher/key/val params */
   ret = cbc_start(cipher, iv, key, KEY_LENGTH, 0, &cbc);
   if( ret != CRYPT_OK )
      return -1;

   do {
      /* Get bytes from the source */
      nb = fread(inbuf.unpad, 1, sizeof(inbuf.unpad), infd);
      if(!nb)
         return encrypt ? CRYPT_OK : CRYPT_ERROR;

      /* Barf if we got a read error */
      if(ferror(infd))
         return CRYPT_ERROR;

      if(encrypt) {
         /* We're encrypting, so pad first (if at EOF) and then
            crypt */
         if(feof(infd))
            nb = pkcs7_pad(&inbuf, nb,
                           aes_desc.block_length, 1);

         ret = cbc_encrypt(inbuf.pad, outbuf.pad, nb, &cbc);
         if(ret != CRYPT_OK)
            return ret;

      } else {
         /* We're decrypting, so decrypt and then unpad if at
            EOF */
         ret = cbc_decrypt(inbuf.unpad, outbuf.unpad, nb, &cbc);
         if( ret != CRYPT_OK )
            return ret;

         if( feof(infd) )
            nb = pkcs7_pad(&outbuf, nb,
                           aes_desc.block_length, 0);
         if(nb == 0)
            /* The file didn't decrypt correctly */
            return CRYPT_ERROR;

      }

      /* Push bytes to outfile */
      if(fwrite(outbuf.unpad, 1, nb, outfd) != nb)
         return CRYPT_ERROR;

   } while(!feof(infd));

   /* Close up */
   cbc_done(&cbc);

   return CRYPT_OK;
}

/* Convenience macro for the various barfable places below */
#define BARF(a) { \
   if(infd) fclose(infd); \
   if(outfd) { fclose(outfd); remove(argv[3]); } \
   barf(argv[0], a); \
}
/*
 * The main routine.  Mostly validate cmdline params, open files, run the KDF,
 * and do the crypt.
 */
int main(int argc, char *argv[]) {
   unsigned char salt[SALT_LENGTH];
   FILE *infd = NULL, *outfd = NULL;
   int encrypt = -1;
   int hash = -1;
   int ret;
   unsigned char keyiv[KEY_LENGTH + IV_LENGTH];
   unsigned long keyivlen = (KEY_LENGTH + IV_LENGTH);
   unsigned char *key, *iv;

   /* Check proper number of cmdline args */
   if(argc < 5 || argc > 6)
      BARF("Invalid number of arguments");

   /* Check proper mode of operation */
   if     (!strncmp(argv[1], "enc", 3))
      encrypt = 1;
   else if(!strncmp(argv[1], "dec", 3))
      encrypt = 0;
   else
      BARF("Bad command name");

   /* Check we can open infile/outfile */
   infd = fopen(argv[2], "rb");
   if(infd == NULL)
      BARF("Could not open infile");
   outfd = fopen(argv[3], "wb");
   if(outfd == NULL)
      BARF("Could not open outfile");

   /* Get the salt from wherever */
   if(argc == 6) {
      /* User-provided */
      if(parse_hex_salt((unsigned char*) argv[5], salt) != CRYPT_OK)
         BARF("Bad user-specified salt");
   } else if(!strncmp(argv[1], "enc", 3)) {
      /* Encrypting; get from RNG */
      if(rng_get_bytes(salt, sizeof(salt), NULL) != sizeof(salt))
         BARF("Not enough random data");
   } else {
      /* Parse from infile (decrypt only) */
      if(parse_openssl_header(infd, salt) != CRYPT_OK)
         BARF("Invalid OpenSSL header in infile");
   }

   /* Fetch the MD5 hasher for PKCS#5 */
   hash = register_hash(&md5_desc);
   if(hash == -1)
      BARF("Could not register MD5 hash");

   /* Set things to a sane initial state */
   zeromem(keyiv, sizeof(keyiv));
   key = keyiv + 0;      /* key comes first */
   iv = keyiv + KEY_LENGTH;   /* iv comes next */

   /* Run the key derivation from the provided passphrase.  This gets us
      the key and iv. */
   ret = pkcs_5_alg1_openssl((unsigned char*)argv[4], strlen(argv[4]), salt,
                             OPENSSL_ITERATIONS, hash, keyiv, &keyivlen );
   if(ret != CRYPT_OK)
      BARF("Could not derive key/iv from passphrase");

   /* Display the salt/key/iv like OpenSSL cmdline does when -p */
   printf("salt="); dump_bytes(salt, sizeof(salt)); printf("\n");
   printf("key=");  dump_bytes(key, KEY_LENGTH);    printf("\n");
   printf("iv =");  dump_bytes(iv,  IV_LENGTH );    printf("\n");

   /* If we're encrypting, write the salt header as OpenSSL does */
   if(!strncmp(argv[1], "enc", 3)) {
      if(fwrite(salt_header, 1, sizeof(salt_header), outfd) !=
         sizeof(salt_header) )
         BARF("Error writing salt header to outfile");
      if(fwrite(salt, 1, sizeof(salt), outfd) != sizeof(salt))
         BARF("Error writing salt to outfile");
   }

   /* At this point, the files are open, the salt has been figured out,
      and we're ready to pump data through crypt. */

   /* Do the crypt operation */
   if(do_crypt(infd, outfd, key, iv, encrypt) != CRYPT_OK)
      BARF("Error during crypt operation");

   /* Clean up */
   fclose(infd); fclose(outfd);
   return 0;
}

/* ref:         HEAD -> master, tag: v1.18.2 */
/* git commit:  7e7eb695d581782f04b24dc444cbfde86af59853 */
/* commit time: 2018-07-01 22:49:01 +0200 */