Prop 123: KDF and encryption

This commit is contained in:
str4d
2018-11-27 00:12:04 +00:00
parent 7096782af0
commit 53e02ed83d

View File

@ -2,10 +2,10 @@
New netDB Entries
=================
.. meta::
:author: zzz, orignal, str4d
:author: zzz, str4d, orignal
:created: 2016-01-16
:thread: http://zzz.i2p/topics/2051
:lastupdated: 2018-11-19
:lastupdated: 2018-11-27
:status: Open
:supercedes: 110, 120, 121, 122
@ -437,6 +437,61 @@ Published by:
Destination
Definitions
```````````
We define the following functions corresponding to the cryptographic building blocks used
for encrypted LS2:
PRNG(n)
n-byte output from a pseudorandom number generator backed by a strong entropy source.
The output of the PRNG MUST be hashed before use if it will appear on the network
(such as a salt, or encrypted padding), in order to avoid leaking raw PRNG bytes to
the network [PRNG-REFS]_. These instances will use the notation H(PRNG(n)) to remove
any ambiguity.
H(p, d)
A cryptographic hash function that takes a personalisation string p and data d, and
produces an output of length HASH_LEN bytes. The hash function should be preimage- and
collision-resistant.
Instantiated with SHA-256 (implying HASH_LEN = 32) as follows::
H(p, d) := SHA-256(p || d)
STREAM
A stream cipher which takes a key of length S_KEY_LEN bytes, and a nonce of length
S_IV_LEN bytes. It has the following functions:
ENCRYPT(k, iv, plaintext)
Encrypts plaintext using the cipher key k, and nonce iv which MUST be unique for
the key k. Returns a ciphertext that is the same size as the plaintext.
The entire ciphertext must be indistinguishable from random if the key is secret.
DECRYPT(k, iv, ciphertext)
Decrypts ciphertext using the cipher key k, and nonce iv. Returns the plaintext.
[Suggestion, TBD]
Instantiated with ChaCha20 as specified in [RFC-7539-S2.4]_, with the initial counter
set to 1. This implies that S_KEY_LEN = 32 and S_IV_LEN = 12.
KDF(ikm, salt, info, n)
A cryptographic key derivation function which takes some input key material ikm (which
should have good entropy but is not required to be a uniformly random string), a salt
of length SALT_LEN bytes, and a context-specific 'info' value, and produces an output
of n bytes suitable for use as key material.
Instantiated with HKDF as specified in [RFC-5869]_, using the hash function SHA-256.
This means that SALT_LEN can be at most 32.
Note: If we care about speed, we could use keyed-BLAKE2b instead. It has an output
size large enough to accommodate the largest n we require (or we can call it once per
desired key with a counter argument). BLAKE2b is much faster than SHA-256, and
keyed-BLAKE2b would reduce the total number of hash function calls.
[UNSCIENTIFIC-KDF-SPEEDS]_
Format
``````
The encrypted LS2 format consists of three nested layers:
@ -536,10 +591,6 @@ Signature
Layer 1 (middle)
~~~~~~~~~~~~~~~~
Published timestamp is the nonce.
Do we need HMAC or ChaCha only? Probably don't need HMAC, everything is signed.
KDF TBD, uses Destination.
Flag
1 byte
@ -570,10 +621,6 @@ innerCiphertext
Layer 2 (inner)
~~~~~~~~~~~~~~~
Published timestamp is the nonce.
Do we need HMAC or ChaCha only? Probably don't need HMAC, everything is signed.
KDF TBD. Used blinded public key. Uses cookie also if per-client.
Type
1 byte
@ -587,25 +634,92 @@ Data
Encryption and processing
`````````````````````````
Layer 1 key derivation
~~~~~~~~~~~~~~~~~~~~~~
TBD
Derivation of subcredentials
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As part of the blinding process, we need to ensure that an encrypted LS2 can only be
decrypted by someone who knows the corresponding Destination. To achieve this, we derive
a credential from the Destination::
credential = H("credential", Destination)
The personalization string ensures that the credential does not collide with any hash used
as a DHT lookup key, such as the plain Destination hash.
For a given blinded key, we can then derive a subcredential::
subcredential = H("subcredential", credential || blindedPublicKey)
The subcredential is included in the key derivation processes below, which binds those
keys to knowledge of the Destination.
Layer 1 encryption
~~~~~~~~~~~~~~~~~~
TBD
First, the input to the key derivation process is prepared::
outerInput = blindedPublicKey || subcredential || publishedTimestamp
Next, a random salt is generated::
outerSalt = H(PRNG(SALT_LEN))
Then the key used to encrypt layer 1 is derived::
keys = KDF(outerInput, outerSalt, "ELS2_L1K", S_KEY_LEN + S_IV_LEN)
outerKey = keys[0..S_KEY_LEN]
outerIV = keys[S_KEY_LEN..(S_KEY_LEN+S_IV_LEN)]
Finally, the layer 1 plaintext is encrypted and serialized::
outerCiphertext = outerSalt || STREAM.ENCRYPT(outerKey, outerIV, outerPlaintext)
Layer 1 decryption
~~~~~~~~~~~~~~~~~~
The salt is parsed from the layer 1 ciphertext::
outerSalt = outerCiphertext[0..S_IV_LEN]
Then the key used to encrypt layer 1 is derived::
outerInput = blindedPublicKey || subcredential || publishedTimestamp
keys = KDF(outerInput, outerSalt, "ELS2_L1K", S_KEY_LEN + S_IV_LEN)
outerKey = keys[0..S_KEY_LEN]
outerIV = keys[S_KEY_LEN..(S_KEY_LEN+S_IV_LEN)]
Finally, the layer 1 ciphertext is decrypted::
outerPlaintext = STREAM.DECRYPT(outerKey, outerIV, outerCiphertext[S_IV_LEN..])
Layer 2 per-client cookie decryption
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
TBD
Layer 2 key derivation
~~~~~~~~~~~~~~~~~~~~~~
TBD
Layer 2 encryption
~~~~~~~~~~~~~~~~~~
TBD
When client authorization is enabled, ``authCookie`` is calculated as described above.
When client authorization is disabled, ``authCookie`` is the zero-length byte array.
Encryption proceeds in a similar fashion to layer 1::
innerInput = blindedPublicKey || authCookie || subcredential || publishedTimestamp
innerSalt = H(PRNG(SALT_LEN))
keys = KDF(innerInput, innerSalt, "ELS2_L2K", S_KEY_LEN + S_IV_LEN)
innerKey = keys[0..S_KEY_LEN]
innerIV = keys[S_KEY_LEN..(S_KEY_LEN+S_IV_LEN)]
innerCiphertext = innerSalt || STREAM.ENCRYPT(innerKey, innerIV, innerPlaintext)
Layer 2 decryption
~~~~~~~~~~~~~~~~~~
When client authorization is enabled, ``authCookie`` is calculated as described above.
When client authorization is disabled, ``authCookie`` is the zero-length byte array.
Decryption proceeds in a similar fashion to layer 1::
innerInput = blindedPublicKey || authCookie || subcredential || publishedTimestamp
innerSalt = innerCiphertext[0..S_IV_LEN]
keys = KDF(innerInput, innerSalt, "ELS2_L2K", S_KEY_LEN + S_IV_LEN)
innerKey = keys[0..S_KEY_LEN]
innerIV = keys[S_KEY_LEN..(S_KEY_LEN+S_IV_LEN)]
innerPlaintext = STREAM.DECRYPT(innerKey, innerIV, innerCiphertext[S_IV_LEN..])
Notes
@ -1103,5 +1217,18 @@ TODO: How to have a shared clients that supports both old and new crypto?
References
==========
.. [PRNG-REFS]
http://projectbullrun.org/dual-ec/ext-rand.html
https://lists.torproject.org/pipermail/tor-dev/2015-November/009954.html
.. [RFC-4880-S5.1]
https://tools.ietf.org/html/rfc4880#section-5.1
.. [RFC-5869]
https://tools.ietf.org/html/rfc5869
.. [RFC-7539-S2.4]
https://tools.ietf.org/html/rfc7539#section-2.4
.. [UNSCIENTIFIC-KDF-SPEEDS]
https://www.lvh.io/posts/secure-key-derivation-performance.html