0

I am creating a secure network data transfer system, and would like to know if there are any obvious flaws in this scheme.

Secrets

The secret is a password that is known to both clients. This password is never transferred plaintext, nor is the PBKDF2 password hash.

Server side

The server never stores the password permanently. The password is salted using a random 16-byte salt and hashed using PBKDF2-SHA256 with at least 30000 iterations (the number of rounds is configurable).

Client side

The client has the password in plaintext.

Notification

The server notifies the client via a UDP packet that has a sensitive information field, "source". The packet looks similar to the following:

{
    "proto": <protocol version as integer, starting at 1 for the first version>,
    "host": <address of sender to communicate privately with, as string>
    "port": <port of sender to communicate privately with, as string>
    "source": <source of data. Note that this field is encrypted using 
              PKCS#7/AES-256-CBC with "iv" as the initialization vector 
              and the 32-byte password hash as the key>
    "iv": <16-byte IV for encrypting "source">
    "salt": <password salt that password hash was derived with>
    "rounds": <number of PBKDF2 rounds that password hash was derived with>
    "hmac": <HMAC-SHA256 of {iv || source}, using the password hash as key>
    "time": <time ping was sent, as string>,
    "manual" <boolean whether ping was a manual send (true) or automatic change notice (false)>
    "ping": true
}

where || is concatenation. The 32-byte PBKDF2-SHA256 result, with provided salt and rounds, makes up the encryption key for "source".

The IV obviously prevents identical messages from having the same encryption signature, and the HMAC should provide the message authentication.

Client <-> Server

When the client receives the server broadcast, it then initiates a TCP connection to the server to obtain the data. The server then sends this response to set up the connection:

{
    "proto": <protocol version as integer, starting at 1 for the first version>,
    "iv": <16-byte IV to use for encrypting information>,
    "salt": <password salt that password hash was derived with>,
    "rounds": <number of PBKDF2 rounds that password hash was derived with>
}

The reason for the server providing the IV is to prevent replay attacks with a known client IV, and the salt and rounds show what the server expects the encryption key to be (again the 32-byte result of the PBKDF2-SHA256 of the password).

The client then sends a request that looks like the following:

{
    "proto": <protocol version as integer, starting at 1 for the first version>,
    "hmac": <HMAC-SHA256 of {iv || req}, using the password hash as key>,
    "req": <binary string containing encrypted packed representation of the following:
        {
            "source": <source of desired information requested>,
            "dest": <destination of desired information requested>,
            "user": <Username, as string (empty string if no username)>,
        }>
}

The HMAC once again allowing no man-in-the-middle tampering, unless the key is leaked. "req" is encrypted with PKCS#7/AES-256-CBC with the previously sent "iv" as the initialization vector and the 32-byte PBKDF2 hash as the key.

The server responds with a final data packet:

{
    "proto": <protocol version as integer, starting at 1 for the first version>,
    "hmac": <HMAC-SHA256 of {iv || req}, using the password hash as key>,
    "resp": <binary string containing encrypted packed representation of the following:
        {
            "iv": <16-byte IV to use with the next request on this connection>
            .
            .
            .
        }>
}

"resp" is encrypted just the same as "req" in the client request.

Question

Are there any major flaws in this scheme? If so, what are they? I've tried to prevent replay attacks by rotating IVs often and ensuring the server generates them, and the encryption key is never sent in plaintext.

9
  • 1
    First question is... Why haven't you mentioned TLS? Second question is, why don't you think TLS is enough? (I'm not saying that TLS is the perfect solution; I've just not yet seen a system where TLS isn't more than good enough, and would love to see a situation where doing more than TLS is appropriate.)
    – Ghedipunk
    Dec 27 '19 at 21:24
  • @Ghedipunk - I tried TLS initially, but because the client and server are in the same application, designed to be used in a mesh on the same network, distributing a certificate with the program would allow anyone with the application to read any communication over the network. Self-signing would still have the problem of sharing the certificate details. I'm not sure how well it would work to encrypt UDP broadcasts using TLS either.
    – owacoder
    Dec 27 '19 at 21:28
  • 1
    In TLS, the secret used for encryption is negotiated on a per-connection basis. Two separate servers with the same private key for a given certificate can not eavesdrop on each other's communication without sharing the per-connection keys as well. So no, distributing the same secret key for each certificate isn't an encryption issue. It is, however, an authentication issue. If you're not delivering the (in your case) shared secret in an out-of-bounds way, you still have a key management problem.
    – Ghedipunk
    Dec 27 '19 at 21:44
  • 1
    (There are a lot of caveats in my last comment; the main point I want to make with it is that there's a key management problem unrelated to the data on the wire.)
    – Ghedipunk
    Dec 27 '19 at 21:45
  • @Ghedipunk - Oh, OK. I guess I used the wrong terminology in my comment. Just to clarify, is my assumption that TLS is inappropriate for a mesh network using a shared secret correct?
    – owacoder
    Dec 27 '19 at 22:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.