├── .gitignore ├── LICENSE ├── README.md ├── src ├── crypto_cpace.c └── crypto_cpace.h └── test └── test.c /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2020-2021, Frank Denis 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CPace-Ristretto255, a balanced PAKE 2 | 3 | A CPace implementation for libsodium 1.0.17+ 4 | 5 | ## Blurb 6 | 7 | [CPace](https://tools.ietf.org/id/draft-haase-cpace-01.html) is a protocol for two parties that share a low-entropy secret (password) to derive a strong shared key without disclosing the secret to offline dictionary attacks. 8 | 9 | CPace is a balanced PAKE, meaning that both parties must know the low-entropy secret. 10 | 11 | Applications include pairing IoT and mobile applications using ephemeral pin codes, QR-codes, serial numbers, etc. 12 | 13 | ## Usage 14 | 15 | The CPace protocol requires a single round trip. 16 | 17 | It returns a set of two 256-bit (`crypto_cpace_SHAREDKEYBYTES` bytes) keys that can be used to communicate in both directions. 18 | 19 | ```c 20 | #include "crypto_cpace.h" 21 | 22 | #include 23 | 24 | /* A client identifier (username, email address, public key...) */ 25 | #define CLIENT_ID "client" 26 | 27 | /* A server identifier (IP address, host name, public key...) */ 28 | #define SERVER_ID "server" 29 | 30 | /* The shared password */ 31 | #define PASSWORD "password" 32 | 33 | /* Optional additional data (application name, version, time stamp...) */ 34 | #define ADDITIONAL_DATA "additional data" 35 | 36 | int 37 | main(void) 38 | { 39 | crypto_cpace_state ctx; 40 | crypto_cpace_shared_keys shared_keys_computed_by_client; 41 | crypto_cpace_shared_keys shared_keys_computed_by_server; 42 | unsigned char public_data[crypto_cpace_PUBLICDATABYTES]; 43 | unsigned char response[crypto_cpace_RESPONSEBYTES]; 44 | char hex[crypto_cpace_SHAREDKEYBYTES * 2 + 1]; 45 | 46 | /* [BOTH SIDES] Initialize the library - This just calls sodium_init() */ 47 | if (crypto_cpace_init() != 0) { 48 | return 1; 49 | } 50 | 51 | /* [CLIENT SIDE] Compute public data to be sent to the server */ 52 | if (crypto_cpace_step1(&ctx, public_data, PASSWORD, sizeof PASSWORD - 1, 53 | CLIENT_ID, sizeof CLIENT_ID - 1, SERVER_ID, 54 | sizeof SERVER_ID, 55 | (const unsigned char *) ADDITIONAL_DATA, 56 | sizeof ADDITIONAL_DATA) != 0) { 57 | return 1; 58 | } 59 | 60 | /* [SERVER SIDE] Compute the shared keys using the public data, 61 | * and return a response to send back to the client. 62 | */ 63 | if (crypto_cpace_step2( 64 | response, public_data, &shared_keys_computed_by_server, PASSWORD, 65 | sizeof PASSWORD - 1, CLIENT_ID, sizeof CLIENT_ID - 1, SERVER_ID, 66 | sizeof SERVER_ID, (const unsigned char *) ADDITIONAL_DATA, 67 | sizeof ADDITIONAL_DATA) != 0) { 68 | return 1; 69 | } 70 | 71 | /* [CLIENT SIDE] Compute the shared keys using the server response */ 72 | if (crypto_cpace_step3(&ctx, &shared_keys_computed_by_client, response) != 73 | 0) { 74 | return 1; 75 | } 76 | 77 | /* Verification */ 78 | 79 | printf("Client key computed by the client: %s\n", 80 | sodium_bin2hex(hex, sizeof hex, 81 | shared_keys_computed_by_client.client_sk, 82 | crypto_cpace_SHAREDKEYBYTES)); 83 | printf("Client key computed by the server: %s\n", 84 | sodium_bin2hex(hex, sizeof hex, 85 | shared_keys_computed_by_server.client_sk, 86 | crypto_cpace_SHAREDKEYBYTES)); 87 | printf("Server key computed by the client: %s\n", 88 | sodium_bin2hex(hex, sizeof hex, 89 | shared_keys_computed_by_client.server_sk, 90 | crypto_cpace_SHAREDKEYBYTES)); 91 | printf("Server key computed by the server: %s\n", 92 | sodium_bin2hex(hex, sizeof hex, 93 | shared_keys_computed_by_server.server_sk, 94 | crypto_cpace_SHAREDKEYBYTES)); 95 | return 0; 96 | } 97 | ``` 98 | 99 | ## Notes 100 | 101 | - This implementation uses the Ristretto255 group and SHA-512 as the hash function, so that it can trivially be ported to [wasm-crypto](https://github.com/jedisct1/wasm-crypto). 102 | - Client and server identifiers have a maximum size of 255 bytes. 103 | - A Rust version is available [here](https://github.com/jedisct1/rust-cpace). 104 | -------------------------------------------------------------------------------- /src/crypto_cpace.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "crypto_cpace.h" 5 | 6 | #define DSI1 "CPaceRistretto255-1" 7 | #define DSI2 "CPaceRistretto255-2" 8 | #define session_id_BYTES 16 9 | #define hash_BLOCKSIZE 128 10 | 11 | #define COMPILER_ASSERT(X) (void) sizeof(char[(X) ? 1 : -1]) 12 | 13 | static int 14 | ctx_init(crypto_cpace_state *const ctx, const char *password, 15 | size_t password_len, const char *id_a, unsigned char id_a_len, 16 | const char *id_b, unsigned char id_b_len, const unsigned char *ad, 17 | size_t ad_len) 18 | { 19 | crypto_hash_sha512_state st; 20 | unsigned char h[crypto_core_ristretto255_HASHBYTES]; 21 | static unsigned char zpad[hash_BLOCKSIZE]; 22 | const size_t dsi_len = sizeof DSI1 - 1U; 23 | size_t pad_len; 24 | 25 | COMPILER_ASSERT(sizeof ctx->session_id == session_id_BYTES && 26 | sizeof ctx->p == crypto_scalarmult_ristretto255_BYTES && 27 | sizeof ctx->r == 28 | crypto_scalarmult_ristretto255_SCALARBYTES); 29 | crypto_hash_sha512_init(&st); 30 | crypto_hash_sha512_update(&st, (const unsigned char *) DSI1, dsi_len); 31 | crypto_hash_sha512_update(&st, (const unsigned char *) password, 32 | password_len); 33 | pad_len = sizeof zpad - (dsi_len + password_len) & (sizeof zpad - 1); 34 | crypto_hash_sha512_update(&st, zpad, pad_len); 35 | crypto_hash_sha512_update(&st, ctx->session_id, session_id_BYTES); 36 | crypto_hash_sha512_update(&st, &id_a_len, 1); 37 | crypto_hash_sha512_update(&st, (const unsigned char *) id_a, id_a_len); 38 | crypto_hash_sha512_update(&st, &id_b_len, 1); 39 | crypto_hash_sha512_update(&st, (const unsigned char *) id_b, id_b_len); 40 | crypto_hash_sha512_update(&st, ad, ad_len); 41 | COMPILER_ASSERT(crypto_core_ristretto255_HASHBYTES == 42 | crypto_hash_sha512_BYTES); 43 | crypto_hash_sha512_final(&st, h); 44 | 45 | crypto_core_ristretto255_from_hash(ctx->p, h); 46 | crypto_core_ristretto255_scalar_random(ctx->r); 47 | 48 | return crypto_scalarmult_ristretto255(ctx->p, ctx->r, ctx->p); 49 | } 50 | 51 | static int 52 | ctx_final(const crypto_cpace_state *ctx, crypto_cpace_shared_keys *shared_keys, 53 | const unsigned char op[crypto_scalarmult_ristretto255_BYTES], 54 | const unsigned char ya[crypto_scalarmult_ristretto255_BYTES], 55 | const unsigned char yb[crypto_scalarmult_ristretto255_BYTES]) 56 | { 57 | crypto_hash_sha512_state st; 58 | unsigned char p[crypto_scalarmult_ristretto255_BYTES]; 59 | unsigned char h[crypto_hash_sha512_BYTES]; 60 | 61 | /* crypto_scalarmult_*() rejects the identity element */ 62 | if (crypto_scalarmult_ristretto255(p, ctx->r, op) != 0) { 63 | return -1; 64 | } 65 | crypto_hash_sha512_init(&st); 66 | crypto_hash_sha512_update(&st, (const unsigned char *) DSI2, 67 | sizeof DSI2 - 1); 68 | crypto_hash_sha512_update(&st, ctx->session_id, session_id_BYTES); 69 | crypto_hash_sha512_update(&st, p, crypto_scalarmult_ristretto255_BYTES); 70 | crypto_hash_sha512_update(&st, ya, crypto_scalarmult_ristretto255_BYTES); 71 | crypto_hash_sha512_update(&st, yb, crypto_scalarmult_ristretto255_BYTES); 72 | crypto_hash_sha512_final(&st, h); 73 | COMPILER_ASSERT(sizeof h >= 2 * crypto_cpace_SHAREDKEYBYTES); 74 | memcpy(shared_keys->client_sk, h, crypto_cpace_SHAREDKEYBYTES); 75 | memcpy(shared_keys->server_sk, h + crypto_cpace_SHAREDKEYBYTES, 76 | crypto_cpace_SHAREDKEYBYTES); 77 | 78 | return 0; 79 | } 80 | 81 | int 82 | crypto_cpace_init(void) 83 | { 84 | COMPILER_ASSERT( 85 | crypto_cpace_RESPONSEBYTES == crypto_scalarmult_ristretto255_BYTES && 86 | crypto_cpace_PUBLICDATABYTES == 87 | session_id_BYTES + crypto_scalarmult_ristretto255_BYTES); 88 | 89 | return sodium_init(); 90 | } 91 | 92 | int 93 | crypto_cpace_step1(crypto_cpace_state *ctx, 94 | unsigned char public_data[crypto_cpace_PUBLICDATABYTES], 95 | const char *password, size_t password_len, const char *id_a, 96 | unsigned char id_a_len, const char *id_b, 97 | unsigned char id_b_len, const unsigned char *ad, 98 | size_t ad_len 99 | 100 | ) 101 | { 102 | randombytes_buf(ctx->session_id, session_id_BYTES); 103 | if (ctx_init(ctx, password, password_len, id_a, id_a_len, id_b, id_b_len, 104 | ad, ad_len) != 0) { 105 | return -1; 106 | } 107 | memcpy(public_data, ctx->session_id, session_id_BYTES); 108 | memcpy(public_data + session_id_BYTES, ctx->p, 109 | crypto_scalarmult_ristretto255_BYTES); 110 | 111 | return 0; 112 | } 113 | 114 | int 115 | crypto_cpace_step2( 116 | unsigned char response[crypto_cpace_RESPONSEBYTES], 117 | const unsigned char public_data[crypto_cpace_PUBLICDATABYTES], 118 | crypto_cpace_shared_keys *shared_keys, const char *password, 119 | size_t password_len, const char *id_a, unsigned char id_a_len, 120 | const char *id_b, unsigned char id_b_len, const unsigned char *ad, 121 | size_t ad_len) 122 | { 123 | crypto_cpace_state ctx; 124 | const unsigned char *ya = public_data + session_id_BYTES; 125 | 126 | memcpy(ctx.session_id, public_data, session_id_BYTES); 127 | if (ctx_init(&ctx, password, password_len, id_a, id_a_len, id_b, id_b_len, 128 | ad, ad_len) != 0) { 129 | return -1; 130 | } 131 | memcpy(response, ctx.p, crypto_scalarmult_ristretto255_BYTES); 132 | if (ctx_final(&ctx, shared_keys, ya, ya, response) != 0) { 133 | return -1; 134 | } 135 | sodium_memzero(&ctx, sizeof ctx); 136 | 137 | return 0; 138 | } 139 | 140 | int 141 | crypto_cpace_step3(crypto_cpace_state * ctx, 142 | crypto_cpace_shared_keys *shared_keys, 143 | const unsigned char response[crypto_cpace_RESPONSEBYTES]) 144 | { 145 | return ctx_final(ctx, shared_keys, response, ctx->p, response); 146 | } 147 | -------------------------------------------------------------------------------- /src/crypto_cpace.h: -------------------------------------------------------------------------------- 1 | #ifndef crypto_cpace_H 2 | #define crypto_cpace_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | #define crypto_cpace_PUBLICDATABYTES (16 + 32) 11 | #define crypto_cpace_RESPONSEBYTES 32 12 | #define crypto_cpace_SHAREDKEYBYTES 32 13 | 14 | typedef struct crypto_cpace_shared_keys_ { 15 | unsigned char client_sk[crypto_cpace_SHAREDKEYBYTES]; 16 | unsigned char server_sk[crypto_cpace_SHAREDKEYBYTES]; 17 | } crypto_cpace_shared_keys; 18 | 19 | typedef struct crypto_cpace_state_ { 20 | unsigned char session_id[16]; 21 | unsigned char p[32]; 22 | unsigned char r[32]; 23 | } crypto_cpace_state; 24 | 25 | int crypto_cpace_init(void); 26 | 27 | int crypto_cpace_step1(crypto_cpace_state *ctx, 28 | unsigned char public_data[crypto_cpace_PUBLICDATABYTES], 29 | const char *password, size_t password_len, 30 | const char *id_a, unsigned char id_a_len, 31 | const char *id_b, unsigned char id_b_len, 32 | const unsigned char *ad, size_t ad_len); 33 | 34 | int crypto_cpace_step2( 35 | unsigned char response[crypto_cpace_RESPONSEBYTES], 36 | const unsigned char public_data[crypto_cpace_PUBLICDATABYTES], 37 | crypto_cpace_shared_keys *shared_keys, const char *password, 38 | size_t password_len, const char *id_a, unsigned char id_a_len, 39 | const char *id_b, unsigned char id_b_len, const unsigned char *ad, 40 | size_t ad_len); 41 | 42 | int 43 | crypto_cpace_step3(crypto_cpace_state * ctx, 44 | crypto_cpace_shared_keys *shared_keys, 45 | const unsigned char response[crypto_cpace_RESPONSEBYTES]); 46 | 47 | #ifdef __cplusplus 48 | } 49 | #endif 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /test/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "crypto_cpace.h" 4 | 5 | /* A client identifier (username, email address, public key...) */ 6 | #define CLIENT_ID "client" 7 | 8 | /* A server identifier (IP address, host name, public key...) */ 9 | #define SERVER_ID "server" 10 | 11 | /* The shared password */ 12 | #define PASSWORD "password" 13 | 14 | /* Optional additional data (application name, version, time stamp...) */ 15 | #define ADDITIONAL_DATA "additional data" 16 | 17 | int 18 | main(void) 19 | { 20 | crypto_cpace_state ctx; 21 | crypto_cpace_shared_keys shared_keys_computed_by_client; 22 | crypto_cpace_shared_keys shared_keys_computed_by_server; 23 | unsigned char public_data[crypto_cpace_PUBLICDATABYTES]; 24 | unsigned char response[crypto_cpace_RESPONSEBYTES]; 25 | char hex[crypto_cpace_SHAREDKEYBYTES * 2 + 1]; 26 | 27 | /* [BOTH SIDES] Initialize the library - This just calls sodium_init() */ 28 | if (crypto_cpace_init() != 0) { 29 | return 1; 30 | } 31 | 32 | /* [CLIENT SIDE] Compute public data to be sent to the server */ 33 | if (crypto_cpace_step1(&ctx, public_data, PASSWORD, sizeof PASSWORD - 1, 34 | CLIENT_ID, sizeof CLIENT_ID - 1, SERVER_ID, 35 | sizeof SERVER_ID, 36 | (const unsigned char *) ADDITIONAL_DATA, 37 | sizeof ADDITIONAL_DATA) != 0) { 38 | return 1; 39 | } 40 | 41 | /* [SERVER SIDE] Compute the shared keys using the public data, 42 | * and return a response to send back to the client. 43 | */ 44 | if (crypto_cpace_step2( 45 | response, public_data, &shared_keys_computed_by_server, PASSWORD, 46 | sizeof PASSWORD - 1, CLIENT_ID, sizeof CLIENT_ID - 1, SERVER_ID, 47 | sizeof SERVER_ID, (const unsigned char *) ADDITIONAL_DATA, 48 | sizeof ADDITIONAL_DATA) != 0) { 49 | return 1; 50 | } 51 | 52 | /* [CLIENT SIDE] Compute the shared keys using the server response */ 53 | if (crypto_cpace_step3(&ctx, &shared_keys_computed_by_client, response) != 54 | 0) { 55 | return 1; 56 | } 57 | 58 | /* Verification */ 59 | 60 | printf("Client key computed by the client: %s\n", 61 | sodium_bin2hex(hex, sizeof hex, 62 | shared_keys_computed_by_client.client_sk, 63 | crypto_cpace_SHAREDKEYBYTES)); 64 | printf("Client key computed by the server: %s\n", 65 | sodium_bin2hex(hex, sizeof hex, 66 | shared_keys_computed_by_server.client_sk, 67 | crypto_cpace_SHAREDKEYBYTES)); 68 | printf("Server key computed by the client: %s\n", 69 | sodium_bin2hex(hex, sizeof hex, 70 | shared_keys_computed_by_client.server_sk, 71 | crypto_cpace_SHAREDKEYBYTES)); 72 | printf("Server key computed by the server: %s\n", 73 | sodium_bin2hex(hex, sizeof hex, 74 | shared_keys_computed_by_server.server_sk, 75 | crypto_cpace_SHAREDKEYBYTES)); 76 | return 0; 77 | } 78 | --------------------------------------------------------------------------------