initial commit master
authorJann Horn <jann@thejh.net>
Sat, 4 Apr 2015 15:39:52 +0000 (17:39 +0200)
committerJann Horn <jann@thejh.net>
Sat, 4 Apr 2015 15:39:52 +0000 (17:39 +0200)
.gitignore [new file with mode: 0644]
LICENSE [new file with mode: 0644]
README [new file with mode: 0644]
authenticator.c [new file with mode: 0644]
base64.c [new file with mode: 0644]
compile.sh [new file with mode: 0755]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..905e077
--- /dev/null
@@ -0,0 +1 @@
+authenticator
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..993ed39
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,7 @@
+Copyright (c) 2015 Jann Horn <jann@thejh.net>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..78857ff
--- /dev/null
+++ b/README
@@ -0,0 +1,22 @@
+This program generates short authenticators roughly as described at
+<https://thejh.net/written-stuff/safe-short-hash>, but with a 10-bit
+hash and a 14-bit salt (to remove the requirement for generation of
+a hash prefix collision and transmission of the nonce used to create
+the collision).
+
+
+WARNINGS:
+
+This scheme trades in some security to gain usability. (Well, I think
+it improves usability compared to comparing whole hashes - you might
+disagree.) It only guarantees that an attacker can't be successful in
+more than 1% of attack attempts, and being successful in 0.5% still
+requires about 2^100 operations, but being successful in about 0.1%
+of all attempts should be pretty simple.
+Therefore, only use this to verify the authenticity of data
+transmitted in such a way that it would be intolerable for an attacker
+to be discovered after a single attack attempt with 99% probability.
+
+I am not aware of anyone having reviewed either the scheme or the
+implementation. Therefore, it's probably not a good idea to rely on
+this.
\ No newline at end of file
diff --git a/authenticator.c b/authenticator.c
new file mode 100644 (file)
index 0000000..34685b3
--- /dev/null
@@ -0,0 +1,122 @@
+/* Copyright 2015 Jann Horn <jann@thejh.net>.
+ * See LICENSE for license information.
+ */
+
+#define _GNU_SOURCE
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h> /* GNU basename */
+#include <openssl/sha.h>
+#include <openssl/rand.h>
+
+#define eprintf(...) fprintf(stderr, __VA_ARGS__)
+
+#define SALT_BITS 14
+#define SALT_MASK 0x00003fffU /* lowest 14 bits */
+#define HASH_MASK 0x00ffc000U /* next 10 bits */
+
+void base64_encode(uint32_t n, char out[5]);
+uint32_t base64_decode(char *in);
+
+static void openssl_failure(void) {
+  eprintf("OpenSSL internal failure\n");
+  exit(3);
+}
+
+static uint32_t gen_authenticator(char *path, uint16_t salt) {
+  SHA256_CTX ctx;
+  if (!SHA256_Init(&ctx)) openssl_failure();
+
+  /* append the filename including the terminating nullbyte to prevent
+   * ambiguity regarding where the filename ends */
+  char *name = basename(path);
+  if (!SHA256_Update(&ctx, name, strlen(name)+1)) openssl_failure();
+
+  FILE *f = fopen(path, "r");
+  if (!f) {
+    perror("unable to open input file");
+    exit(1);
+  }
+  char buf[20000];
+  size_t len;
+  while ((len = fread(buf, 1, sizeof(buf), f)) > 0) {
+    if (!SHA256_Update(&ctx, buf, len)) openssl_failure();
+  }
+  if (!feof(f) || ferror(f)) {
+    eprintf("error occured while reading input file");
+    exit(1);
+  }
+  fclose(f);
+
+  if (!SHA256_Update(&ctx, &salt, sizeof(salt))) openssl_failure();
+
+  unsigned char digest[SHA256_DIGEST_LENGTH];
+  if (!SHA256_Final(digest, &ctx)) openssl_failure();
+
+  uint32_t authenticator = salt;
+  authenticator |= (digest[0] << SALT_BITS | digest[1] << (SALT_BITS+8)) & HASH_MASK;
+  return authenticator;
+}
+
+static void chop(char *str) {
+  if (*str == '\0') return;
+  char *p = str + strlen(str) - 1;
+  while (p >= str && (*p == '\r' || *p == '\n')) {
+    *p = '\0';
+    p--;
+  }
+}
+
+#define RED "\x1b[0;31m"
+#define GREEN "\x1b[0;32m"
+#define RESET "\x1b[0m"
+
+int main(int argc, char **argv) {
+  if (argc != 3) {
+usage:
+    eprintf("bad invocation. usage:\n"
+            "%s <gen | verify> <path>\n", argv[0]);
+    return 2;
+  }
+
+  /* 4 bytes base64, 1 byte null, rest trailing garbage from fgets */
+  char b64_authenticator[30];
+
+  if (strcmp(argv[1], "gen") == 0) {
+    uint16_t salt;
+    if (RAND_bytes((unsigned char*)&salt, sizeof(salt)) != 1) openssl_failure();
+    salt &= SALT_MASK;
+    uint32_t authenticator = gen_authenticator(argv[2], salt);
+    base64_encode(authenticator, b64_authenticator);
+    printf("Authenticator generated: \x1b[7m%s\x1b[0m\n", b64_authenticator);
+    printf(RED"AFTER"RESET" the recipient confirms that he has received the file,\n"
+           "tell him the authenticator code and roughly compare the filename.\n");
+  } else if (strcmp(argv[1], "verify") == 0) {
+    printf("Please enter the 4-character authenticator: ");
+    if (!fgets(b64_authenticator, sizeof(b64_authenticator), stdin)) {
+      eprintf("unable to read user input\n");
+      return 2;
+    }
+    chop(b64_authenticator);
+    uint32_t authenticator = base64_decode(b64_authenticator);
+    if (gen_authenticator(argv[2], authenticator & SALT_MASK) == authenticator) {
+      printf("verification "GREEN"SUCCESSFUL"RESET"\n"
+             "If the filename of the received file matches\n"
+             "the one you expected, everything is fine.\n");
+    } else {
+      printf("verification "RED"FAILED"RESET"\n"
+             "Something nasty might be going on!\n"
+             RED"DO NOT RETRANSMIT THE FILE OR LET THE SENDER REGENERATE THE AUTHENTICATOR!"RESET"\n"
+             "Because there is a small inherent chance of attacks succeeding against this scheme,\n"
+             "retrying might make an attack possible. Find out what happened and, if you think an\n"
+             "attack might have happened, switch to normal cryptographic checksums.\n");
+      return 1;
+    }
+  } else {
+    goto usage;
+  }
+
+  return 0;
+}
\ No newline at end of file
diff --git a/base64.c b/base64.c
new file mode 100644 (file)
index 0000000..324b049
--- /dev/null
+++ b/base64.c
@@ -0,0 +1,57 @@
+/* Copyright 2015 Jann Horn <jann@thejh.net>.
+ * See LICENSE for license information.
+ * 
+ * This file performs Base64 encoding and decoding using the 
+ * "URL and Filename Safe Alphabet" from RFC 4648; however, it does
+ * not attempt to be RFC-compliant apart from the choice of alphabet.
+ * These Base64 routines are not generic; they only convert between
+ * 24 bits of binary data and 4 characters of Base64.
+ */
+
+#define _GNU_SOURCE
+#include <stdint.h>
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+static char alphabet[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+
+static char bin_to_char(uint32_t n) {
+  return alphabet[n];
+}
+
+static uint32_t char_to_bin(char c) {
+  char *p = strchr(alphabet, c);
+  if (p == 0) {
+    fprintf(stderr, "invalid base64 character encountered: '%c'\n", c);
+    exit(1);
+  }
+  return p - alphabet;
+}
+
+/* base64-encode 24 bits of data, least significant bits first */
+void base64_encode(uint32_t n, char out[5]) {
+  assert((n & 0xff000000U) == 0);
+  for (int i = 0; i < 4; i++) {
+    out[i] = bin_to_char(n & 0x3f);
+    n >>= 6;
+  }
+  out[4] = '\0';
+  assert(n == 0);
+}
+
+uint32_t base64_decode(char *in) {
+  size_t len = strlen(in);
+  if (len != 4) {
+    fprintf(stderr, "Expected 4 bytes of Base64 data, but got %d\n", (int)len);
+    exit(1);
+  }
+  uint32_t out = 0;
+  for (int i = 3; i >= 0; i--) {
+    out <<= 6;
+    out |= char_to_bin(in[i]);
+  }
+  assert((out & 0xff000000U) == 0);
+  return out;
+}
\ No newline at end of file
diff --git a/compile.sh b/compile.sh
new file mode 100755 (executable)
index 0000000..b8b4bd5
--- /dev/null
@@ -0,0 +1,5 @@
+#!/bin/sh
+CC=gcc
+FLAGS='-std=gnu99 -ggdb -Wall -lcrypto'
+
+$CC $FLAGS -o authenticator authenticator.c base64.c
\ No newline at end of file