Browse Source

Add helpful hybrid encryption functions

svn:r1423
Nick Mathewson 21 years ago
parent
commit
35f531b94f
3 changed files with 148 additions and 1 deletions
  1. 123 0
      src/common/crypto.c
  2. 7 0
      src/common/crypto.h
  3. 18 1
      src/or/test.c

+ 123 - 0
src/common/crypto.c

@@ -114,6 +114,17 @@ crypto_cipher_evp_cipher(int type, int enc) {
     }
 }
 
+static INLINE int
+crypto_get_rsa_padding_overhead(int padding) {
+  switch(padding)
+    {
+    case RSA_NO_PADDING: return 0;
+    case RSA_PKCS1_OAEP_PADDING: return 42;
+    case RSA_PKCS1_PADDING: return 11;
+    default: assert(0); return -1;
+    }
+}
+
 static int _crypto_global_initialized = 0;
 
 int crypto_global_init()
@@ -645,6 +656,118 @@ int crypto_pk_private_sign(crypto_pk_env_t *env, unsigned char *from, int fromle
   }
 }
 
+/* Perform a hybrid (public/secret) encryption on 'fromlen' bytes of data
+ * from 'from', with padding type 'padding', storing the results on 'to'.
+ *
+ * If no padding is used, the public key must be at least as large as
+ * 'from'.
+ *
+ * Returns the number of bytes written on success, -1 on failure.
+ *
+ * The encrypted data consists of:
+ *
+ *   The source data, padded and encrypted with the public key, if the
+ *   padded source data is no longer than the public key.
+ *  OR
+ *   The beginning of the source data prefixed with a 16-symmetric key,
+ *   padded and encrypted with the public key; followed by the rest of
+ *   the source data encrypted in AES-CTR mode with the symmetric key.
+ */
+int crypto_pk_public_hybrid_encrypt(crypto_pk_env_t *env, unsigned char *from,
+				    int fromlen, unsigned char *to,
+				    int padding)
+{
+  int overhead, pkeylen, outlen, r, symlen;
+  crypto_cipher_env_t *cipher = NULL;
+  char buf[1024];
+
+  assert(env && from && to);
+
+  overhead = crypto_get_rsa_padding_overhead(padding);
+  pkeylen = crypto_pk_keysize(env);
+
+  if (padding == RSA_NO_PADDING && fromlen < pkeylen)
+    return -1;
+
+  if (fromlen+overhead <= pkeylen) {
+    /* It all fits in a single encrypt. */
+    return crypto_pk_public_encrypt(env,from,fromlen,to,padding);
+  }
+  cipher = crypto_new_cipher_env(CRYPTO_CIPHER_AES_CTR);
+  if (!cipher) return -1;
+  if (crypto_cipher_generate_key(cipher)<0)
+    goto err;
+  if (padding == RSA_NO_PADDING)
+    cipher->key[0] &= 0x7f;
+  if (crypto_cipher_encrypt_init_cipher(cipher)<0)
+    goto err;
+  memcpy(buf, cipher->key, 16);
+  memcpy(buf+16, from, pkeylen-overhead-16);
+
+  /* Length of symmetrically encrypted data. */
+  symlen = fromlen-(pkeylen-overhead-16);
+
+  outlen = crypto_pk_public_encrypt(env,buf,pkeylen-overhead,to,padding);
+  if (outlen!=pkeylen) {
+    goto err;
+  }
+  r = crypto_cipher_encrypt(cipher,
+			    from+pkeylen-overhead-16, symlen,
+			    to+outlen);
+
+  if (r<0) goto err;
+  memset(buf, 0, 1024);
+  crypto_free_cipher_env(cipher);
+  return outlen + symlen;
+ err:
+  memset(buf, 0, 1024);
+  if (cipher) crypto_free_cipher_env(cipher);
+  return -1;
+}
+
+/* Invert crypto_pk_public_hybrid_encrypt. */
+int crypto_pk_private_hybrid_decrypt(crypto_pk_env_t *env, unsigned char *from,
+				    int fromlen, unsigned char *to,
+				    int padding)
+{
+  int overhead, pkeylen, outlen, r;
+  crypto_cipher_env_t *cipher = NULL;
+  char buf[1024];
+
+  overhead = crypto_get_rsa_padding_overhead(padding);
+  pkeylen = crypto_pk_keysize(env);
+
+  if (fromlen <= pkeylen) {
+    return crypto_pk_private_decrypt(env,from,fromlen,to,padding);
+  }
+  outlen = crypto_pk_private_decrypt(env,from,pkeylen,buf,padding);
+  if (outlen<0) {
+    log_fn(LOG_WARN, "Error decrypting public-key data");
+    return -1;
+  }
+  if (outlen < 16) {
+    log_fn(LOG_WARN, "No room for a symmetric key");
+    return -1;
+  }
+  cipher = crypto_create_init_cipher(CRYPTO_CIPHER_AES_CTR, buf, "", 0);
+  if (!cipher) {
+    return -1;
+  }
+  memcpy(to,buf+16,outlen-16);
+  outlen -= 16;
+  r = crypto_cipher_decrypt(cipher, from+pkeylen, fromlen-pkeylen,
+			    to+outlen);
+  if (r<0)
+    goto err;
+  memset(buf,0,1024);
+  crypto_free_cipher_env(cipher);
+  return outlen + (fromlen-pkeylen);
+ err:
+  memset(buf, 0, 1024);
+  if (cipher) crypto_free_cipher_env(cipher);
+  return -1;
+}
+
 /* Encode the public portion of 'pk' into 'dest'.  Return -1 on error,
  * or the number of characters used on success.
  */

+ 7 - 0
src/common/crypto.h

@@ -59,6 +59,13 @@ int crypto_pk_public_encrypt(crypto_pk_env_t *env, unsigned char *from, int from
 int crypto_pk_private_decrypt(crypto_pk_env_t *env, unsigned char *from, int fromlen, unsigned char *to, int padding);
 int crypto_pk_private_sign(crypto_pk_env_t *env, unsigned char *from, int fromlen, unsigned char *to);
 int crypto_pk_public_checksig(crypto_pk_env_t *env, unsigned char *from, int fromlen, unsigned char *to);
+int crypto_pk_public_hybrid_encrypt(crypto_pk_env_t *env, unsigned char *from,
+				    int fromlen, unsigned char *to,
+				    int padding);
+int crypto_pk_private_hybrid_decrypt(crypto_pk_env_t *env, unsigned char *from,
+				    int fromlen, unsigned char *to,
+				    int padding);
+
 #define FINGERPRINT_LEN 49
 int crypto_pk_asn1_encode(crypto_pk_env_t *pk, char *dest, int dest_len);
 crypto_pk_env_t *crypto_pk_asn1_decode(const char *str, int len);

+ 18 - 1
src/or/test.c

@@ -242,7 +242,7 @@ test_crypto()
   crypto_pk_env_t *pk1, *pk2;
   char *data1, *data2, *data3, *cp;
   FILE *f;
-  int i, j;
+  int i, j, p, len;
   int str_ciphers[] = { CRYPTO_CIPHER_IDENTITY,
                         CRYPTO_CIPHER_DES,
                         CRYPTO_CIPHER_RC4,
@@ -416,6 +416,23 @@ test_crypto()
   pk2 = crypto_pk_asn1_decode(data1, i);
   test_assert(crypto_pk_cmp_keys(pk1,pk2) == 0);
 
+  /* Try with hybrid encryption wrappers. */
+  crypto_rand(1024, data1);
+  for (i = 0; i < 3; ++i) {
+    for (j = 85; j < 140; ++j) {
+      memset(data2,0,1024);
+      memset(data3,0,1024);
+      if (i == 0 && j < 129)
+        continue;
+      p = (i==0)?RSA_NO_PADDING:
+        (i==1)?RSA_PKCS1_PADDING:RSA_PKCS1_OAEP_PADDING;
+      len = crypto_pk_public_hybrid_encrypt(pk1,data1,j,data2,p);
+      test_assert(len>=0);
+      len = crypto_pk_private_hybrid_decrypt(pk1,data2,len,data3,p);
+      test_eq(len,j);
+      test_memeq(data1,data3,j);
+    }
+  }
   crypto_free_pk_env(pk1);
   crypto_free_pk_env(pk2);