Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minimal code example for us noobs? #4

Open
maxsei opened this issue Jan 12, 2023 · 5 comments
Open

Minimal code example for us noobs? #4

maxsei opened this issue Jan 12, 2023 · 5 comments

Comments

@maxsei
Copy link

maxsei commented Jan 12, 2023

I struggled to grok how state I was supposed to managed state between lib calls. I realized that state needs to be fully managed between two peers which seems obvious, but I guess for those just looking to play with the library and see how it works it's not. Anyway I have this short working code example that I would like to find a home for here if at all possible, thanks.

#include "charm.h"
#include <stdio.h>
#include <string.h>

#define TAG_LEN 6 // not sure why this is here but it's used here https://github.com/fengjijiao/dsvpn/blob/19c9d6612ea29cb9796484a9a62b0f31ca8d42f3/include/vpn.h#L46
#define SHARED_CONTEXT "my code example"
#define SHARED_KEY "0my0secret0key0ayy0lmaooo0:)0000"

void printBytes(unsigned char *bb, size_t len) {
  for (void *end = &bb[len]; bb != end; bb++)
    printf("\\x%02x", *bb);
}

int main(int argc, char *argv[]) {
  // Show message.
  unsigned char msg[6] = "hello";
  printf("msg\n");
  printBytes(msg, sizeof msg);
  printf("\n");

  unsigned char tag_full[16];

  // Encrypt.
  {
    uint32_t st[12];
    unsigned char key[32] = SHARED_KEY;
    uint8_t iv[16] = SHARED_CONTEXT;

    uc_state_init(st, key, iv);
    uc_encrypt(st, (unsigned char *)msg, sizeof msg, tag_full);

    printf("encrypted msg\n");
    printBytes(msg, sizeof msg);
    printf("\n");
  }

  // Decrypt.
  {
    uint32_t st[12];
    unsigned char key[32] = SHARED_KEY;
    uint8_t iv[16] = SHARED_CONTEXT;
    unsigned char tag[TAG_LEN];

    uc_state_init(st, key, iv);
    memcpy(tag, tag_full, TAG_LEN);
    if (uc_decrypt(st, (unsigned char *)msg, sizeof msg, tag, sizeof(tag)) != 0) {
      fprintf(stderr, "failed to decrypt\n");
      exit(1);
    }
    printf("decrypted msg\n");
    printBytes(msg, 6);
    printf("\n");
  }

  return 0;
}
@HarryR
Copy link

HarryR commented Mar 14, 2023

Example:

dd if=/dev/urandom of=test bs=1M count=1
key=`charm encrypt test test.encrypted`
charm decrypt test.encrypted test.decrypted $key
sha256sum test*

Uses random IV, if no key specified generates random key & prints it when encrypting

#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include "_charm.c"
typedef struct { int fd; size_t size; uint8_t *map; } handle_t;
static inline uint8_t hexdigit( const char hex ) { return (hex <= '9') ? hex - '0' : toupper(hex) - 'A' + 10; }
static inline uint8_t hexbyte( const char *hex ) { return (hexdigit(*hex) << 4) | hexdigit(*(hex+1)); }
static inline void hexprint(const uint8_t *bytes, const size_t n) { for( size_t i = 0; i < n; i++ ) printf("%02x", bytes[i]); }
static inline void pexit(const char *msg, const int code) { perror(msg); exit(code); }
static inline void randombytes(void *buf, size_t len) { if ((size_t) syscall(SYS_getrandom, buf, (int) len, 0) != len) abort(); }
static inline void parse_hex(const char *hex_str, size_t len, uint8_t *out_bytes) {
    if( (2*len) != strlen(hex_str) ) { fprintf(stderr, "Error: need %ld hex encoded bytes\n", len); exit(__LINE__); }
    for( size_t b = 0; b < len; b++ ) out_bytes[b] = hexbyte(&hex_str[b * 2]);
}
static inline handle_t open_handle( const char *path, off_t truncate_to ) {
    struct stat sb;
    const int fd = open(path, truncate_to > 0 ? O_RDWR|O_CREAT|O_TRUNC : O_RDWR, 0644);
    if( -1 == fd || -1 == fstat(fd, &sb) ) { fprintf(stderr, "%s: ", path); pexit("error open/stat", __LINE__); }
    if( truncate_to > 0 ) {
        if( -1 == ftruncate(fd, truncate_to) ) { fprintf(stderr, "%s: ", path); pexit("error ftruncate", __LINE__); }
        sb.st_size = truncate_to;
    }
    uint8_t *map = mmap(NULL, sb.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if( map == NULL ) { printf("%s: ", path); pexit("map", __LINE__); }
    return (handle_t){fd, sb.st_size, map};
}
static inline void close_handle( const handle_t *handle ) {
    if( -1 == munmap(handle->map, handle->size) ) pexit("munmap infile error", __LINE__);
    if( -1 == close(handle->fd) ) pexit("close infile error", __LINE__);
}
static inline void charm_init( const char *key_hex, const char *nonce_str, uint32_t (*st)[XOODOO_STATE_SIZE], uint8_t (*key)[XOODOO_KEY_SIZE], uint8_t (*iv)[XOODOO_IV_SIZE], int rand_iv ) {    
    if( NULL != key_hex ) parse_hex(key_hex, XOODOO_KEY_SIZE, *key); else randombytes(key, XOODOO_KEY_SIZE);
    if( NULL != nonce_str ) parse_hex(nonce_str, XOODOO_IV_SIZE, *iv); else if (rand_iv ) randombytes(iv, XOODOO_IV_SIZE);
    uc_state_init(*st, *key, *iv);
}
static inline void main_hash( const char *infile, const char *nonce_str ) {
    const handle_t in = open_handle(infile, 0);
    uint32_t st[XOODOO_STATE_SIZE];
    uint8_t key[XOODOO_KEY_SIZE], iv[XOODOO_IV_SIZE], out_hash[XOODOO_DIGEST_SIZE];
    charm_init(NULL, nonce_str, &st, &key, &iv, 1);
    uc_hash(st, out_hash, in.map, in.size);
    hexprint(out_hash, XOODOO_DIGEST_SIZE); printf("\n");
    close_handle(&in);
    exit(0);
}
static inline void main_encdec(const int is_encrypt, const char *infile, const char *outfile, const char *key_hex, const char *nonce_str) {
    uint32_t st[XOODOO_STATE_SIZE];
    uint8_t key[XOODOO_KEY_SIZE], iv[XOODOO_IV_SIZE];
    const handle_t in = open_handle(infile, 0);
    handle_t out = open_handle(outfile, in.size + (is_encrypt ? (XOODOO_TAG_SIZE + XOODOO_IV_SIZE) : 0));
    memcpy(out.map, in.map, in.size);
    if( ! is_encrypt ) memcpy(iv, in.map+(in.size-XOODOO_IV_SIZE), XOODOO_IV_SIZE);
    charm_init(key_hex, nonce_str, &st, &key, &iv, is_encrypt);
    if( is_encrypt ) {
        hexprint(key, XOODOO_KEY_SIZE); printf("\n");
        uc_encrypt(st, out.map, in.size, out.map+in.size);
        memcpy(out.map+(in.size+XOODOO_TAG_SIZE), iv, XOODOO_IV_SIZE);
    } else {
        if( in.size < (1+XOODOO_TAG_SIZE+XOODOO_IV_SIZE) ) { fprintf(stderr, "Error: file too small, missing tag or IV\n"); exit(1); }
        if( 0 != uc_decrypt(st, out.map, in.size-XOODOO_TAG_SIZE-XOODOO_IV_SIZE, out.map+(in.size-XOODOO_TAG_SIZE-XOODOO_IV_SIZE), XOODOO_TAG_SIZE) )
        { fprintf(stderr, "Error: decrypt failed! Mismatched tag\n"); exit(1); }
    }
    close_handle(&in);
    if( ! is_encrypt && -1 == ftruncate(out.fd, in.size-XOODOO_TAG_SIZE-XOODOO_IV_SIZE)) pexit("ftruncate outfile error", __LINE__);
    close_handle(&out);
    exit(0);
}
int main( int argc, char **argv ) {
    if( argc >= 4 && argc <= 6 ) {
        const int is_encrypt = (0 == strcmp(argv[1], "encrypt"));
        if( is_encrypt || (0 == strcmp(argv[1], "decrypt")) ) main_encdec(is_encrypt, argv[2], argv[3], argv[4], 6 == argc ? argv[5] : NULL);
    }
    if( (3 == argc || 4 == argc) && 0 == strcmp(argv[1], "hash") ) main_hash(argv[2], 4 == argc ? argv[3] : NULL);
    fprintf(stderr, "Usage: %s hash <infile> [nonce]\n", argv[0]);
    fprintf(stderr, "   or...\n");
    fprintf(stderr, "Usage: %s encrypt|decrypt <infile> <outfile> [<256bit-key-hex> [128bit-nonce-hex]]\n", argv[0]);
    fprintf(stderr, "  e.g. %s encrypt %s %s.encrypted `od -vAn -N32 -tx1 /dev/urandom | tr -cd '[a-f0-9]'`\n", argv[0], argv[0], argv[0]);
    return __LINE__;
}

@maxsei
Copy link
Author

maxsei commented Mar 15, 2023

@HarryR it's great to see a CLI that applies charm functionality to user input and files (also cool to see how you adapted earlier revisions of this post to make things more idiomatic for this application)! I have a few questions though:
Charm pertainant:

  • What is the point of XOODOO_TAG_SIZE?
  • How come it is acceptable to use a random key if none is provided?
    Trivial:
  • Why are you memory mapping files for efficiency of writes instead of using malloc?
  • Why is __LINE__ returned for usage error?

@HarryR
Copy link

HarryR commented Mar 15, 2023

What is the point of XOODOO_TAG_SIZE?

Constants should have names, otherwise I could abbreviate XOODOO_TAG_SIZE+XOODOO_IV_SIZE to 32 which could be confused with either XOODOO_KEY_SIZE or XOODOO_DIGEST_SIZE...

#ifndef XOODOO_ROUNDS
#define XOODOO_ROUNDS 12
#endif
#define XOODOO_STATE_SIZE 12
#define XOODOO_KEY_SIZE 32
#define XOODOO_DIGEST_SIZE 32
#define XOODOO_TAG_SIZE 16
#define XOODOO_IV_SIZE 16

How come it is acceptable to use a random key if none is provided?

The nonce and the tag is appended to the end of the encrypted file.

If a fixed nonce was used and two files encrypted with the same key, knowledge of unencrypted content of either could be used to partially decrypt the other. Encryption is just an xor of the content with a key stream, so knowing content you can xor against encrypted to get the keystream, xor same position in other encrypted file to get the plaintext.

There have been many exploits where a bad random entropy source was used for nonce/IV which caused accidental nonce reuse. Some signature schemes such as EdDSA use a deterministic nonce (a keyed hash of the input) to ensure that the same content encrypted with the same key will always use the same nonce, which ensures that a bad random source cannot lead to accidental nonce reuse (e.g. when many embedded computers first boot they have very low or predictable entropy)

Why are you memory mapping files for efficiency of writes instead of using malloc?

It makes file I/O much easier, instead of loop, read() etc. I can just use memcpy, read() may fail or return fewer bytes than requested so mmap makes code simpler.

It should also work on huge files, as the pages of data being encrypted/decrypted will be paged in & out of memory by the kernel on an as-needed basis - using memcpy would mean you can't encrypt or decrypt files bigger than the computers memory, with mmap you could encrypt e.g. a 4tb file.

Why is __LINE__ returned for usage error?

When program is under 255 lines, exiting with __LINE__ provides exact source location of error.

e.g. if program prints same error message in multiple places (idk why), you can use return code to get location:

echo $?

Bonus question

Why use 4 == ... or NULL == ... rather than ... == NULL?

Because if you type x = NULL by accident, it could compile without warnings and change the flow.

Whereas you can't assign a constant, so the compiler will error.

@jedisct1
Copy link
Owner

Hi!

Adding a couple examples would indeed be useful, even If dsvpn is one.

A strong selling point is that the same state can be used for any sequence of operations, each of them automatically depending on the previous ones.

That effectively secures an entire session, not just individual operations. Examples should ideally show that.

The above example is a bit complicated to follow. Examples should remain simple to understand.
BTW for file encryption, it's better to encrypt one chunk after another rather than a possibly huge input as a single block. Decryption errors can be caught immediately, rather than after having loaded a huge amount of data that will eventually have to be discarded.

Maybe a version of encpipe using charm could be a better example of file encryption. That should be even easier and shorter to implement than with libhydrogen.

@HarryR
Copy link

HarryR commented Mar 16, 2023

(thank you for writing charm, I've used it in a few places as a replacement for tweetnacl)

The above example is a bit complicated to follow. Examples should remain simple to understand.

I got distracted by code golf, it is a deliberately ugly code monolith which only supports what I needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants