From 2eb10b8925e2c6c8146f6202527109ba77210301 Mon Sep 17 00:00:00 2001 From: Daiki Ueno Date: Fri, 7 Feb 2014 16:41:29 +0900 Subject: [PATCH] Add GPG compatible symmetric encryption command * admin/merge-gnulib (GNULIB_MODULES): Add crypto/rijndael. * src/fns.c: Include rijndael-alg-fst.h and rijndael-api-fst.h. (randomize, cipher_write_length) (cipher_write_session_key_packet) (cipher_write_literal_data_packet) (cipher_write_encrypted_data_packet, cipher_init) (cipher_encrypt, Fsimple_encrypt_string): New function. (syms_of_fns): Register Ssimple_encrypt_string. --- admin/merge-gnulib | 2 +- src/fns.c | 354 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 355 insertions(+), 1 deletion(-) diff --git a/admin/merge-gnulib b/admin/merge-gnulib index 75808d3..9e759ce 100755 --- a/admin/merge-gnulib +++ b/admin/merge-gnulib @@ -28,7 +28,7 @@ GNULIB_URL=git://git.savannah.gnu.org/gnulib.git GNULIB_MODULES=' alloca-opt byteswap c-ctype c-strcase careadlinkat close-stream count-one-bits count-trailing-zeros - crypto/md5 crypto/sha1 crypto/sha256 crypto/sha512 + crypto/md5 crypto/sha1 crypto/sha256 crypto/sha512 crypto/rijndael dtoastr dtotimespec dup2 environ execinfo faccessat fcntl fcntl-h fdatasync fdopendir filemode fstatat fsync getloadavg getopt-gnu gettime gettimeofday diff --git a/src/fns.c b/src/fns.c index bc53313..8585fb1 100644 --- a/src/fns.c +++ b/src/fns.c @@ -4842,6 +4842,359 @@ If BINARY is non-nil, returns a string in binary form. */) { return secure_hash (algorithm, object, start, end, Qnil, Qnil, binary); } + + + +/************************************************************************ + Symmetric Key Encryption +************************************************************************/ + +#include "rijndael-alg-fst.h" +#include "rijndael-api-fst.h" + +struct cipher_handle +{ + rijndaelCipherInstance cipher; + rijndaelKeyInstance key; + + char iv[RIJNDAEL_BITSPERBLOCK / 8]; +}; + +/* FIXME: Use a good random source instead of random(). */ +static void +randomize (unsigned char *buffer, size_t length) +{ + int i; + + /* FIXME: Optimize considering the width of EMACS_INT. */ + for (i = 0; i < length; i++) + buffer[i] = get_random () & 0xFF; +} + +static size_t +cipher_write_length (unsigned char *output, size_t output_length, size_t length) +{ + if (length <= 191 && output_length >= 1) + { + output[0] = length; + return 1; + } + else if (length <= 8383 && output_length >= 2) + { + output[0] = ((length >> 8) & 0xFF) + 192; + output[1] = (length & 0xFF) - 192; + return 2; + } + else if (length <= 0xFFFFFFFF && output_length >= 5) + { + output[0] = 0xFF; + output[1] = (length >> 24) & 0xFF; + output[2] = (length >> 16) & 0xFF; + output[3] = (length >> 8) & 0xFF; + output[4] = length & 0xFF; + return 5; + } + + return 0; +} + +static size_t +cipher_write_session_key_packet (unsigned char *output, size_t output_length, + unsigned char *salt, size_t salt_length) +{ + size_t written; + unsigned char *p = output; + + if (salt_length < 8) + return 0; + + *p++ = 0xC0 | 3; + output_length--; + + written = cipher_write_length (p, output_length, 12); + if (!written) + return 0; + + output_length -= written; + p += written; + + if (output_length < 12) + return 0; + + *p++ = 4; + *p++ = 7; /* Cipher: AES128 */ + *p++ = 0x01; /* Salted S2K */ + *p++ = 8; /* Hash: SHA256 */ + memcpy (p, salt, 8); + p += 8; + + return p - output; +} + +static size_t +cipher_write_literal_data_packet (unsigned char *output, + size_t output_length, + const unsigned char *input, + size_t input_length) +{ + size_t written; + unsigned char *p = output; + + *p++ = 0xC0 | 11; + output_length--; + + written = cipher_write_length (p, output_length, 6 + input_length); + if (!written) + return 0; + + output_length -= written; + p += written; + + if (output_length < 6 + input_length) + return 0; + + *p++ = 'b'; /* Format: binary */ + *p++ = 0; /* Filename: none */ + *p++ = 0; /* Date: none */ + *p++ = 0; /* Date: none */ + *p++ = 0; /* Date: none */ + *p++ = 0; /* Date: none */ + + memcpy (p, input, input_length); + p += input_length; + + return p - output; +} + +static size_t +cipher_write_encrypted_data_packet (unsigned char *output, + size_t output_length, + const unsigned char *input, + size_t input_length) +{ + size_t written; + unsigned char *p = output; + + *p++ = 0xC0 | 9; + output_length--; + + written = cipher_write_length (p, output_length, input_length); + if (!written) + return 0; + + output_length -= written; + p += written; + + if (output_length < input_length) + return 0; + + /* Memory may overlap as we use the same buffer for encrypting and + formatting packets. */ + memmove (p, input, input_length); + p += input_length; + + return p - output; +} + +static bool +cipher_init (struct cipher_handle *handle, + const unsigned char *key, + size_t key_length) +{ + int rc; + char key_hex[32]; + const size_t block_size = RIJNDAEL_BITSPERBLOCK / 8; + char *p; + int i; + + if (key_length < block_size) + return false; + + memcpy (key_hex, key, key_length); + p = key_hex; + + for (i = block_size - 1; i >= 0; i--) + { + static char const hexdigit[16] = "0123456789abcdef"; + int p_i = p[i]; + p[2 * i] = hexdigit[p_i >> 4]; + p[2 * i + 1] = hexdigit[p_i & 0xf]; + } + + rc = rijndaelMakeKey (&handle->key, + RIJNDAEL_DIR_ENCRYPT, + RIJNDAEL_BITSPERBLOCK, + key_hex); + memset (key_hex, 0, sizeof (key_hex)); + if (rc != 0) + return false; + + rc = rijndaelCipherInit (&handle->cipher, RIJNDAEL_MODE_ECB, NULL); + if (rc != 0) + return false; + + memset (handle->iv, 0, sizeof (handle->iv)); + + return true; +} + +static size_t +cipher_encrypt (struct cipher_handle *handle, + const unsigned char *input, + size_t input_length, + unsigned char *output, + size_t output_length) +{ + int rc; + const size_t block_size = RIJNDAEL_BITSPERBLOCK / 8; + unsigned char header[RIJNDAEL_BITSPERBLOCK / 8 + 2]; + int i, nblocks; + unsigned char *cp = output; + + randomize (header, block_size); + header[block_size] = header[block_size - 2]; + header[block_size + 1] = header[block_size - 1]; + + rc = rijndaelBlockEncrypt (&handle->cipher, + &handle->key, + handle->iv, + RIJNDAEL_BITSPERBLOCK, + handle->iv); + if (rc < 0) + return 0; + + for (i = 0; i < block_size; i++) + *cp++ = handle->iv[i] = handle->iv[i] ^ header[i]; + + rc = rijndaelBlockEncrypt (&handle->cipher, + &handle->key, + handle->iv, + RIJNDAEL_BITSPERBLOCK, + handle->iv); + if (rc < 0) + return 0; + + *cp++ = handle->iv[0] ^ header[block_size]; + *cp++ = handle->iv[1] ^ header[block_size + 1]; + + /* Resynchronization. */ + for (i = 0; i < block_size; i++) + handle->iv[i] = output[i + 2]; + + rc = rijndaelBlockEncrypt (&handle->cipher, + &handle->key, + handle->iv, + RIJNDAEL_BITSPERBLOCK, + handle->iv); + if (rc < 0) + return 0; + + nblocks = (input_length + block_size - 1) / block_size; + for (i = 0; i < nblocks; i++) + { + size_t length = i == nblocks - 1 + ? input_length - i * block_size + : block_size; + int j; + + for (j = 0; j < length; j++) + *cp++ = handle->iv[j] = handle->iv[j] ^ input[16 * i + j]; + + rc = rijndaelBlockEncrypt (&handle->cipher, + &handle->key, + handle->iv, + length * 8, + handle->iv); + if (rc < 0) + return 0; + } + + return cp - output; +} + +DEFUN ("simple-encrypt-string", Fsimple_encrypt_string, Ssimple_encrypt_string, + 2, 2, 0, + doc: /* Symmetrically encrypt STRING with KEY and returns ciphertext. + +The format of the return value is compatible with GnuPG. +This function currently uses 128-bit AES for the cipher algorithm, +SHA-256 for the hash algorithm, and 8-octet random salt for key +derivation. */) + (Lisp_Object string, Lisp_Object key) +{ + ptrdiff_t length, output_length, written; + Lisp_Object encoded_string; + unsigned char key_hash[SHA256_DIGEST_SIZE]; + unsigned char salt[8]; + const size_t block_size = RIJNDAEL_BITSPERBLOCK / 8; + struct sha256_ctx ctx; + struct cipher_handle handle; + unsigned char *output, *p; + + CHECK_STRING (string); + CHECK_STRING (key); + + USE_SAFE_ALLOCA; + + length = 32 + SBYTES (string); + + /* Large enough to hold output and work area. */ + output_length = 24 + (length + block_size - 1) / block_size * block_size; + output = SAFE_ALLOCA (output_length); + + /* Write Encrypted Session Key packet. */ + randomize (salt, sizeof (salt)); + + p = output; + written = cipher_write_session_key_packet (p, + output_length, + salt, sizeof (salt)); + eassert (written > 0); + + p += written; + output_length -= written; + + /* Initialize cipher and set key. */ + sha256_init_ctx (&ctx); + sha256_process_bytes (salt, sizeof (salt), &ctx); + sha256_process_bytes (SSDATA (key), SBYTES (key), &ctx); + sha256_finish_ctx (&ctx, key_hash); + cipher_init (&handle, key_hash, sizeof (key_hash)); + memset (key_hash, 0, sizeof (key_hash)); + + /* Perform symmetric encryption. */ + written = + cipher_write_literal_data_packet (p + block_size + 8, + output_length - (block_size + 8), + (const unsigned char *) SSDATA (string), + SBYTES (string)); + eassert (written > 0); + + written = cipher_encrypt (&handle, + p + block_size + 8, + written, + p + 6, + output_length); + eassert (written > 0); + + written = cipher_write_encrypted_data_packet (p, + output_length, + p + 6, + written); + eassert (written > 0); + + p += written; + output_length -= written; + + /* Clear the rest of OUTPUT, which may contain part of plaintext. */ + memset (p, 0, output_length); + + encoded_string = make_unibyte_string ((char *) output, p - output); + SAFE_FREE (); + + return encoded_string; +} + void syms_of_fns (void) @@ -4999,6 +5352,7 @@ this variable. */); defsubr (&Smd5); defsubr (&Ssecure_hash); defsubr (&Slocale_info); + defsubr (&Ssimple_encrypt_string); hashtest_eq.name = Qeq; hashtest_eq.user_hash_function = Qnil; -- 1.8.4.2