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.