1 /** Practical One-time Pad Library
18 /** Show a list of all loaded pads. */
23 for (p
= pads
; p
; p
= p
->next
) {
24 printf("Pad: %s: %s\n", p
->name
, p
->local_filename
);
28 /** Open a companion offset file for given pad. Caller must close.
30 * @param mode Either "rt" or "wt", for reading or writing.
32 FILE *open_offset_file(PAD
*p
, char *mode
)
35 char *offset_filename
;
36 size_t filename_length
;
38 /* Offset is stored in a separate file; pad plus OFFSET_FILE_NAME_EXTENSION. */
39 filename_length
= strlen(p
->local_filename
) + strlen(OFFSET_FILE_EXTENSION
) + 1;
40 offset_filename
= malloc(filename_length
);
41 if (!offset_filename
) {
45 snprintf(offset_filename
, filename_length
, "%s" OFFSET_FILE_EXTENSION
, p
->local_filename
);
47 /* Read offset from file. */
48 ofp
= fopen(offset_filename
, mode
);
50 fprintf(stderr
, "opening offset file %s failed\n", offset_filename
);
52 free(offset_filename
);
55 free(offset_filename
);
60 /** Read the pad offset of a given pad. */
61 unsigned long read_offset(PAD
*p
)
64 char buffer
[OFFSET_SIZE
];
67 ofp
= open_offset_file(p
, "rt");
69 memset(buffer
, 0, OFFSET_SIZE
);
70 if (fread(buffer
, 1, OFFSET_SIZE
- 1, ofp
) < 1) {
71 fprintf(stderr
, "could not read offset file for %s\n", p
->local_filename
);
75 if (fclose(ofp
) != 0) {
76 fprintf(stderr
, "error closing offset file for reading for %s\n", p
->local_filename
);
81 /* We finally got it! */
82 offset
= strtoul(buffer
, NULL
, 10);
87 /** Write the pad offset to the given pad. */
88 void write_offset(PAD
*p
, unsigned long offset
)
91 char buffer
[OFFSET_SIZE
];
93 ofp
= open_offset_file(p
, "wt");
95 memset(buffer
, 0, OFFSET_SIZE
);
96 snprintf(buffer
, OFFSET_SIZE
- 1, "%ld", offset
);
98 if (fwrite(buffer
, strlen(buffer
), 1, ofp
) != 1) {
99 fprintf(stderr
, "write error saving offset %ld for %s\n", offset
, p
->local_filename
);
104 if (fclose(ofp
) != 0) {
105 fprintf(stderr
, "error closing offset file for writing for %s\n", p
->local_filename
);
111 /** Load a pad file from disk, adding to 'pads' global. */
112 void load_pad(char *local_filename
, char *pad_name
)
117 fp
= fopen(local_filename
, "rb+");
123 new_pad
= malloc(sizeof(PAD
));
126 exit(EX_UNAVAILABLE
);
129 new_pad
->local_filename
= strdup(local_filename
);
130 new_pad
->name
= strdup(pad_name
);
132 new_pad
->next
= NULL
;
134 /* Add to linked list. */
141 for (p
= pads
; p
; p
= p
->next
)
143 tail
->next
= new_pad
;
147 /** Read a line from a file, up to max characters.
148 * Does not place \n in line string.
150 * Based on http://www.eskimo.com/~scs/cclass/notes/sx6c.html
152 * @return Line length (0 for empty), or EOF for end-of-file.
154 static int getline(FILE *fp
, char *line
, unsigned int max
)
161 while ((c
= fgetc(fp
)) != EOF
) {
169 if (c
== EOF
&& i
== 0)
176 void load_config(char *config_filename
)
179 char line
[MAX_CONFIG_LINE
];
181 cfg
= fopen(config_filename
, "rt");
183 fprintf(stderr
, "failed to open configuration file %s\n", config_filename
);
184 perror("fopen config file");
188 while(getline(cfg
, line
, MAX_CONFIG_LINE
) != EOF
) {
189 char *pad_name
, *pad_filename
;
191 /* Line is: (pad name)=(local filename) */
192 pad_name
= strtok(line
, "=");
195 pad_filename
= strtok(NULL
, "=");
197 /* TODO: separate send and recv filename, separated with : */
199 load_pad(pad_filename
, pad_name
);
203 /** Find pad with given name. */
204 PAD
*find_pad(char *pad_name
)
208 /* Null or blank pad = default (first) pad. */
209 if (!pad_name
|| !strcmp(pad_name
, ""))
212 for (p
= pads
; p
; p
= p
->next
) {
213 if (!strcmp(p
->name
, pad_name
))
220 /** Close all pads and free allocated memory. */
225 for (p
= pads
; p
; p
= next
) {
227 free(p
->local_filename
);
234 void free_message(MESSAGE
*msg
)
236 free(msg
->cipher_text
);
240 /** Unpackage a message packaged for transport.
242 * Caller must free_message(). */
243 MESSAGE
*unpackage(char *input
)
246 unsigned int at
, i
, j
;
248 char *s
, *end
, *b64_ct
;
249 char pad_name
[PAD_NAME_LENGTH
];
251 msg
= malloc(sizeof(MESSAGE
));
254 exit(EX_UNAVAILABLE
);
257 /** Format <v0.7 (pad name is unspecified; use default):
258 * MARKER_BEGIN + offset + comma
263 * MARKER_BEGIN + offset + comma + pad_name + comma
270 /* Locate where the message begins. */
271 s
= strstr(input
, MARKER_BEGIN
);
273 fprintf(stderr
, "unpackage: input |%s| lacks beginning marker %s\n",
274 input
, MARKER_BEGIN
);
279 end
= strstr(input
, MARKER_END
);
281 fprintf(stderr
, "unpackage: input |%s| lacks ending marker %s\n",
286 s
+= strlen(MARKER_BEGIN
);
287 msg
->offset
= strtoul(s
, &s
, 10);
289 /* Move after mandatory comma. */
291 fprintf(stderr
, "unpackage: missing comma after offset, at |%s|, input |%s|\n",
297 memset(pad_name
, 0, PAD_NAME_LENGTH
);
299 /* Includes pad name? */
300 if (s
[0] != '\n' && s
[0] != '\r') {
304 /* v0.7+ message, includes pad name */
305 while(s
[0] != '\n' && s
[0] != '\r' && s
[0] != ',' && s
< end
) {
309 if (i
> PAD_NAME_LENGTH
) {
310 fprintf(stderr
, "unpackage: pad name length > maximum %d, in input |%s|\n",
311 PAD_NAME_LENGTH
, input
);
317 msg
->pad
= find_pad(pad_name
);
319 fprintf(stderr
, "No such pad by name '%s'\n", pad_name
);
323 /* Go to next line */
324 while((s
[0] == '\n' || s
[0] == '\r' || s
[0] == ',') && (s
< end
))
327 /* Extract base64 data from end of message, removing whitespace. */
328 b64_ct
= malloc(end
- s
+ 4);
330 perror("malloc b64_ct");
339 } while(i
++ < end
- s
);
340 b64_ct
[i
- 1] = '\0';
342 b64_ct
[i
+ 1] = '\0';
343 b64_ct
[i
+ 2] = '\0';
346 /* Add base64 padding, if needed */
347 i
= strlen(b64_ct
) % 4;
349 b64_ct
[strlen(b64_ct
)] = '=';
351 b64_ct
[strlen(b64_ct
) + 1] = '\0';
354 msg
->cipher_text
= malloc(strlen(b64_ct
));
356 /* Decode base64. This may return -1 if it fails, so assign to a
357 * signed variable. */
358 b64_ret
= base64_decode(b64_ct
, msg
->cipher_text
);
361 fprintf(stderr
, "unpackage: failed to base64 decode |%s|\n", b64_ct
);
365 msg
->length
= b64_ret
;
372 /** Apply Vernam cipher (XOR) with given input and text.
374 * @param input What to XOR with the pad, either ciphertext to decrypt or plaintext to encrypt.
376 * @param length Length of input, in bytes.
378 * @param pad Pad to XOR 'input' with offset.
380 * @param offset Offset within pad to start XOR at.
382 * @return Pad XOR'd with input. Caller must free.
383 * The return value is the same length as 'length'. However, it is null-terminated
384 * in an extra byte for convenience. There may be embedded nulls.
386 static char *xor_with_pad(char *input
, unsigned long length
, PAD
*pad
, unsigned long offset
)
388 char *pad_data
, *out
;
391 /* Seek to area of pad we're using. */
392 if (fseek(pad
->fp
, offset
, SEEK_SET
) < 0) {
393 fprintf(stderr
, "failed to seek in pad %s to %ld\n",
400 pad_data
= malloc(length
);
402 perror("malloc pad data");
403 exit(EX_UNAVAILABLE
);
405 if (fread(pad_data
, length
, 1, pad
->fp
) < 1) {
406 fprintf(stderr
, "read pad %s, offset %ld, length %ld failed",
407 pad
->name
, offset
, length
);
411 /* Apply XOR to give output. */
412 out
= malloc(length
+ 1);
414 perror("malloc output buffer");
415 exit(EX_UNAVAILABLE
);
418 for (i
= 0; i
< length
; ++i
) {
419 out
[i
] = pad_data
[i
] ^ input
[i
];
423 /* Null-terminate output for convenience when using text-only messages.
424 * The output is still of length msg->length; use msg->length when decrypting
425 * binary messages. */
432 /** Decrypt a packaged message and return plaintext.
434 * @param out Pointer to pointer which is set to the output. Caller must free.
435 * There is an extra null at the end of this buffer, not part of the output,
436 * but there may be embedded nulls if binary data is encoded. If this is expected,
437 * use the return value:
439 * @return Length of message, in bytes.
441 unsigned int otp_decrypt(char *input
, char **out
)
446 msg
= unpackage(input
);
448 if (msg
->offset
< read_offset(msg
->pad
)) {
450 fprintf(stderr
, "** warning: this is an old message! possible replay attack: %ld < %ld\n",
451 msg
->offset
, read_offset(msg
->pad
));
455 length
= msg
->length
;
456 *out
= xor_with_pad(msg
->cipher_text
, msg
->length
, msg
->pad
, msg
->offset
);
462 /** Replace part of the pad corresponding to an encrypted message
463 * so that it encrypts to something else.
465 * @param input A packaged, encrypted message.
466 * @param with What to make 'input' decrypt to by changing the pad.
468 unsigned int otp_replace(char *input
, char *with
)
475 msg
= unpackage(input
);
477 if (fseek(msg
->pad
->fp
, msg
->offset
, SEEK_SET
) < 0) {
482 for (i
= 0; i
< msg
->length
; ++i
) {
485 /* What to make the message decrypt to. */
486 if (i
< strlen(with
))
491 c
= msg
->cipher_text
[i
] ^ with_c
;
493 fputc(c
, msg
->pad
->fp
);
499 /** Package up a message for transport.
501 * @return Packaged message. Caller frees.
503 char *package(MESSAGE
*msg
, unsigned int *out_length
)
508 /* Space for markers, and 4/3 of plaintext since base64'd. */
509 *out_length
= strlen(MARKER_BEGIN
) + 1 + OFFSET_SIZE
+ 1 +
510 1 + msg
->length
* 4 / 3 + 1 + strlen(MARKER_END
) + 1 + 1 + 1;
511 out
= malloc(*out_length
);
513 perror("malloc otp_decrypt output");
517 base64_encode(msg
->cipher_text
, msg
->length
, &b64_data
);
519 snprintf(out
, *out_length
, "%s%ld,%s,\n"
531 /** Encrypt and package a message.
533 * @return Encrypted, packaged message. Caller must free.
535 char *otp_encrypt(char *input
, unsigned int length
, char *to
, unsigned int *out_length
)
540 unsigned int length_padded
;
542 msg
.pad
= find_pad(to
);
544 fprintf(stderr
, "otp_encrypt: no such pad %s\n", to
);
548 /* Round up to nearest multiple of PADDING_MULTIPLE. Mitigates analysis
549 * based on message length. */
550 length_padded
= length
+ (PADDING_MULTIPLE
- length
% PADDING_MULTIPLE
);
551 input_padded
= malloc(length_padded
);
553 perror("malloc input padded");
557 /* input_padded is input, padded with 0s. */
558 memset(input_padded
, 0, length_padded
);
559 strncpy(input_padded
, input
, length
);
561 /* Use current offset. */
562 msg
.offset
= read_offset(msg
.pad
);
563 msg
.length
= length_padded
;
564 msg
.cipher_text
= xor_with_pad(input_padded
, length_padded
, msg
.pad
, msg
.offset
);
567 write_offset(msg
.pad
, msg
.offset
+ msg
.length
);
569 out
= package(&msg
, out_length
);