//Can we change the ChaCha20Poly1305 input to some kind of ICrypto interface or action with 'Encrypt' and 'Decrypt'? private void Pack(IBufferWriter <byte> output, CryptoDtoHeaderDto header, ChaCha20Poly1305 crypto, ReadOnlySpan <byte> dtoNameBuffer, ReadOnlySpan <byte> dtoBuffer) { lock (bufferLock) { headerBuffer.Clear(); MessagePackSerializer.Serialize(headerBuffer, header); ReadOnlySpan <byte> headerBytes = headerBuffer.WrittenSpan; ushort headerLength = (ushort)headerBytes.Length; BinaryPrimitives.WriteUInt16LittleEndian(headerLengthBytes, headerLength); ushort dtoNameLength = (ushort)dtoNameBuffer.Length; BinaryPrimitives.WriteUInt16LittleEndian(dtoNameLengthBytes, dtoNameLength); ushort dtoLength = (ushort)dtoBuffer.Length; BinaryPrimitives.WriteUInt16LittleEndian(dtoLengthBytes, dtoLength); switch (header.Mode) { case CryptoDtoMode.ChaCha20Poly1305: { int adLength = 2 + headerLength; int aeLength = 2 + dtoNameLength + 2 + dtoLength; // Copy data into associated data buffer adBuffer.Clear(); adBuffer.Write(headerLengthBytes); adBuffer.Write(headerBytes); // Copy data into authenticated encryption buffer aeBuffer.Clear(); aeBuffer.Write(dtoNameLengthBytes); aeBuffer.Write(dtoNameBuffer); aeBuffer.Write(dtoLengthBytes); aeBuffer.Write(dtoBuffer); Span <byte> nonceSpan = new Span <byte>(nonceBytes); BinaryPrimitives.WriteUInt64LittleEndian(nonceSpan.Slice(4), header.Sequence); var adSpan = adBuffer.WrittenSpan; var aeSpan = aeBuffer.WrittenSpan; var cipherTextSpan = cipherText.Span.Slice(0, aeLength); cipherTextSpan.Clear(); Span <byte> tagSpan = tagBuffer; tagSpan.Clear(); crypto.Encrypt(nonceSpan, aeSpan, cipherTextSpan, tagSpan, adSpan); output.Write(adSpan); output.Write(cipherTextSpan); output.Write(tagSpan); break; } default: throw new CryptographicException("Mode not recognised"); } } }
//[Obsolete] //public byte[] Pack(CryptoDtoChannelStore channelStore, string channelTag, CryptoDtoMode mode, ReadOnlySpan<byte> dtoNameBuffer, ReadOnlySpan<byte> dtoBuffer) //{ // var channel = channelStore.GetChannel(channelTag); // return Pack(channel, mode, dtoNameBuffer, dtoBuffer); //} //[Obsolete] //public byte[] Pack(CryptoDtoChannel channel, CryptoDtoMode mode, ReadOnlySpan<byte> dtoNameBuffer, ReadOnlySpan<byte> dtoBuffer) //{ // ArrayBufferWriter<byte> arrayBufferWriter = new ArrayBufferWriter<byte>(); // Pack(arrayBufferWriter, channel, mode, dtoNameBuffer, dtoBuffer); // return arrayBufferWriter.WrittenSpan.ToArray(); //} public void Pack(IBufferWriter <byte> output, CryptoDtoChannel channel, CryptoDtoMode mode, ReadOnlySpan <byte> dtoNameBuffer, ReadOnlySpan <byte> dtoBuffer) { channel.GetTransmitKey(mode, out ulong sequenceToSend); CryptoDtoHeaderDto header = new CryptoDtoHeaderDto { ChannelTag = channel.ChannelTag, Mode = mode, Sequence = sequenceToSend }; Pack(output, header, channel.TransmitChaCha20Poly1305, dtoNameBuffer, dtoBuffer); }
private static byte[] Pack(string channelTag, CryptoDtoMode mode, ReadOnlySpan <byte> transmitKey, ulong sequenceToBeSent, byte[] dtoNameBuffer, byte[] dtoBuffer) { CryptoDtoHeaderDto header = new CryptoDtoHeaderDto { ChannelTag = channelTag, Sequence = sequenceToBeSent, Mode = mode }; var headerBuffer = MessagePackSerializer.Serialize(header); ushort headerLength = (ushort)headerBuffer.Length; ushort dtoNameLength = (ushort)dtoNameBuffer.Length; ushort dtoLength = (ushort)dtoBuffer.Length; switch (header.Mode) { case CryptoDtoMode.ChaCha20Poly1305: { int aePayloadLength = 2 + dtoNameLength + 2 + dtoLength; var aePayloadBuffer = new byte[aePayloadLength]; Array.Copy(BitConverter.GetBytes(dtoNameLength), 0, aePayloadBuffer, 0, 2); Array.Copy(dtoNameBuffer, 0, aePayloadBuffer, 2, dtoNameLength); Array.Copy(BitConverter.GetBytes(dtoLength), 0, aePayloadBuffer, 2 + dtoNameLength, 2); Array.Copy(dtoBuffer, 0, aePayloadBuffer, 2 + dtoNameLength + 2, dtoLength); int adPayloadLength = 2 + headerLength; var adPayloadBuffer = new byte[adPayloadLength]; Array.Copy(BitConverter.GetBytes(headerLength), 0, adPayloadBuffer, 0, 2); Array.Copy(headerBuffer, 0, adPayloadBuffer, 2, headerLength); Span <byte> nonceBuffer = stackalloc byte[Aead.NonceSize]; BinaryPrimitives.WriteUInt64LittleEndian(nonceBuffer.Slice(4), header.Sequence); var aead = new ChaCha20Poly1305(transmitKey.ToArray()); byte[] aeadPayload = aead.Encrypt(aePayloadBuffer, adPayloadBuffer, nonceBuffer); int aeadPayloadLength = aeadPayload.Length; byte[] packetBuffer = new byte[2 + headerLength + aeadPayloadLength]; Array.Copy(BitConverter.GetBytes(headerLength), 0, packetBuffer, 0, 2); Array.Copy(headerBuffer, 0, packetBuffer, 2, headerLength); Array.Copy(aeadPayload, 0, packetBuffer, 2 + headerLength, aeadPayloadLength); return(packetBuffer); } default: throw new CryptographicException("Mode not recognised"); } }
internal Deserializer(CryptoDtoChannelStore channelStore, ReadOnlySpan <byte> bytes, bool ignoreSequence) { sequenceValid = false; headerLength = Unsafe.ReadUnaligned <ushort>(ref MemoryMarshal.GetReference(bytes)); //.NET Standard 2.0 doesn't have BitConverter.ToUInt16(Span<T>) if (bytes.Length < (2 + headerLength)) { throw new CryptographicException("Not enough bytes to process packet."); } ReadOnlySpan <byte> headerDataBuffer = bytes.Slice(2, headerLength); header = MessagePackSerializer.Deserialize <CryptoDtoHeaderDto>(headerDataBuffer.ToArray()); ReadOnlySpan <byte> receiveKey = channelStore.GetReceiveKey(header.ChannelTag, header.Mode); //This will throw exception if channel tag isn't in the store switch (header.Mode) { case CryptoDtoMode.ChaCha20Poly1305: { int aeLength = bytes.Length - (2 + headerLength); ReadOnlySpan <byte> aePayloadBuffer = bytes.Slice(2 + headerLength, aeLength); ReadOnlySpan <byte> adBuffer = bytes.Slice(0, 2 + headerLength); Span <byte> nonceBuffer = stackalloc byte[Aead.NonceSize]; BinaryPrimitives.WriteUInt64LittleEndian(nonceBuffer.Slice(4), header.Sequence); var aead = new ChaCha20Poly1305(receiveKey.ToArray()); ReadOnlySpan <byte> decryptedPayload = aead.Decrypt(aePayloadBuffer.ToArray(), adBuffer.ToArray(), nonceBuffer); if (ignoreSequence) { sequenceValid = channelStore.IsReceivedSequenceAllowed(header.ChannelTag, header.Sequence); } else { channelStore.CheckReceivedSequence(header.ChannelTag, header.Sequence); //The packet has passed MAC, so now check if it's being duplicated or replayed sequenceValid = true; } dtoNameLength = Unsafe.ReadUnaligned <ushort>(ref MemoryMarshal.GetReference(decryptedPayload)); //.NET Standard 2.0 doesn't have BitConverter.ToUInt16(Span<T>) dtoNameBuffer = decryptedPayload.Slice(2, dtoNameLength); dataLength = Unsafe.ReadUnaligned <ushort>(ref MemoryMarshal.GetReference(decryptedPayload.Slice(2 + dtoNameLength, 2))); //.NET Standard 2.0 doesn't have BitConverter.ToUInt16(Span<T>) dataBuffer = decryptedPayload.Slice(2 + dtoNameLength + 2, dataLength); break; } default: throw new CryptographicException("Mode not recognised"); } }