javapublic-key-encryptionprivate-keydeterministictink

Hybrid Public Key Encryption (HPKE) with deterministically generated key pairs using Tink


I would like to use Google Tink for public key encryption / private key decryption, mostly because it's high-level enough and is available in a variety of programming languages (e.g. from Android and IOS).

I chose keys of type DHKEM_X25519_HKDF_SHA256_HKDF_SHA256_AES_256_GCM which use X25519 Elliptic Curve Cryptography, because they are:

I need those key to be generated deterministically, for disaster recovery. There are other layers of security, and my main concern is data loss.

Private key generation is under control (!). I think public key are generated by Tink on-the-fly, as it's a simple arithmetical operation given the private key. However, I can't create a HpkePrivateKey in java without providing both public and private keys (and using an API in alpha).

I also tried to hack my way into importing hand-made JSON with JsonKeysetReader, but I get not-so-verbose errors about my key data not matching the expected ProtoBuf format. I'd go for it (at least temporarily) if I could make it work, but honestly I'd feel better implementing something that doesn't feel so much like a hack.

I'm struggling to find a solution to implement this. Any idea on how to import/generate a KeysetHandle containing a valid HPKE key pair, given a raw 32-byte private key?


Solution

  • In cryptography, if you generate or derive a key from "something", that something is the key. That's because the algorithm is deterministic. It does not have to be, as you could generate a key by sampling noise around you. But in your case, it seems that you want to generate a key from user input.

    You'll notice that I haven't mentioned symmetric or asymmetric cryptography yet.

    Let's say that user input is a password (better: a passphrase). You said that you want to provide encrypted file sharing, so asymmetric cryptography comes naturally. But generating a key pair involves a good source of entropy, or randomness.

    So to enable encrypted file sharing, where a server will not be able to decrypt the files shared between users, you could do the following:

    In the browser:

    1. Get the passphrase from the user - that's deterministic. Throw in some user specific metadata in the mix so that two users with the same passphrase don't have the same key.
    2. Generate a symmetric key from the input+metadata, using Argon2 for example.
    3. It is more convenient for the user to save the symmetric key locally, but it increases its exposure (reduces its security). I'll let you figure out the right balance here.
    4. Generate a key pair randomly
    5. Encrypt the private key with the symmetric key generated at step 2
    6. Save the encrypted private key locally
    7. Send the encrypted private key and the plain text public key to the server.
    8. When someone sends you an encrypted file, decrypt the private key and use it to decrypt the file.

    (The actual file encryption will likely be hybrid, where a random symmetric key is encrypted with the recipient's public key, and used to bulk encrypt the file much faster)

    Since the symmetric key generated at step 2 never reached the server, the server cannot see the files going through it.

    When disaster strikes on the client (or when they just want to read files from another computer):

    1. Do steps 1, 2 and 3 in the process as above. You will recover the initial symmetric key.
    2. Instead of generating a key pair, request the encrypted private key from the server
    3. Decrypt the private key and use it to read files shared with you.

    +I suggest that you integrate a key distribution test in the onboarding process.

    1. After generating the key pair, encrypt some flag with it. The time in seconds modulo 600 (10 minute increment) for example.
    2. Do not save the key just yet. Just send the encrypted private key and public key (step 7 above).
    3. Ask the encrypted key from the server and try to decrypt it
    4. Decrypt the flag with the key.

    The user won't notice the extra round trip, and you'll have confidence that it was saved properly on the server.