Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add encryption key to the account #26

Open
jedmccaleb opened this issue Jan 19, 2016 · 11 comments
Open

Add encryption key to the account #26

jedmccaleb opened this issue Jan 19, 2016 · 11 comments
Labels
help wanted Open especially for those who want to write a CAP/SEP! needs draft This an issue that has no corresponding draft, and as such has not entered the CAP/SEP process. SEP Represents an issue that requires a SEP.

Comments

@jedmccaleb
Copy link
Contributor

This would allow people to send private messages to accounts. We definitely need this ability for a lot of things. For example when banks are exchanging KYC info of their customers it should be encrypted.
The alternative to sticking the encryption keys in the ledger is sticking them in the stellar.toml and as federation results.

The upside of sticking in the ledger is that it is more flexible and requires people to set up less infrastructure outside of the core network. It requires less third parties.
The downside is adding more stuff in the ledger and extra complexity in the core.

@bartekn
Copy link
Contributor

bartekn commented Jan 20, 2016

Can't we just use public key of the account (account ID) for encryption? Recipient can then decrypt it using corresponding secret key of the account.

@nullstyle
Copy link

Ed25519 keys are not directly useable for encryption (ed25519 is just a digital signature system), and even if not the case, using the account ID directly does not allow for flexibility in the encryption scheme, and finally I've been taught that you shouldn't use the same key in two separate systems (i.e. greater surface area for key compromise).

I have a feeling that David would be very against directly using an account's public key for our memo scheme.

@donovanhide
Copy link

@nullstyle An interesting counterexample of reusing two ed25519 public keys to create a key exchange for nacl box:

https://github.com/dchest/ed2curve-js

Also supported in agl's Go package:

https://github.com/agl/ed25519/blob/master/extra25519/extra25519.go

Clever stuff :-)

@jedmccaleb
Copy link
Contributor Author

Yes david had these concerns. But even if we can use the accountID it is better to have a different field since you should rotate your encryption key periodically. And you might not want the thing decrypting to have access to the secret for the account.

I'm leaning toward adding it to account.

@donovanhide
Copy link

Worth a read:
https://nacl.cr.yp.to/box.html
The issue worth thinking about is correctly choosing the nonce. Given two accounts A and B, each with an attached account sequence number at the point in time of the transaction, the sum of the two sequences would provide a unique nonce every time for the pairing of A and B's public keys. If the sum overflowed you'd probably need to disallow it as a nonce, but that is unlikely to ever happen :-)

@MonsieurNicolas
Copy link
Contributor

It was my understanding that compliance would not work without federation... if this is the case I don't see why this should go in core.
In any case, I'd like to if possible create a layered architecture for this and only move things into core when we have a proven case that we need it there as it makes iterating/experimenting much faster

@jedmccaleb
Copy link
Contributor Author

This is less about compliance (though it is obviously useful there) and more about providing a way for accounts to exchange information securely. But now that you mention it I think it might be better to make this more generic and add the ability to save key/value pairs attached to an account. Created this other issue: #27

@MikeFair
Copy link

I was thinking last night about how to successfully use the Accounts to share Bank Account Info and other KYC stuff both for a wallet on many machines, and with a counter party.

To keep us on the same page; I'm using Asymmetric PKI to communicate a Symmetric Key stored on the account's properties (aka the password/passphrase/cipherkey for decoding encrypted account data). This technique also applies to encrypted data sent OOB (out-of-band) (like via IPFS) and uses Stellar to communicate the symmetric key value.

Here's what I came up with:
(1)
Dynamically generate a 64-byte symmetric key, double encrypt it (first with a private key (i.e. a msg from alice)/then with the public key (i.e. an envelope to alice)); and stick it in a KVP on the account.

This value is retrievable by any software that can get both the ciphertext (every Stellar connected agent can get this) and both the public and private keys used to encrypt it. The ciphertext is complete gibberish to an attacker because it's an encrypted block of arbitrary random data; the message could validly be any 64 byte value and there's no way to tell what it should be.

(2)
Use this symmetric key to encode all the other data values that you wish to be encrypted. My hope is to somehow use the data from (1) as seed information to make a OneTimePad for each distinct key value on the account (using PSRGs in a way that makes it impossible to attack the PSRG).

One risk to keep in mind is we have to assume would-be attackers will know the value of certain fields stored on the account (like a phone number, bank account number, ssn, etc.). The algorithm must prevent knowing that information from compromising the rest of the encrpyted data (hence my focus on creating a OneTimePad technique because it prevents information leakage from one byte to the next).

Here is a basic psuedocode of the approach:

account.setData("encryptMethod", "OneTimePad")
otp_seed = random(64 bytes) 
message = encrypt(private_key, otp_seed) // A message from Alice
envelope = encrypt(public_key, message)  // A letter for Alice
account.setData("myOtpSeed", envelope)

key_name = "US/SSN"
raw_data = "mike's super secret ssn"
encrypted_value = otp_encode(otp_seed, key_name, raw_data)
account.setData(key_name, encrypted_value)

To get the values back, retrieve the OTP seed ciphertext; use the private key to decrypt the outside envelope (a message to alice); then the public key (a message from alice); and now you have the OTP seed to decode the encrypted KVP data stored on the account.

Again in psuedocode:

enc_method = account.getData("encryptMethod")
// Use some method/mechanism to call the right decryption method based on the returned value

otp_envelope = account.getData("myOtpSeed")
otp_message = decrypt(private_key, otp_envelope) // Get message in the letter for Alice
otp_seed = decrypt(public_key, otp_message)  // Decrypt the message from Alice

key_name = "US/SSN"
ssn_ciphertext = account.getData(key_name)
ssn_value = otp_decode(otp_seed, key_name, ssn_ciphertext)

The agent can now use the decrypted value and reuse the same otp_seed to decrypt other values.

To be clear the otp_seed is a seed for an algorithm generating the one time pad. The key_name is used to create a unique location/address within that pad for 64 bytes of OTP data. The value stored on the account is not the one time pad itself, nor is any value within the dynamically generated one time pad ever reused (each key gets its own unique 64 byte string within the pad).

One advantage here is the OTP seed can be rotated as often as we like. It requires decrypting all the KVPs and reencrypting/restoring them; but that's a rather small burden to bear for an easy to implement "Rotate the account's encryption seed" procedure. The other advantage is the keypair used to encrypt the seed can also be rotated as often as desired; it requires only that the OTP seed be reencrypted, but the data values themselves can be left alone.

As is typical, if the encryption KeyPair is lost; the OTP seed is unrecoverable, along with the encoded data.


That scheme allows me to share encrypted data with myself safely only using OOB storage for the keypair encrpyting the OTP seed.
But I still can't share with other accounts because they don't have my private key (nor am I going to give it to them).

To share that information with another Stellar Account we create a third account that we both share.

If we concatenate our two public account addresses, putting the "numerically lower" one first, then use that as the passphrase for this third account; we can, without prior foreknowledge, create a named account space that we can collaborate through. This also let's me change the answers specifically for that counter party, and let's the counter party populate whatever extra data they wish about my account that we can "share".

shared_account_address = keyPair.fromRawSeed(hash.Hash(AccountAddress1 + AccountAddress2))

We go through essentially the same procedure, but this time we need to store the OTP seed 4 times.
One of the parties generates a 64 bytes of OTP seed.
First they encrypt it with their own private key (a message from them);
Second they encrypt it with the counterparty's public key (an envelope for the counter party);
Third they store the result on the account.
Fourth, they also encrypt it with their own public key and store that on the account too.

This way both parties can decrypt the OTP seed value that account uploaded.

The other two copies of the seed show up when the counterparty checks the account.
It detects that the seed has changed because the copy of the seed that it uploaded (assuming there was one there) does not match what the other account uploaded.

It updates its own copies of the seed information by uploading the new OTP seed value to the two key_names (one for itself, and one for the counterparty) that it "controls".

It can now move on to decoding/encoding KVP values on the account.

Either account can change the OTP seed at any time.
Either account can change the keypair it uses to encrypt the OTP seed at any time.

Whenever the OTP seed itself changes, the account will have to reencrypt all encrypted data on the account. Whenever the keypair used to encrypt the seed changes, it only updates the two encrypted seed values it controls. The counterparty can detect the changes next time it accesses the account.

This technique scales to N accounts by concatenating the public addresses of all accounts involved in numeric ascending order and each account adding an additional encrpyted seed value for each of the other accounts.

The total number of encrypted seed KVPs is N^2. This is required because the private key used by each account involved is secret to each account.

@MikeFair
Copy link

I recently learned that a "stream cipher" is the name of the thing I described to protect the data.

@jedmccaleb
Copy link
Contributor Author

Can do this with manage data and an ecosystem proposal

@jedmccaleb jedmccaleb added the SEP Represents an issue that requires a SEP. label Aug 2, 2018
@hmatejx
Copy link

hmatejx commented Sep 13, 2018

This is the construction I use in my Interstellar-Whisper example (Alice and Bob share a secret using X25519, Alice is the receiver below, and AES used for encryption):

The 32 byte initiation vector is given by the sum (concatenation) of the first 16 bytes of Alice’s public key (interpreted as an integer) and the sending accounts’ sequence number. Specifically,

  IV = (pk_Alice[0:16] + sequence_number)[-16:]

This construction assures that IV is unique and direction-dependent. If Alice sends Bob a message, the IV will be given by the sum of Bob’s public key and the sequence_number of Alice’s account. Even if Alice sends Bob the exact same plaintext within a transaction with the exact same sequence_number for her account, the IV is guaranteed to be different, unless a collision of the first 16 bytes of the public key occurs which is highly unlikely (~1 in 2^64 chance).

Edit: I just realized that xor-ing would be even better than concatenating, no need to take only 16 bytes.

@theaeolianmachine theaeolianmachine added help wanted Open especially for those who want to write a CAP/SEP! needs draft This an issue that has no corresponding draft, and as such has not entered the CAP/SEP process. labels Mar 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Open especially for those who want to write a CAP/SEP! needs draft This an issue that has no corresponding draft, and as such has not entered the CAP/SEP process. SEP Represents an issue that requires a SEP.
Projects
None yet
Development

No branches or pull requests

8 participants