From a112296850384f33e36c8904136771b6a2ba44c0 Mon Sep 17 00:00:00 2001 From: Johny Mattsson Date: Sat, 17 Sep 2016 01:46:39 +1000 Subject: [PATCH] Added crypto.new_hmac() feature. (#1499) Docs for crypto module refactored for easier maintenance. --- app/crypto/digests.c | 55 +++++++++++++++++++----------- app/crypto/digests.h | 30 ++++++++++++++++- app/modules/crypto.c | 61 +++++++++++++++++++++++++--------- docs/en/modules/crypto.md | 70 ++++++++++++++++++++------------------- 4 files changed, 147 insertions(+), 69 deletions(-) diff --git a/app/crypto/digests.c b/app/crypto/digests.c index 22531ae8..a81f8a31 100644 --- a/app/crypto/digests.c +++ b/app/crypto/digests.c @@ -156,31 +156,22 @@ int ICACHE_FLASH_ATTR crypto_fhash (const digest_mech_info_t *mi, } -int ICACHE_FLASH_ATTR crypto_hmac (const digest_mech_info_t *mi, - const char *data, size_t data_len, - const char *key, size_t key_len, - uint8_t *digest) +void crypto_hmac_begin (void *ctx, const digest_mech_info_t *mi, + const char *key, size_t key_len, uint8_t *k_opad) { - if (!mi) - return EINVAL; - - void *ctx = (void *)os_malloc (mi->ctx_size); - if (!ctx) - return ENOMEM; - // If key too long, it needs to be hashed before use + char tmp[mi->digest_size]; if (key_len > mi->block_size) { - mi->create (ctx); mi->update (ctx, key, key_len); - mi->finalize (digest, ctx); - key = digest; + mi->finalize (tmp, ctx); + key = tmp; key_len = mi->digest_size; + mi->create (ctx); // refresh } const size_t bs = mi->block_size; uint8_t k_ipad[bs]; - uint8_t k_opad[bs]; os_memset (k_ipad, 0x36, bs); os_memset (k_opad, 0x5c, bs); @@ -191,16 +182,42 @@ int ICACHE_FLASH_ATTR crypto_hmac (const digest_mech_info_t *mi, k_opad[i] ^= key[i]; } - mi->create (ctx); mi->update (ctx, k_ipad, bs); - mi->update (ctx, data, data_len); +} + + +void crypto_hmac_finalize (void *ctx, const digest_mech_info_t *mi, + const uint8_t *k_opad, uint8_t *digest) +{ mi->finalize (digest, ctx); mi->create (ctx); - mi->update (ctx, k_opad, bs); + mi->update (ctx, k_opad, mi->block_size); mi->update (ctx, digest, mi->digest_size); mi->finalize (digest, ctx); +} - os_free (ctx); + +int crypto_hmac (const digest_mech_info_t *mi, + const char *data, size_t data_len, + const char *key, size_t key_len, + uint8_t *digest) +{ + if (!mi) + return EINVAL; + + struct { + uint8_t ctx[mi->ctx_size]; + uint8_t k_opad[mi->block_size]; + } *tmp = os_malloc (sizeof (*tmp)); + if (!tmp) + return ENOMEM; + + mi->create (tmp->ctx); + crypto_hmac_begin (tmp->ctx, mi, key, key_len, tmp->k_opad); + mi->update (tmp->ctx, data, data_len); + crypto_hmac_finalize (tmp->ctx, mi, tmp->k_opad, digest); + + os_free (tmp); return 0; } diff --git a/app/crypto/digests.h b/app/crypto/digests.h index c63364d9..93d802bb 100644 --- a/app/crypto/digests.h +++ b/app/crypto/digests.h @@ -66,7 +66,35 @@ int crypto_hash (const digest_mech_info_t *mi, const char *data, size_t data_len int crypto_fhash (const digest_mech_info_t *mi, read_fn read, int readarg, uint8_t *digest); /** - * Generate a HMAC signature. + * Commence calculating a HMAC signature. + * + * Once this fuction returns successfully, the @c ctx may be updated with + * data in multiple passes uses @c mi->update(), before being finalized via + * @c crypto_hmac_finalize(). + * + * @param ctx A created context for the given mech @c mi. + * @param mi A mech from @c crypto_digest_mech(). + * @param key The key bytes to use. + * @param key_len Number of bytes the @c key comprises. + * @param k_opad Buffer of size @c mi->block_size bytes to store the second + * round of key material in. + * Must be passed to @c crypto_hmac_finalize(). + */ +void crypto_hmac_begin (void *ctx, const digest_mech_info_t *mi, const char *key, size_t key_len, uint8_t *k_opad); + +/** + * Finalizes calculating a HMAC signature. + * @param ctx The created context used with @c crypto_hmac_begin(). + * @param mi The mech used with @c crypto_hmac_begin(). + * @param k_opad Second round of keying material, obtained + * from @c crypto_hmac_begin(). + * @param digest Output buffer, must be at least @c mi->digest_size in size. + */ +void crypto_hmac_finalize (void *ctx, const digest_mech_info_t *mi, const uint8_t *k_opad, uint8_t *digest); + +/** + * Generate a HMAC signature in one pass. + * Implemented in terms of @c crypto_hmac_begin() / @c crypto_hmac_end(). * @param mi A mech from @c crypto_digest_mech(). A null pointer @c mi * is harmless, but will of course result in an error return. * @param data The data to generate a signature for. diff --git a/app/modules/crypto.c b/app/modules/crypto.c index aa85a746..89a3b832 100644 --- a/app/modules/crypto.c +++ b/app/modules/crypto.c @@ -9,11 +9,18 @@ #include "vfs.h" #include "../crypto/digests.h" #include "../crypto/mech.h" +#include "lmem.h" #include "user_interface.h" #include "rom.h" +typedef struct { + const digest_mech_info_t *mech_info; + void *ctx; + uint8_t *k_opad; +} digest_user_datum_t; + /** * hash = crypto.sha1(input) * @@ -150,11 +157,6 @@ static int crypto_lhash (lua_State *L) return 1; } -typedef struct digest_user_datum_t { - const digest_mech_info_t *mech_info; - void *ctx; -} digest_user_datum_t; - /* General Usage for extensible hash functions: * sha = crypto.new_hash("MD5") * sha.update("Data") @@ -162,19 +164,30 @@ typedef struct digest_user_datum_t { * strdigest = crypto.toHex(sha.finalize()) */ -/* crypto.new_hash("MECHTYPE") */ -static int crypto_new_hash (lua_State *L) +#define WANT_HASH 0 +#define WANT_HMAC 1 + +static int crypto_new_hash_hmac (lua_State *L, int what) { const digest_mech_info_t *mi = crypto_digest_mech (luaL_checkstring (L, 1)); if (!mi) return bad_mech (L); - void *ctx = os_malloc (mi->ctx_size); - if (ctx==NULL) - return bad_mem (L); + size_t len = 0; + const char *key = 0; + uint8_t *k_opad = 0; + if (what == WANT_HMAC) + { + key = luaL_checklstring (L, 2, &len); + k_opad = luaM_malloc (L, mi->block_size); + } + void *ctx = luaM_malloc (L, mi->ctx_size); mi->create (ctx); + if (what == WANT_HMAC) + crypto_hmac_begin (ctx, mi, key, len, k_opad); + // create a userdataum with specific metatable digest_user_datum_t *dudat = (digest_user_datum_t *)lua_newuserdata(L, sizeof(digest_user_datum_t)); luaL_getmetatable(L, "crypto.hash"); @@ -183,10 +196,24 @@ static int crypto_new_hash (lua_State *L) // Set pointers to the mechanics and CTX dudat->mech_info = mi; dudat->ctx = ctx; + dudat->k_opad = k_opad; return 1; // Pass userdata object back } +/* crypto.new_hash("MECHTYPE") */ +static int crypto_new_hash (lua_State *L) +{ + return crypto_new_hash_hmac (L, WANT_HASH); +} + +/* crypto.new_hmac("MECHTYPE", "KEY") */ +static int crypto_new_hmac (lua_State *L) +{ + return crypto_new_hash_hmac (L, WANT_HMAC); +} + + /* Called as object, params: 1 - userdata "this" 2 - new string to add to the hash state */ @@ -197,7 +224,6 @@ static int crypto_hash_update (lua_State *L) size_t sl; dudat = (digest_user_datum_t *)luaL_checkudata(L, 1, "crypto.hash"); - luaL_argcheck(L, dudat, 1, "crypto.hash expected"); const digest_mech_info_t *mi = dudat->mech_info; @@ -217,12 +243,14 @@ static int crypto_hash_finalize (lua_State *L) size_t sl; dudat = (digest_user_datum_t *)luaL_checkudata(L, 1, "crypto.hash"); - luaL_argcheck(L, dudat, 1, "crypto.hash expected"); const digest_mech_info_t *mi = dudat->mech_info; uint8_t digest[mi->digest_size]; // Allocate as local - mi->finalize (digest, dudat->ctx); + if (dudat->k_opad) + crypto_hmac_finalize (dudat->ctx, mi, dudat->k_opad, digest); + else + mi->finalize (digest, dudat->ctx); lua_pushlstring (L, digest, sizeof (digest)); return 1; @@ -235,9 +263,11 @@ static int crypto_hash_gcdelete (lua_State *L) digest_user_datum_t *dudat; dudat = (digest_user_datum_t *)luaL_checkudata(L, 1, "crypto.hash"); - luaL_argcheck(L, dudat, 1, "crypto.hash expected"); - os_free(dudat->ctx); + // luaM_free() uses type info to obtain original size, so have to delve + // one level deeper and explicitly pass the size due to void* + luaM_realloc_ (L, dudat->ctx, dudat->mech_info->ctx_size, 0); + luaM_free (L, dudat->k_opad); return 0; } @@ -386,6 +416,7 @@ static const LUA_REG_TYPE crypto_map[] = { { LSTRKEY( "fhash" ), LFUNCVAL( crypto_flhash ) }, { LSTRKEY( "new_hash" ), LFUNCVAL( crypto_new_hash ) }, { LSTRKEY( "hmac" ), LFUNCVAL( crypto_lhmac ) }, + { LSTRKEY( "new_hmac" ), LFUNCVAL( crypto_new_hmac ) }, { LSTRKEY( "encrypt" ), LFUNCVAL( lcrypto_encrypt ) }, { LSTRKEY( "decrypt" ), LFUNCVAL( lcrypto_decrypt ) }, { LNILKEY, LNILVAL } diff --git a/docs/en/modules/crypto.md b/docs/en/modules/crypto.md index 80f43843..5ad210e9 100644 --- a/docs/en/modules/crypto.md +++ b/docs/en/modules/crypto.md @@ -5,6 +5,16 @@ The crypto modules provides various functions for working with cryptographic algorithms. +The following encryption/decryption algorithms/modes are supported: +- `"AES-ECB"` for 128-bit AES in ECB mode (NOT recommended) +- `"AES-CBC"` for 128-bit AES in CBC mode + +The following hash algorithms are supported: +- MD2 (not available by default, has to be explicitly enabled in `app/include/user_config.h`) +- MD5 +- SHA1 +- SHA256, SHA384, SHA512 (unless disabled in `app/include/user_config.h`) + ## crypto.encrypt() Encrypts Lua strings. @@ -13,9 +23,7 @@ Encrypts Lua strings. `crypto.encrypt(algo, key, plain [, iv])` #### Parameters - - `algo` the name of the encryption algorithm to use, one of - - `"AES-ECB"` for 128-bit AES in ECB mode - - `"AES-CBC"` for 128-bit AES in CBC mode + - `algo` the name of a supported encryption algorithm to use - `key` the encryption key as a string; for AES encryption this *MUST* be 16 bytes long - `plain` the string to encrypt; it will be automatically zero-padded to a 16-byte boundary if necessary - `iv` the initilization vector, if using AES-CBC; defaults to all-zero if not given @@ -40,9 +48,7 @@ Decrypts previously encrypted data. `crypto.decrypt(algo, key, cipher [, iv])` #### Parameters - - `algo` the name of the encryption algorithm to use, one of - - `"AES-ECB"` for 128-bit AES in ECB mode - - `"AES-CBC"` for 128-bit AES in CBC mode + - `algo` the name of a supported encryption algorithm to use - `key` the encryption key as a string; for AES encryption this *MUST* be 16 bytes long - `cipher` the cipher text to decrypt (as obtained from `crypto.encrypt()`) - `iv` the initilization vector, if using AES-CBC; defaults to all-zero if not given @@ -75,13 +81,6 @@ Compute a cryptographic hash of a a file. - `algo` the hash algorithm to use, case insensitive string - `filename` the path to the file to hash -Supported hash algorithms are: - -- MD2 (not available by default, has to be explicitly enabled in `app/include/user_config.h`) -- MD5 -- SHA1 -- SHA256, SHA384, SHA512 (unless disabled in `app/include/user_config.h`) - #### Returns A binary string containing the message digest. To obtain the textual version (ASCII hex characters), please use [`crypto.toHex()`](#cryptotohex ). @@ -101,13 +100,6 @@ Compute a cryptographic hash of a Lua string. `algo` the hash algorithm to use, case insensitive string `str` string to hash contents of -Supported hash algorithms are: - -- MD2 (not available by default, has to be explicitly enabled in `app/include/user_config.h`) -- MD5 -- SHA1 -- SHA256, SHA384, SHA512 (unless disabled in `app/include/user_config.h`) - #### Returns A binary string containing the message digest. To obtain the textual version (ASCII hex characters), please use [`crypto.toHex()`](#cryptotohex ). @@ -126,13 +118,6 @@ Create a digest/hash object that can have any number of strings added to it. Obj #### Parameters `algo` the hash algorithm to use, case insensitive string -Supported hash algorithms are: - -- MD2 (not available by default, has to be explicitly enabled in `app/include/user_config.h`) -- MD5 -- SHA1 -- SHA256, SHA384, SHA512 (unless disabled in `app/include/user_config.h`) - #### Returns Userdata object with `update` and `finalize` functions available. @@ -157,13 +142,6 @@ Compute a [HMAC](https://en.wikipedia.org/wiki/Hash-based_message_authentication - `str` data to calculate the hash for - `key` key to use for signing, may be a binary string -Supported hash algorithms are: - -- MD2 (not available by default, has to be explicitly enabled in `app/include/user_config.h`) -- MD5 -- SHA1 -- SHA256, SHA384, SHA512 (unless disabled in `app/include/user_config.h`) - #### Returns A binary string containing the HMAC signature. Use [`crypto.toHex()`](#cryptotohex ) to obtain the textual version. @@ -172,6 +150,30 @@ A binary string containing the HMAC signature. Use [`crypto.toHex()`](#cryptotoh print(crypto.toHex(crypto.hmac("sha1","abc","mysecret"))) ``` +## crypto.new_hmac() + +Create a hmac object that can have any number of strings added to it. Object has `update` and `finalize` functions. + +#### Syntax +`hmacobj = crypto.new_hmac(algo, key)` + +#### Parameters +- `algo` the hash algorithm to use, case insensitive string +- `key` the key to use (may be a binary string) + +#### Returns +Userdata object with `update` and `finalize` functions available. + +#### Example +```lua +hmacobj = crypto.new_hmac("SHA1", "s3kr3t") +hmacobj:update("FirstString")) +hmacobj:update("SecondString")) +digest = hmacobj:finalize() +print(crypto.toHex(digest)) +``` + + ## crypto.mask() Applies an XOR mask to a Lua string. Note that this is not a proper cryptographic mechanism, but some protocols may use it nevertheless.