Code Monkey home page Code Monkey logo

libsodium-signcryption's Introduction

signcryption using libsodium

An implementation of the Toorani-Beheshti signcryption scheme, instantiated over the Ristretto255 group.

Why

Traditional authenticated encryption with a shared key allows two or more parties to decrypt a ciphertext and verify that it was created by a member of the group knowing that secret key.

However, it doesn't allow verification of who in a group originally created a message.

In order to do so, authenticated encryption has to be combined with signatures.

The Toorani-Beheshti signcryption scheme achieves this using a single key pair per device, with forward security and public verifiability.

Parameter definitions

  • sender_id: an identifier for a sender. It may be the sender's public key but it doesn't have to. It can also be an account number, or anything that can uniquely identify a user. It doesn't need to be secret, nor have high entropy. A user can send messages from multiple devices, each with their own key pair, with sender_id remaining the same.
  • recipient_id: an identifier for the recipient of a message. It can represent a specific party, or, for a message sent to a group, a group identifier.
  • info: this describes the context in which a message was sent. Signature verification will fail if the context expected by the verifier doesn't match the one the signature was originally created for.
  • shared_key: a shared secret key, used for encryption. The scheme only generates shared secrets; applications are free to use them with the encryption system of their choice.

Algorithm summary

(a, A) = sender_sk, sender_pk
(b, B) = recipent_sk, recipient_pk

    r = H("nonce", sender_sk, recipient_pk, noise, plaintext)
    R = rG
    K = (Ra + r)B
    x = H("shared_key", K, sender_id, recipient_id, info)
    y = H("sign_key", R, sender_id, recipient_id, info, ciphertext)
    R = rG
    s = ya - r

signature = (R, s) = (rG, ya - r)

    K = b(RA + R)
    S = (sG + R)A

alternatively:

    S = A^2 + R

Source code

  • The src/tbsbr directory contains the main source code, with the scheme implemented using the BLAKE2b hash function and the Ristretto255 group. This is the recommended version.
  • As an alternative, the src/tbsbe directory contains a version using the standard edwards25519 encoding.

The API decription below assumes that the tbsbr version is being used, but both versions have the exact same API with a different prefix.

Key pair creation

void crypto_signcrypt_tbsbr_keygen(unsigned char pk[crypto_signcrypt_tbsbr_PUBLICKEYBYTES],
                                   unsigned char sk[crypto_signcrypt_tbsbr_SECRETKEYBYTES]);

Create a new key pair, putting the public key into pk and the secret into sk.

void crypto_signcrypt_tbsbr_seed_keygen(unsigned char pk[crypto_signcrypt_tbsbr_PUBLICKEYBYTES],
                                        unsigned char sk[crypto_signcrypt_tbsbr_SECRETKEYBYTES],
                                        const unsigned char seed[crypto_signcrypt_tbsbr_SEEDBYTES]);

Create a deterministic key pair from the seed seed.

Signcryption

These functions are called by the sender.

int crypto_signcrypt_tbsbr_sign_before(
    unsigned char st[crypto_signcrypt_tbsbr_STATEBYTES],
    unsigned char shared_key[crypto_signcrypt_tbsbr_SHAREDBYTES],
    const unsigned char *sender_id, size_t sender_id_len,
    const unsigned char *recipient_id, size_t recipient_id_len,
    const unsigned char *info, size_t info_len,
    const unsigned char sender_sk[crypto_signcrypt_tbsbr_SECRETKEYBYTES],
    const unsigned char recipient_pk[crypto_signcrypt_tbsbr_PUBLICKEYBYTES],
    const unsigned char *m, size_t m_len);

This function computes a shared key shared_key that can later be used to encrypt a message from sender_id to recipient_id in the info context.

m is the message to be encrypted, and m_len its size in bytes. On a system with a reliable secure random number generator, this is optional and m can be set to NULL, with m_len set to 0.

shared_key can then be used to encrypt the message with any authenticated encryption system.

st will contain the state, required for the sign_after step.

The function returns -1 or error, 0 on success.

int crypto_signcrypt_tbsbr_sign_after(
    unsigned char       st[crypto_signcrypt_tbsbr_STATEBYTES],
    unsigned char       sig[crypto_signcrypt_tbsbr_SIGNBYTES],
    const unsigned char sender_sk[crypto_signcrypt_tbsbr_SECRETKEYBYTES],
    const unsigned char *c, size_t c_len);

Once the message has been encrypted, it must be signed with this function. c is the ciphertext, and c_len its length.

The signature is stored into sig.

The function returns -1 or error, 0 on success.

A typical signcryption sequence is thus:

  1. crypto_signcrypt_tbsbr_sign_before()
  2. encrypt with shared_key
  3. crypto_signcrypt_tbsbr_sign_after()

Unsigncryption

The functions are called by the recipient.

int crypto_signcrypt_tbsbr_verify_before(
    unsigned char       st[crypto_signcrypt_tbsbr_STATEBYTES],
    unsigned char       shared_key[crypto_signcrypt_tbsbr_SHAREDBYTES],
    const unsigned char sig[crypto_signcrypt_tbsbr_SIGNBYTES],
    const unsigned char *sender_id, size_t sender_id_len,
    const unsigned char *recipient_id, size_t recipient_id_len,
    const unsigned char *info, size_t info_len,
    const unsigned char sender_pk[crypto_signcrypt_tbsbr_PUBLICKEYBYTES],
    const unsigned char recipient_sk[crypto_signcrypt_tbsbr_PUBLICKEYBYTES]);

This function creates a state st and recovers the encryption shared key shared_key from the signature sig, the message sender identifier sender_id, the recipient recipient_len, the context info, the sender's public key sender_pk and the recipent's secret key recipient_sk.

The shared key can then be used to decrypt the ciphertext.

The function returns -1 or error, 0 on success.

int crypto_signcrypt_tbsbr_verify_after(
    unsigned char       st[crypto_signcrypt_tbsbr_STATEBYTES],
    const unsigned char sig[crypto_signcrypt_tbsbr_SIGNBYTES],
    const unsigned char sender_pk[crypto_signcrypt_tbsbr_PUBLICKEYBYTES],
    const unsigned char *c, size_t c_len);

This function verifies that the signature sig is valid for the ciphertext c of length c_len bytes, the sender's public key sender_pk and the previously computed state st.

It returns -1 is the verification failed, and 0 if it succeeded.

A typical unsigncryption sequence is thus:

  1. crypto_signcrypt_tbsbr_verify_before()
  2. decrypt with shared_key
  3. crypto_signcrypt_tbsbr_verify_after() - The return of that function must be checked.

Public verification

The fact that a message was sent by a specific sender to a specific recipient in a specific context can also be publicly verified, without giving the ability to decrypt the ciphertext.

int crypto_signcrypt_tbsbr_verify_public(
    const unsigned char sig[crypto_signcrypt_tbsbr_SIGNBYTES],
    const unsigned char *sender_id, size_t sender_id_len,
    const unsigned char *recipient_id, size_t recipient_id_len,
    const unsigned char *info, size_t info_len,
    const unsigned char sender_pk[crypto_signcrypt_tbsbr_PUBLICKEYBYTES],
    const unsigned char *c, size_t c_len);

This function verifies that sig is a valid signature for the ciphertext c of length c_len bytes, the sender identifier sender_id, the recipient recipient_id, the context info, and the sender's public key sender_pk.

Constants

  • crypto_signcrypt_tbsbr_SECRETKEYBYTES = 32
  • crypto_signcrypt_tbsbr_PUBLICKEYBYTES = 32
  • crypto_signcrypt_tbsbr_SHAREDBYTES = 32
  • crypto_signcrypt_tbsbr_SEEDBYTES = 64
  • crypto_signcrypt_tbsbr_SIGNBYTES = 64
  • crypto_signcrypt_tbsbr_STATEBYTES = 512

Other implementations

References

  • A Directly Public Verifiable Signcryption Scheme based on Elliptic Curves [PDF] (M. Toorani, A. Beheshti).

libsodium-signcryption's People

Contributors

jedisct1 avatar michaeljclark avatar stsch9 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

libsodium-signcryption's Issues

A SignCryption Token Proposal

While adding libsodium-signcryption, libsodium-xchacha20-siv and support for helpers like sodium_bin2base64 to pgsodium it occurred to me that signcryption could be an excellent basis for a libsodium token format, so I typed up my thoughts for discussion. If this isn't the appropriate forum for discussion lmk and I'll move it.

SCT: Libsodium Sign-Cryption Token

A binary, encrypted, authenticated token format called a
Sign-Cryption Token aka SCT.

SCT version 0x0000

  • SCT requires libsodium, including the additional function
    libraries in libsodium-xchacha20-siv and
    libsodium-signcryption.

  • SCTs have an encrypted payload and unencrypted authenticated
    additional data payload. Either payload, but not both may be empty.

  • SCT does NOT support shared-secret encryption. All SCT token
    producers MUST have a keypair consisting of public and secret
    keys.

  • Token consumers without possession of the recipient secret key
    CANNOT decrypt the encrypted payload.

  • Token consumers with the public key of the token producer can
    verify the authenticity of the unencrypted portion of a token.

  • SCT gives you no choice of cryptographic algorithm. SCTs MUST use
    XChaCha20-SIV combined mode deterministic authenticated
    encryption with additional data using
    crypto_aead_det_xchacha20_*.

  • The shared_key used for encryption MUST be generated by the
    crypto_signcrypt_tbsbr_sign_* API using the sender's secret key
    and the recipient's public key. The recipient can then recover
    this key using crypto_signcrypt_tbsbr_verify_*.

  • SCT does NOT care about encodings such as JSON or any kind of human
    readability. The input payloads, sender/recipient ids, and key
    pairs are binary byte arrays and the output is a base64 character
    array. Encoding payloads to and from formats like JSON to a byte
    representation is a trivial operation and entirely up to the user.

  • The output of SCT is a byte string of four encoded base64 sections:

    <version>.<sender_id>.<ciphertext>.<context>.<signature>

    The function sodium_bin2base64 can be used to encode using the
    sodium_base64_VARIANT_URLSAFE_NO_PADDING variant.

Example function with pgsodium

This is a PostgreSQL function that generates a token:

CREATE OR REPLACE FUNCTION crypto_signcrypt_token(
    sender       bytea,
    recipient    bytea,
    sender_sk    bytea,
    recipient_pk bytea,
    message      bytea,
    additional   bytea)
RETURNS text AS $$
WITH
    sign_before AS (
        SELECT state, shared_key
        FROM crypto_signcrypt_sign_before(
            sender,
            recipient,
            sender_sk,
            recipient_pk,
            additional)
    ),
    ciphertext AS (
        SELECT crypto_aead_det_encrypt(
            message,
            additional,
            b.shared_key
        ) AS ciphertext
        FROM sign_before b
    ),
    signature AS (
        SELECT crypto_signcrypt_sign_after(
            b.state,
            sender_sk,
            c.ciphertext
        ) AS signature
        FROM
            sign_before b,
            ciphertext c
    )
    SELECT format(
        '0000.%s.%s.%s.%s',
        sodium_bin2base64(sender),
        sodium_bin2base64(c.ciphertext),
        sodium_bin2base64(additional),
        sodium_bin2base64(s.signature))
    FROM
        ciphertext c,
        signature s;
$$ LANGUAGE SQL STRICT;

Here's an example of a token generated from keypairs. First create
keypairs for bob and alice. Typically this would be done in
separate edge processes but are shown together here for brevity:

postgres=# select public as pk, secret as sk from crypto_signcrypt_new_keypair () \gset bob_
postgres=# select public as pk, secret as sk from crypto_signcrypt_new_keypair () \gset alice_

Now generate a token from bob to alice. In this exasmple the sender
and recipient ids are corresponding public keys, but this is not
necessary, the id can be any unique identifier for a user or group:

postgres=# select crypto_signcrypt_token(:'bob_pk', :'alice_pk', :'bob_sk', :'alice_pk', 'this is encrypted s3kret message', 'this is unencrpyted additional data');

0000.YvWFDwWg1sBVhwnccoMwDbvw7PzE0SeHvj3g7fDhqBQ.XnPzbou0Rr-NahE3nEGW6EC5QAFvT11iQzAFHu9NjOksdzV61fuftjDfLgU_vZp7IMAfryeoUAGlQCP7h4RM5g.dGhpcyBpcyB1bmVuY3JweXRlZCBhZGRpdGlvbmFsIGRhdGE.qS1slA8qW4J_uKO079VlzKC5BUazG1W67TVuYCqRKgY8CHybwfgho5U_LNGQTZ60nkDxfU4Q9U3o2w2BAwAAAA

Rational and Comparisons

"Perfection is achieved, not when there is nothing more to add, but
when there is nothing left to take away." - Antoine de Saint Exupery

JWT is bad. There are endless blog posts on the badness of JWT. One
of the big baddnesses is that the token specifies the encryption
algorithm in its header, and a baddie can influence the decrypting of
the token by choosing the algorithm. This is called an algorithm
confusion attack.

PASETO is better in many ways, for example by specifying specific
versions, but it also introduced the same flaw that it was meant to
fix with JWT, an attacker can choose either local or public
token "purposes" in an attempt to fool the server with the same kind
of algorithm confusion attacks.

So, PASETO version 3 and 4 put a new section in their spec about
"Algorithm Lucidity" and how every language MUST use whatever type
enforcement features exist to safeguard against using the wrong key
for the wrong purpose.

SCT does not allow algorithm confusion attacks as there is one and
only one algorithm that can cover either or both purposes that local
and public serve in PASETO. SCT does NOT support shared secret
encryption, and so there is no possibility of Algorithm Lucidity
attacks being an issue. All SCT producers MUST have a valid key pair.

PASETO's current two "purposes", local and public are quite
different, local uses shared secret encryption and its payload is
encrypted and authenticated. public does NO ENCRYPTION and just
authenticates its payload data. These two very different purposes do
not overlap.

SCT has combines both purposes using an algorithm called
signcryption. A token can have encrypted payload, in which case the
the decryptor must have the recipient secret key. But a token can
also be authenticated by anyone with the sender's public key. They
can't decrypt the encrypted payload, but the can verify the token is
authentic including the unencrypted additional data.

So now, instead of one token spec with two distinct purposes, and
rules about algorithm lucidity and type checking, etc, SCT has one and
only one token format. The token can contain an authenticated
encrypted payload, and/or it can authenticated unencrypted additional
data. If you have the recipient's secret key, you can decrypt it, but
anyone with the senders public key can authenticate it.

One last thing I personally don't like about JWT and PASETO is their
focus on JSON payloads. SCT payloads are bytes. If you want to
encode your JSON into bytes, go for it, every language in the world
can trivially do that. But JSON is not required.

Question about verification

I have a question: Only the ciphertext and not the plaintext is used for verification. So if the verification fails, the ciphertext does not have to be decrypted at all. This is an advantage over signcryption schemes, which are described in https://www.itiis.org/digital-library/manuscript/2298, for example. Would the following sequence therefore make sense?

  1. crypto_signcrypt_tbsbr_verify_before()
  2. crypto_signcrypt_tbsbr_verify_after() - The return of that function must be checked.
  3. decrypt with shared_key

typo?

In signcrypt_tbsbr.h
line 11
should this read attribute ?

#if !defined(__clang__) && !defined(__GNUC__) && !defined(__attribute__)
#define __atribute__(X)
#endif

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.