public static byte[] Respond(ECPublicKeyParameters senderPublicKey, ECPrivateKeyParameters receiverPrivateKey, ECPublicKeyParameters ephemeralSenderPublicKey) { ECPublicKeyParameters Q_static_U = senderPublicKey; ECPrivateKeyParameters d_static_V = receiverPrivateKey; ECPublicKeyParameters Q_ephemeral_U = ephemeralSenderPublicKey; // Calculate shared ephemeral secret 'Ze' BigInteger Ze = KeyAgreementFactory.CalculateEcdhcSecret(Q_ephemeral_U, d_static_V); // EC-DHC byte[] Ze_encoded = Ze.ToByteArrayUnsigned(); // Calculate shared static secret 'Zs' BigInteger Zs = KeyAgreementFactory.CalculateEcdhcSecret(Q_static_U, d_static_V); // EC-DHC byte[] Zs_encoded = Zs.ToByteArrayUnsigned(); // Concatenate Ze and Zs byte strings to form shared secret, pre-KDF : Ze||Zs var Z = new byte[Ze_encoded.Length + Zs_encoded.Length]; Ze_encoded.DeepCopy_NoChecks(0, Z, 0, Ze_encoded.Length); Zs_encoded.DeepCopy_NoChecks(0, Z, Ze_encoded.Length, Zs_encoded.Length); // Zero intermediate secrets Ze_encoded.SecureWipe(); Zs_encoded.SecureWipe(); return(Z); }
private byte[] SendInitializationRequest(State state) { // message format: // nonce(16), pubkey(32), ecdh(32), padding(...), signature(64), mac(12) if (!(state is ClientState clientState)) { throw new InvalidOperationException("Only the client can send init request."); } // 16 bytes nonce clientState.InitializationNonce = RandomNumberGenerator.Generate(InitializationNonceSize); // get the public key var pubkey = Signature.GetPublicKey(); // generate new ECDH keypair for init message and root key IKeyAgreement clientEcdh = KeyAgreementFactory.GenerateNew(); clientState.LocalEcdhForInit = clientEcdh; // nonce(16), <pubkey(32), ecdh(32), signature(64)>, mac(12) var initializationMessageSize = InitializationNonceSize + EcNumSize * 4 + MacSize; var messageSize = Math.Max(Configuration.MinimumMessageSize, initializationMessageSize); var initializationMessageSizeWithSignature = messageSize - MacSize; var initializationMessageSizeWithoutSignature = messageSize - MacSize - SignatureSize; var signatureOffset = messageSize - MacSize - SignatureSize; var message = new byte[messageSize]; Array.Copy(clientState.InitializationNonce, 0, message, 0, InitializationNonceSize); Array.Copy(pubkey, 0, message, InitializationNonceSize, EcNumSize); Array.Copy(clientEcdh.GetPublicKey(), 0, message, InitializationNonceSize + EcNumSize, EcNumSize); // sign the message var digest = Digest.ComputeDigest(message, 0, initializationMessageSizeWithoutSignature); Array.Copy(Signature.Sign(digest), 0, message, signatureOffset, SignatureSize); // encrypt the message with the application key var cipher = new AesCtrMode(AesFactory.GetAes(true, Configuration.ApplicationKey), clientState.InitializationNonce); var encryptedPayload = cipher.Process(message, InitializationNonceSize, initializationMessageSizeWithSignature - InitializationNonceSize); Array.Copy(encryptedPayload, 0, message, InitializationNonceSize, initializationMessageSizeWithSignature - InitializationNonceSize); // calculate mac var Mac = new Poly(AesFactory); Mac.Init(Configuration.ApplicationKey, clientState.InitializationNonce, MacSize); Mac.Process(message, 0, initializationMessageSizeWithSignature); var mac = Mac.Compute(); Array.Copy(mac, 0, message, initializationMessageSizeWithSignature, MacSize); return(message); }
public static byte[] Initiate(ECPublicKeyParameters recipientPublicKey, ECPrivateKeyParameters senderPrivateKey, out ECPublicKeyParameters ephemeralSenderPublicKey) { ECPublicKeyParameters Q_static_V = recipientPublicKey; ECPrivateKeyParameters d_static_U = senderPrivateKey; ECPoint QeV; BigInteger deU; KeypairFactory.GenerateECKeypair(recipientPublicKey.Parameters, out QeV, out deU); var Q_ephemeral_V = new ECPublicKeyParameters("ECDHC", QeV, recipientPublicKey.Parameters); var d_ephemeral_U = new ECPrivateKeyParameters("ECDHC", deU, recipientPublicKey.Parameters); // Calculate shared ephemeral secret 'Ze' BigInteger Ze = KeyAgreementFactory.CalculateEcdhcSecret(Q_static_V, d_ephemeral_U); // EC-DHC byte[] Ze_encoded = Ze.ToByteArrayUnsigned(); // Calculate shared static secret 'Zs' BigInteger Zs = KeyAgreementFactory.CalculateEcdhcSecret(Q_static_V, d_static_U); // EC-DHC byte[] Zs_encoded = Zs.ToByteArrayUnsigned(); // Concatenate Ze and Zs byte strings to form shared secret, pre-KDF : Ze||Zs var Z = new byte[Ze_encoded.Length + Zs_encoded.Length]; Ze_encoded.DeepCopy_NoChecks(0, Z, 0, Ze_encoded.Length); Zs_encoded.DeepCopy_NoChecks(0, Z, Ze_encoded.Length, Zs_encoded.Length); ephemeralSenderPublicKey = Q_ephemeral_V; // Zero intermediate secrets Ze_encoded.SecureWipe(); Zs_encoded.SecureWipe(); return(Z); }
/// <summary> /// Calculate the shared secret in participant U's (initiator) role. /// </summary> /// <param name="recipientPublicKey">Public key of the recipient.</param> /// <param name="senderPrivateKey">Private key of the sender.</param> /// <param name="ephemeralSenderPublicKey"> /// Ephemeral public key to send to the responder (V, receiver). Output to this /// parameter. /// </param> public static byte[] Initiate(ECKey recipientPublicKey, ECKey senderPrivateKey, out ECKey ephemeralSenderPublicKey) { if (recipientPublicKey.PublicComponent == false) { throw new ArgumentException("Recipient key is not public component.", "recipientPublicKey"); } if (senderPrivateKey.PublicComponent) { throw new ArgumentException("Sender key not private component.", "senderPrivateKey"); } ECKey Q_static_V = recipientPublicKey; ECKey d_static_U = senderPrivateKey; ECKeypair kp_ephemeral_U = KeypairFactory.GenerateECKeypair(senderPrivateKey.CurveName); ECKey Q_ephemeral_U = kp_ephemeral_U.ExportPublicKey(); ECKey d_ephemeral_U = kp_ephemeral_U.GetPrivateKey(); // Calculate shared ephemeral secret 'Ze' byte[] Ze = KeyAgreementFactory.CalculateEcdhcSecret(Q_static_V, d_ephemeral_U); // Calculate shared static secret 'Zs' byte[] Zs = KeyAgreementFactory.CalculateEcdhcSecret(Q_static_V, d_static_U); // Concatenate Ze and Zs byte strings to form shared secret, pre-KDF : Ze||Zs var Z = new byte[Ze.Length + Zs.Length]; Ze.DeepCopy_NoChecks(0, Z, 0, Ze.Length); Zs.DeepCopy_NoChecks(0, Z, Ze.Length, Zs.Length); ephemeralSenderPublicKey = Q_ephemeral_U; // Zero intermediate secrets Ze.SecureWipe(); Zs.SecureWipe(); return(Z); }
private byte[] DeconstructMessage(State state, byte[] payload, byte[] headerKey, EcdhRatchetStep ratchetUsed, bool usedNextHeaderKey) { if (state == null) { throw new ArgumentNullException(nameof(state)); } if (payload == null) { throw new ArgumentNullException(nameof(payload)); } if (headerKey == null) { throw new ArgumentNullException(nameof(headerKey)); } var messageSize = payload.Length; var encryptedNonce = new ArraySegment <byte>(payload, 0, NonceSize); // decrypt the nonce var headerEncryptionNonce = new ArraySegment <byte>(payload, payload.Length - MacSize - HeaderIVSize, HeaderIVSize); var hcipher = new AesCtrMode(GetHeaderKeyCipher(headerKey), headerEncryptionNonce); var decryptedNonce = hcipher.Process(encryptedNonce); CheckNonce(headerEncryptionNonce); // get the ecdh bit var hasEcdh = (decryptedNonce[0] & 0b1000_0000) != 0; decryptedNonce[0] &= 0b0111_1111; // extract ecdh if needed var step = BigEndianBitConverter.ToInt32(decryptedNonce); if (hasEcdh) { var clientEcdhPublic = new ArraySegment <byte>(hcipher.Process(new ArraySegment <byte>(payload, NonceSize, EcNumSize))); if (ratchetUsed == null) { // an override header key was used. // this means we have to initialize the ratchet if (!(state is ServerState serverState)) { throw new InvalidOperationException("Only the server can initialize a ratchet."); } ratchetUsed = EcdhRatchetStep.InitializeServer(KeyDerivation, Digest, serverState.LocalEcdhRatchetStep0, serverState.RootKey, clientEcdhPublic, serverState.LocalEcdhRatchetStep1, serverState.FirstReceiveHeaderKey, serverState.FirstSendHeaderKey); serverState.Ratchets.Add(ratchetUsed); } else { if (usedNextHeaderKey) { // perform ecdh ratchet IKeyAgreement newEcdh = KeyAgreementFactory.GenerateNew(); // this is the hottest line in the deconstruct process: EcdhRatchetStep newRatchet = ratchetUsed.Ratchet(KeyDerivation, Digest, clientEcdhPublic, newEcdh); state.Ratchets.Add(newRatchet); ratchetUsed = newRatchet; } } } // get the inner payload key from the receive chain if (ratchetUsed == null) { throw new InvalidOperationException("An override header key was used but the message did not contain ECDH parameters"); } (var key, var _) = ratchetUsed.ReceivingChain.RatchetForReceiving(KeyDerivation, step); CheckNonce(key); // get the encrypted payload var payloadOffset = hasEcdh ? NonceSize + EcNumSize : NonceSize; var encryptedPayload = new ArraySegment <byte>(payload, payloadOffset, messageSize - payloadOffset - MacSize); // decrypt the inner payload var icipher = new AesCtrMode(AesFactory.GetAes(true, key), decryptedNonce); var decryptedInnerPayload = icipher.Process(encryptedPayload); return(decryptedInnerPayload); }
private void ReceiveInitializationResponse(State state, byte[] data) { if (!(state is ClientState clientState)) { throw new InvalidOperationException("Only the client can receive an init response."); } var messageSize = data.Length; var macOffset = messageSize - MacSize; var headerIvOffset = macOffset - HeaderIVSize; var headerSize = InitializationNonceSize + EcNumSize; var payloadSize = messageSize - headerSize - MacSize; // decrypt header var cipher = new AesCtrMode(AesFactory.GetAes(true, Configuration.ApplicationKey), data, headerIvOffset, HeaderIVSize); var decryptedHeader = cipher.Process(data, 0, headerSize); Array.Copy(decryptedHeader, 0, data, 0, headerSize); // new nonce(16), ecdh pubkey(32), <nonce(16), server pubkey(32), // new ecdh pubkey(32) x2, signature(64)>, mac(12) var nonce = new ArraySegment <byte>(data, 0, InitializationNonceSize); var rootEcdhKey = new ArraySegment <byte>(data, InitializationNonceSize, EcNumSize); var encryptedPayload = new ArraySegment <byte>(data, headerSize, payloadSize); CheckNonce(nonce); // decrypt payload IKeyAgreement rootEcdh = clientState.LocalEcdhForInit; var rootPreKey = rootEcdh.DeriveKey(rootEcdhKey); rootPreKey = Digest.ComputeDigest(rootPreKey); cipher = new AesCtrMode(AesFactory.GetAes(true, rootPreKey), nonce); var decryptedPayload = cipher.Process(encryptedPayload); Array.Copy(decryptedPayload, 0, data, headerSize, payloadSize); // extract some goodies var oldNonce = new ArraySegment <byte>(data, headerSize, InitializationNonceSize); var serverPubKey = new ArraySegment <byte>(data, headerSize + InitializationNonceSize, EcNumSize); var remoteRatchetEcdh0 = new ArraySegment <byte>(data, headerSize + InitializationNonceSize + EcNumSize, EcNumSize); var remoteRatchetEcdh1 = new ArraySegment <byte>(data, headerSize + InitializationNonceSize + EcNumSize * 2, EcNumSize); // make sure the nonce sent back by the server (which is encrypted and signed) // matches the nonce we sent previously if (!oldNonce.Matches(clientState.InitializationNonce)) { throw new InvalidOperationException("Nonce did not match"); } // verify that the signature matches IVerifier verifier = VerifierFactory.Create(serverPubKey); if (!verifier.VerifySignedMessage(Digest, new ArraySegment <byte>(data, 0, payloadSize + headerSize))) { throw new InvalidOperationException("The signature was invalid"); } // keep the server public key around clientState.ServerPublicKey = serverPubKey.ToArray(); // store the new nonce we got from the server clientState.InitializationNonce = nonce.ToArray(); Log.Verbose($"storing iniitlizaionta nonce: {Log.ShowBytes(nonce)}"); // we now have enough information to construct our double ratchet IKeyAgreement localStep0EcdhRatchet = KeyAgreementFactory.GenerateNew(); IKeyAgreement localStep1EcdhRatchet = KeyAgreementFactory.GenerateNew(); // initialize client root key and ecdh ratchet var genKeys = KeyDerivation.GenerateKeys(rootPreKey, clientState.InitializationNonce, 3, 32); var rootKey = genKeys[0]; var receiveHeaderKey = genKeys[1]; var sendHeaderKey = genKeys[2]; clientState.Ratchets.Add(EcdhRatchetStep.InitializeClient(KeyDerivation, Digest, rootKey, remoteRatchetEcdh0, remoteRatchetEcdh1, localStep0EcdhRatchet, receiveHeaderKey, sendHeaderKey, localStep1EcdhRatchet)); clientState.LocalEcdhForInit = null; }
private byte[] SendInitializationResponse(State state, ArraySegment <byte> initializationNonce, ArraySegment <byte> remoteEcdhForInit) { // message format: // new nonce(16), ecdh pubkey(32), // <nonce from init request(16), server pubkey(32), // new ecdh pubkey(32) x2, Padding(...), signature(64)>, mac(12) = 236 bytes if (!(state is ServerState serverState)) { throw new InvalidOperationException("Only the server can send init response."); } // generate a nonce and new ecdh parms var serverNonce = RandomNumberGenerator.Generate(InitializationNonceSize); serverState.NextInitializationNonce = serverNonce; IKeyAgreement rootPreEcdh = KeyAgreementFactory.GenerateNew(); var rootPreEcdhPubkey = rootPreEcdh.GetPublicKey(); // generate server ECDH for root key and root key var rootPreKey = rootPreEcdh.DeriveKey(remoteEcdhForInit); rootPreKey = Digest.ComputeDigest(rootPreKey); var genKeys = KeyDerivation.GenerateKeys(rootPreKey, serverNonce, 3, EcNumSize); serverState.RootKey = genKeys[0]; serverState.FirstSendHeaderKey = genKeys[1]; serverState.FirstReceiveHeaderKey = genKeys[2]; // generate two server ECDH. One for ratchet 0 sending key and one for the next // this is enough for the server to generate a receiving chain key and sending // chain key as soon as the client sends a sending chain key IKeyAgreement serverEcdhRatchet0 = KeyAgreementFactory.GenerateNew(); serverState.LocalEcdhRatchetStep0 = serverEcdhRatchet0; IKeyAgreement serverEcdhRatchet1 = KeyAgreementFactory.GenerateNew(); serverState.LocalEcdhRatchetStep1 = serverEcdhRatchet1; var minimumMessageSize = InitializationNonceSize * 2 + EcNumSize * 6 + MacSize; var entireMessageSize = Math.Max(Configuration.MinimumMessageSize, minimumMessageSize); var macOffset = entireMessageSize - MacSize; var entireMessageWithoutMacOrSignatureSize = macOffset - SignatureSize; var encryptedPayloadOffset = InitializationNonceSize + EcNumSize; var encryptedPayloadSize = macOffset - encryptedPayloadOffset; // construct the message var message = new byte[entireMessageSize]; Array.Copy(serverNonce, 0, message, 0, InitializationNonceSize); Array.Copy(rootPreEcdhPubkey, 0, message, InitializationNonceSize, EcNumSize); // construct the to-be-encrypted part var rre0 = serverEcdhRatchet0.GetPublicKey(); var rre1 = serverEcdhRatchet1.GetPublicKey(); Array.Copy(initializationNonce.Array, initializationNonce.Offset, message, encryptedPayloadOffset, InitializationNonceSize); Array.Copy(Signature.GetPublicKey(), 0, message, encryptedPayloadOffset + InitializationNonceSize, EcNumSize); Array.Copy(rre0, 0, message, encryptedPayloadOffset + InitializationNonceSize + EcNumSize, EcNumSize); Array.Copy(rre1, 0, message, encryptedPayloadOffset + InitializationNonceSize + EcNumSize * 2, EcNumSize); // sign the message var digest = Digest.ComputeDigest(message, 0, entireMessageWithoutMacOrSignatureSize); Array.Copy(Signature.Sign(digest), 0, message, entireMessageWithoutMacOrSignatureSize, SignatureSize); // encrypt the message var cipher = new AesCtrMode(AesFactory.GetAes(true, rootPreKey), serverNonce); var encryptedPayload = cipher.Process(message, encryptedPayloadOffset, encryptedPayloadSize); Array.Copy(encryptedPayload, 0, message, encryptedPayloadOffset, encryptedPayloadSize); // encrypt the header cipher = new AesCtrMode(AesFactory.GetAes(true, Configuration.ApplicationKey), encryptedPayload, encryptedPayloadSize - HeaderIVSize, HeaderIVSize); var encryptedHeader = cipher.Process(message, 0, encryptedPayloadOffset); Array.Copy(encryptedHeader, 0, message, 0, encryptedPayloadOffset); // calculate mac var Mac = new Poly(AesFactory); Mac.Init(Configuration.ApplicationKey, encryptedHeader, 0, InitializationNonceSize, MacSize); Mac.Process(message, 0, macOffset); var mac = Mac.Compute(); Array.Copy(mac, 0, message, macOffset, MacSize); return(message); }