Sending whispers across the interstellar space!
This project holds the development of encrypted messaging over the distributed Stellar network.
$./whisper.py -h
___ . __ . __ ___ .
. / _/___ / /____ *__________/ /____ / / /___ ______ + /\
/ // __ \/ __/ _ \/ ___/ ___/ __/ _ \/ / / __ `/ ___/ .' '.
_/ // / / / /_/ __/ / (__ ) /_/ __/ / / /_/ / / /======\
/___/_/ /_____/\___/_/ /____/\__/\___/_/_/\__,_/_/ ;:. _ ;
| | . / / /_ (_)________ ___ _____ |:. (_) |
| | /| / / __ \/ / ___/ __ \/ _ \/ ___/ + ;:. ;
| |/ |/ / / / / (__ ) /_/ / __/ / .' \:.XLM / `.
|__/|__/_/ /_/_/____/ .___/\___/_/ . . / .-'':._.'`-. \
/_/ |/ /||\ \|
Usage:
whisper.py (-r [-n N] | -s MSG -a ADDR) [-k FILE] [-e ENC]
whisper.py -h | --help
whisper.py -v | --version
Options:
-r Read messages.
-s MSG The message text to send.
-a ADDR The destination address (required for sending).
-n N Read last N messages (optional for reading) [default: 1].
-k FILE Path to the file containing the password-protected stellar
seed for your account [default: ~/.stellar/wallet].
-e ENC Required encoding for the message text [default: 0].
Valid options are:
0 = raw (no) encoding,
1 = GSM 03.38 encoding,
2 = Sixbit ASCII encoding,
3 = smaz compression.
-v --version Display version and exit.
-h --help Show this screen.
As a shout-out to fellow like-minded cryptonauts (is this even a word?) I'm starting with some links to similar projects that I found about recently:
- https://github.com/stellarguard/secret-memo, a Stellar implementation by @stellarguard
- https://github.com/WietseWind/Secure-XRP-Text, a Ripple implementation by @WietseWind
It's inspiring to see other people to come to similar ideas!
Previous attempts at ubiquitous encryption, such as the Web of trust concepts of PGP/GPG, PKI, and DNSSEC have been (and to some extent still are) hampered by a lack of proper incentives. Security just isn't considered a must-have in the eyes of a typical user.
For example, I still remember myself trying to use GPG with some discipline approximately 10 years ago, only to lose sync of the public keys of my contacts, and eventually losing my own keys at some point. I didn't even bother to recover them. I had nothing to loose. My interactions continued un-encrypted. Eventually, even the most enthusiastic of my contacts gave up.
On the other hand, the idea of public-key cryptography is essentially embedded into the concept of cryptocurrencies. In simplified terms, your public key (or sometimes a hash of it, as in the case of Bitcoin) is the address, to which you can send a coin, and your secret key (which you should really never tell anyone) is the ticket that allows you to spend the coins that you own.
One of the benefits of the rise, adoption, and proliferation of cryptocurrencies is that adopters are also necessarily (stake)holders of their cryptographic key pairs. As such, they have a direct monetary incentive to never lose their keys! Considering this, it becomes obvious that these exact same cryptographic keys could be further utilized and leveraged.
In this project I propose using cryptocurrency keys for highly secure, persistent, and resilient messaging. What features do we gain by sending messages over cryptocurrency networks?
- Security: strong encryption with no possibility of man-in-the-middle attacks.
- Authenticity: message authenticity is guaranteed by the rules of the network.
- Resilience: the network is decentralized, no single node to attack, making sending messages difficult to block (the only option is to completely block the client from the network).
- Persistency: the history is immutable, distributed, and protected by the consensus protocol, which allows retrieving messages at any point in the future.
- Plausible deniability (optional): some protocols contain optional fields for hashes which are indistinguishable from encrypted messages and the sender himself cannot decode the sent messages if ephemeral key pairs were used.
A break of any of the above would at the same time mean a break of the underlying cryptography, breaking the network itself. As such, cryptocurrencies will continue to evolve towards more secure algorithms when security margin become too low.
From the various cryptocurrencies that I am familiar with, I could not find a better candidate than Stellar (XLM) for this project.
Stellar has one of the best developer ecosystems that I have looked at. There are official SDK libraries available for Java, JavaScript, Ruby and Go with excellent documentation. Further community SDKs for Python, C#, and C++ are also available.
Transactions are also very fast (resolve in 2 - 5 seconds) and cheap (100 stroops ~ 0.000003 USD at the time of writing). Decentralization and the size of the network continue to increase and there is currently no end in sight. But the most important criterion is, in my opinion, the open-mindedness of the Stellar Development Foundation.
Prior to messaging, Alice and Bob convert their Ed25519 Elliptic Curve keys, corresponding to Stellar's private seed and public address, to Curve25519 keys which will allow them to perform Elliptic Curve Diffie-Hellman exchange according to the rules of X25519.
Let's call these Curve25519 keys sk_Bob, pk_Bob, sk_Alice, and pk_Alice
.
Scenario: Bob sends a message to Alice
Bob calculates the shared secret by using his secret key and Alice's public key using the ECDH
algorithm. Alice does the same calculation using her secret key and Bob's public key. Let's call this shared 256-bit secret value k
,
k = ECDH(pk_Alice, sk_Bob) = ECDH(pk_Bob, sk_Alice)
The encapsulation is easiest to describe on an example. Imagine wanting to transmit a 46 byte long payload. The payload itself is a suitably encoded plaintext message or a small file that Bob wants to transmit.
|0 |10 |20 |30 |40
|0 1 2 3 4 5 6 7 8 9|0 1 2 3 4 5 6 7 8 9|0 1 2 3 4 5 6 7 8 9|0 1 2 3 4 5 6 7 8 9|0 1 2 3 4 5 6
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
| Payload 46 bytes |
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
-
The first step of encapsulation is to split the payload into fragments of maximum length of 31 bytes, resulting in two fragments of lengths of 31 and 15.
|0 |10 |20 |30 |0 1 2 3 4 5 6 7 8 9|0 1 2 3 4 5 6 7 8 9|0 1 2 3 4 5 6 7 8 9|0 ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ | Fragment 1 31 bytes | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ | Fragment 2 15 bytes | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
-
Next, a 1 byte header
H
is attached to each fragment|0 |10 |20 |30 |0 1 2 3 4 5 6 7 8 9|0 1 2 3 4 5 6 7 8 9|0 1 2 3 4 5 6 7 8 9|0 1 ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ |H| Fragment 1 1 + 31 = 32 bytes | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ |H| Fragment 2 1 + 15 = 16 bytes | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
The 8 bits of the header depend on the length L
of the fragment (encoded in bits 0 - 4) and on the encoding E
of the payload (encoded in bits 5 - 7). The fragment length is encoded in the header in the following way:
Length (L) | Header bits 0-4 | Description |
---|---|---|
1 ... 31 | 00001 ... 11111 | Last fragment of the payload of size L |
0 | 00000 | Full-size fragment, payload continues in the next fragment |
The next 3 bits of the header encode the encoding:
Header bits 5-7 | Content | Description | Symbol rate |
---|---|---|---|
000 | binary | no encoding, raw 8-bit | 31 B / fragment |
001 | SMS TEXT | GSM 03.38 charset | 35 characters / fragment |
010 | restricted uppercase TEXT | Sixbit ASCII | 41 characters / fragment |
011 | TEXT | smaz/smac compression | ~ 31 - 60 B / fragment |
100 | RESERVED for future use | ||
101 | RESERVED for future use | ||
110 | RESERVED for future use | ||
111 | RESERVED for future use |
For instance, the headers of fragments of the above hypothetical 46 B compression-encoded payload look like this:
|0 1 2 3 4|5 6 7|
┌─┬─┬─┬─┬─┬─┬─┬─┐
|0 0 0 0 0|0 1 1| header of Fragment 1
└─┴─┴─┴─┴─┴─┴─┴─┘
┌─┬─┬─┬─┬─┬─┬─┬─┐
|0 1 1 1 1|0 1 1| header of Fragment 2
└─┴─┴─┴─┴─┴─┴─┴─┘
The combination of the header and the fragment will be called a block.
-
Let's call
b_i
a (zero-padded, if required) block of the payload. -
The initiation vector (nonce) is given by the sum 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:]
Here
sequence_number
is the sequential and increasing number attached to the transaction and incremented in Bob's account after the transaction has been settled. This construction assures thatIV
is unique and direction-dependent. If Alice sends Bob a message, theIV
will be given by the sum of Bob's public key and thesequence_number
of Alice's account. Never reusing the sameIV
is critically important. Failing to do so would catastrophically compromise the encryption (remember both Alice and Bob share the same secretk
). With the proposedIV
construction, even if Alice sends Bob the exact same plaintext message within a transaction with the exact samesequence_number
(the sequence number is only guaranteed to be unique for each Stellar account separately), theIV
is guaranteed to be different, unless a collision of the first 16 bytes of the public key occurs which is highly unlikely (~1 in 264 chance).The encrypted block will then be
c_i = AES(b_i, IV, k)
With the construction of the
IV
we have essentially selected the CTR mode of operation for AES. -
Optional: It is also possible to chain multiple encryptions (even though I don't think that is necessary, AES should provide plenty of security margin). In this case we need to extend the 256-bit shared secret to more, e.g. 512, bits using a suitable Key Derivation Function
k1 || k2 = KDF(k)
The first encryption round (using AES-256) is then
c_i' = AES(b_i, IV, k1)
and the second encryption round (for example, using Twofish)
c_i = Twofish(c_i', IV, k2)
The encrypted blocks c_i
are set to the 32 byte MEMO_HASH
field of the Stellar transaction object. A payment transaction is constructed (e.g. using the 0.0000001 XLM minimum amount). The total cost in this case will be 0.00000101 XLM per fragment (the total cost is the sum of the transaction fee and the payment amount). The transactions for all message fragments are executed sequentially with consecutive sequence numbers.
Basically, for receiving the message, the corresponding inverse operations of steps 1-4 are performed at the receiving site in the opposite order: the encrypted blocks are decrypted and assembled into the payload (removing the headers and padding), and finally the payload is decoded.
The application is running on TESTNET at the moment, but that can easily be switched as soon as the security measures for protecting your seed are implemented (see TODO).
Bob (identified by this address GD2TA...2TIY) can send a message to Alice (identified by this address GCU2R...7ZDH):
$./whisper.py -s "Wow! A message through Stellar!" -a GCU2RRJHYBEIP6R6SJHLTCC32FVFGATYMTYB3ZBKT3OMPZLCTVSS7ZDH -k .bob_wallet
____ . __ . __ ___ .
. / _/___ / /____ *__________/ /____ / / /___ ______ + /\
/ // __ \/ __/ _ \/ ___/ ___/ __/ _ \/ / / __ `/ ___/ .' '.
_/ // / / / /_/ __/ / (__ ) /_/ __/ / / /_/ / / /======\
/___/_/ /_____/\___/_/ /____/\__/\___/_/_/\__,_/_/ ;:. _ ;
| | . / / /_ (_)________ ___ _____ |:. (_) |
| | /| / / __ \/ / ___/ __ \/ _ \/ ___/ + ;:. ;
| |/ |/ / / / / (__ ) /_/ / __/ / .' \:.XLM / `.
|__/|__/_/ /_/_/____/ .___/\___/_/ . . / .-'':._.'`-. \
/_/ |/ /||\ \|
Enter password:
Sending message to GCU2RRJHYBEIP6R6SJHLTCC32FVFGATYMTYB3ZBKT3OMPZLCTVSS7ZDH... Done.
Alice can indeed read the message.
$./whisper.py -r -n 1 -k .alice_wallet
____ . __ . __ ___ .
. / _/___ / /____ *__________/ /____ / / /___ ______ + /\
/ // __ \/ __/ _ \/ ___/ ___/ __/ _ \/ / / __ `/ ___/ .' '.
_/ // / / / /_/ __/ / (__ ) /_/ __/ / / /_/ / / /======\
/___/_/ /_____/\___/_/ /____/\__/\___/_/_/\__,_/_/ ;:. _ ;
| | . / / /_ (_)________ ___ _____ |:. (_) |
| | /| / / __ \/ / ___/ __ \/ _ \/ ___/ + ;:. ;
| |/ |/ / / / / (__ ) /_/ / __/ / .' \:.XLM / `.
|__/|__/_/ /_/_/____/ .___/\___/_/ . . / .-'':._.'`-. \
/_/ |/ /||\ \|
Enter password:
Date From Message
2018-04-24 GD2TA2JCQTM6… Wow! A message through Stellar!
Note: If you want to play around in the TESTNET using these two demo accounts, the password of both wallet files is aaaa1111
, which is something one can reasonably quickly type during testing.
The code is written in Python 3. You need the following python packages (install them with pip):
- Implement password protection for the seed file.
- Implement the defined message encodings.
- Introduce argument for specifying the cursor from where to start reading messages.
- Integrate federation address handling.
- Make the implementation cleaner (refactor).
- Provide a library, e.g. for integrating into wallet software.