初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace PCL.Core.Utils.Encryption
|
||||
{
|
||||
[Obsolete("Do not use this AES mode for Encryption")]
|
||||
public sealed class AesCbcProvider : IEncryptionProvider
|
||||
{
|
||||
public static AesCbcProvider Instance { get; } = new();
|
||||
|
||||
private const int SaltSize = 32;
|
||||
private const int IvSize = 16;
|
||||
|
||||
public byte[] Decrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key)
|
||||
{
|
||||
using var aes = Aes.Create();
|
||||
aes.KeySize = 256;
|
||||
aes.BlockSize = 128;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.PKCS7;
|
||||
|
||||
var salt = data[..SaltSize];
|
||||
|
||||
var iv = data[SaltSize..(SaltSize + IvSize)];
|
||||
aes.IV = iv.ToArray();
|
||||
|
||||
if (data.Length < salt.Length + iv.Length)
|
||||
{
|
||||
throw new ArgumentException("AES-CBC: Can not decrypt data, the encrypted data is broken");
|
||||
}
|
||||
|
||||
#pragma warning disable SYSLIB0041
|
||||
using (var deriveBytes = new Rfc2898DeriveBytes(key.ToArray(), salt.ToArray(), 1000))
|
||||
{
|
||||
aes.Key = deriveBytes.GetBytes(aes.KeySize / 8);
|
||||
}
|
||||
#pragma warning restore SYSLIB0041
|
||||
|
||||
using var ret = new MemoryStream();
|
||||
using var ms = new MemoryStream(data[(SaltSize + IvSize)..].ToArray());
|
||||
using var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Read);
|
||||
cs.CopyTo(ret);
|
||||
return ret.ToArray();
|
||||
}
|
||||
|
||||
public byte[] Encrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key)
|
||||
{
|
||||
throw new NotSupportedException("You should no longer use AES-CBC as your encryption method");
|
||||
}
|
||||
|
||||
public bool IsSupported { get => false; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace PCL.Core.Utils.Encryption
|
||||
{
|
||||
public class AesGcmProvider : IEncryptionProvider
|
||||
{
|
||||
public static AesGcmProvider Instance { get; } = new();
|
||||
|
||||
private const int NonceSize = 12; // 96 bits
|
||||
private const int TagSize = 16; // 128 bits
|
||||
|
||||
public byte[] Encrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key)
|
||||
{
|
||||
byte[] nonce = new byte[NonceSize];
|
||||
RandomNumberGenerator.Fill(nonce);
|
||||
|
||||
byte[] tag = new byte[TagSize];
|
||||
byte[] ciphertext = new byte[data.Length];
|
||||
|
||||
using (var aesGcm = new AesGcm(key, TagSize))
|
||||
{
|
||||
aesGcm.Encrypt(nonce, data, ciphertext, tag);
|
||||
}
|
||||
|
||||
// [Nonce(12)] [Tag(16)] [Ciphertext(n)]
|
||||
byte[] result = new byte[NonceSize + TagSize + ciphertext.Length];
|
||||
Buffer.BlockCopy(nonce, 0, result, 0, NonceSize);
|
||||
Buffer.BlockCopy(tag, 0, result, NonceSize, TagSize);
|
||||
Buffer.BlockCopy(ciphertext, 0, result, NonceSize + TagSize, ciphertext.Length);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public byte[] Decrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key)
|
||||
{
|
||||
if (data.Length < NonceSize + TagSize)
|
||||
throw new ArgumentException("加密数据长度不足。");
|
||||
|
||||
ReadOnlySpan<byte> nonce = data.Slice(0, NonceSize);
|
||||
ReadOnlySpan<byte> tag = data.Slice(NonceSize, TagSize);
|
||||
ReadOnlySpan<byte> ciphertext = data.Slice(NonceSize + TagSize);
|
||||
|
||||
byte[] plaintext = new byte[ciphertext.Length];
|
||||
|
||||
using (var aesGcm = new AesGcm(key, TagSize))
|
||||
{
|
||||
aesGcm.Decrypt(nonce, ciphertext, tag, plaintext);
|
||||
}
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
public bool IsSupported { get => AesGcm.IsSupported; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace PCL.Core.Utils.Encryption;
|
||||
|
||||
public sealed class ChaCha20Poly1305Provider : IEncryptionProvider
|
||||
{
|
||||
public static ChaCha20Poly1305Provider Instance { get; } = new();
|
||||
|
||||
private const int NonceSize = 12; // 96-bit nonce for ChaCha20Poly1305
|
||||
private const int TagSize = 16; // 128-bit authentication tag
|
||||
private const int KeySize = 32; // 256-bit key
|
||||
private const int SaltSize = 16; // 128-bit salt for HKDF
|
||||
|
||||
public byte[] Encrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key)
|
||||
{
|
||||
// Generate random salt, nonce and the tag
|
||||
var salt = new byte[SaltSize];
|
||||
var nonce = new byte[NonceSize];
|
||||
var tag = new byte[TagSize];
|
||||
RandomNumberGenerator.Fill(salt);
|
||||
RandomNumberGenerator.Fill(nonce);
|
||||
RandomNumberGenerator.Fill(tag);
|
||||
|
||||
// Derive key using the salt
|
||||
Span<byte> outputKey = stackalloc byte[KeySize];
|
||||
_DeriveKey(key, salt, outputKey);
|
||||
using var chacha = new ChaCha20Poly1305(outputKey);
|
||||
|
||||
// Prepare output arrays
|
||||
var ciphertext = new byte[data.Length];
|
||||
|
||||
// Perform encryption
|
||||
chacha.Encrypt(nonce, data, ciphertext, tag);
|
||||
|
||||
// Make the encryption data: salt + nonce + tag + ciphertext
|
||||
var result = new byte[SaltSize + NonceSize + ciphertext.Length + TagSize];
|
||||
var resultSpan = result.AsSpan();
|
||||
|
||||
salt.CopyTo(resultSpan[..SaltSize]);
|
||||
nonce.CopyTo(resultSpan.Slice(SaltSize, NonceSize));
|
||||
tag.CopyTo(resultSpan.Slice(SaltSize + NonceSize, TagSize));
|
||||
ciphertext.CopyTo(resultSpan.Slice(SaltSize + NonceSize + TagSize, ciphertext.Length));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public byte[] Decrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key)
|
||||
{
|
||||
// Verify minimum data length
|
||||
if (data.Length < SaltSize + NonceSize + TagSize)
|
||||
throw new ArgumentException("Invalid encrypted data length");
|
||||
|
||||
// Encryption data: salt + nonce + tag + ciphertext
|
||||
var salt = data[..SaltSize];
|
||||
var nonce = data.Slice(SaltSize, NonceSize);
|
||||
var tag = data.Slice(SaltSize + NonceSize, TagSize);
|
||||
var ciphertext = data[(SaltSize + NonceSize + TagSize)..];
|
||||
|
||||
// Derive key using the extracted salt
|
||||
Span<byte> outputKey = stackalloc byte[KeySize];
|
||||
_DeriveKey(key, salt, outputKey);
|
||||
using var chacha = new ChaCha20Poly1305(outputKey);
|
||||
|
||||
// Perform decryption
|
||||
var plaintext = new byte[ciphertext.Length];
|
||||
chacha.Decrypt(nonce, ciphertext, tag, plaintext);
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
private static readonly byte[] _Info = "PCL.Core.Utils.Encryption.ChaCha20"u8.ToArray();
|
||||
private static void _DeriveKey(ReadOnlySpan<byte> ikm, ReadOnlySpan<byte> salt, Span<byte> outputKey)
|
||||
{
|
||||
HKDF.DeriveKey(
|
||||
HashAlgorithmName.SHA256,
|
||||
ikm,
|
||||
outputKey,
|
||||
salt,
|
||||
_Info.AsSpan());
|
||||
}
|
||||
|
||||
public bool IsSupported { get => ChaCha20Poly1305.IsSupported; }
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace PCL.Core.Utils.Encryption;
|
||||
|
||||
public class ChaCha20SoftwareProvider : IEncryptionProvider
|
||||
{
|
||||
public static ChaCha20SoftwareProvider Instance { get; } = new();
|
||||
|
||||
// 常量:expand 32-byte k
|
||||
private static ReadOnlySpan<uint> Sigma => new[] { 0x61707865u, 0x3320646eu, 0x79622d32u, 0x6b206574u };
|
||||
|
||||
public static bool IsSupported => true;
|
||||
|
||||
public byte[] Encrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key)
|
||||
{
|
||||
// 预留 12 字节 Nonce 空间
|
||||
var result = new byte[data.Length + 12];
|
||||
var nonce = result.AsSpan(0, 12);
|
||||
|
||||
// 生成随机 Nonce
|
||||
RandomNumberGenerator.Fill(nonce);
|
||||
|
||||
_Process(data, result.AsSpan(12), key, nonce);
|
||||
return result;
|
||||
}
|
||||
|
||||
public byte[] Decrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key)
|
||||
{
|
||||
if (data.Length < 12) throw new ArgumentException("数据长度不足以包含 Nonce");
|
||||
|
||||
var nonce = data[..12];
|
||||
var ciphertext = data[12..];
|
||||
var plaintext = new byte[ciphertext.Length];
|
||||
|
||||
_Process(ciphertext, plaintext, key, nonce);
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
private static void _Process(ReadOnlySpan<byte> input, Span<byte> output, ReadOnlySpan<byte> key, ReadOnlySpan<byte> nonce)
|
||||
{
|
||||
if (key.Length != 32) throw new ArgumentException("Key 必须为 32 字节");
|
||||
if (nonce.Length != 12) throw new ArgumentException("Nonce 必须为 12 字节");
|
||||
|
||||
// 在栈上分配状态矩阵和工作块,避免 GC
|
||||
Span<uint> state = stackalloc uint[16];
|
||||
|
||||
// 1. 初始化状态
|
||||
Sigma.CopyTo(state[..4]);
|
||||
for (var i = 0; i < 8; i++)
|
||||
{
|
||||
state[4 + i] = BinaryPrimitives.ReadUInt32LittleEndian(key.Slice(i * 4, 4));
|
||||
}
|
||||
state[12] = 0; // 计数器初始值
|
||||
state[13] = BinaryPrimitives.ReadUInt32LittleEndian(nonce[..4]);
|
||||
state[14] = BinaryPrimitives.ReadUInt32LittleEndian(nonce[4..8]);
|
||||
state[15] = BinaryPrimitives.ReadUInt32LittleEndian(nonce[8..12]);
|
||||
|
||||
Span<uint> workingBlock = stackalloc uint[16];
|
||||
var offset = 0;
|
||||
var length = input.Length;
|
||||
|
||||
while (offset < length)
|
||||
{
|
||||
_GenerateBlock(workingBlock, state);
|
||||
state[12]++; // 递增计数器
|
||||
|
||||
var remaining = Math.Min(64, length - offset);
|
||||
var inputPart = input.Slice(offset, remaining);
|
||||
var outputPart = output.Slice(offset, remaining);
|
||||
|
||||
// 将 uint 块视为字节流进行异或
|
||||
var blockBytes = MemoryMarshal.AsBytes(workingBlock);
|
||||
for (var i = 0; i < remaining; i++)
|
||||
{
|
||||
outputPart[i] = (byte)(inputPart[i] ^ blockBytes[i]);
|
||||
}
|
||||
|
||||
offset += 64;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void _GenerateBlock(Span<uint> x, ReadOnlySpan<uint> input)
|
||||
{
|
||||
input.CopyTo(x);
|
||||
|
||||
for (var i = 0; i < 10; i++) // 20 轮计算
|
||||
{
|
||||
// 列变换
|
||||
_QuarterRound(x, 0, 4, 8, 12);
|
||||
_QuarterRound(x, 1, 5, 9, 13);
|
||||
_QuarterRound(x, 2, 6, 10, 14);
|
||||
_QuarterRound(x, 3, 7, 11, 15);
|
||||
// 对角线变换
|
||||
_QuarterRound(x, 0, 5, 10, 15);
|
||||
_QuarterRound(x, 1, 6, 11, 12);
|
||||
_QuarterRound(x, 2, 7, 8, 13);
|
||||
_QuarterRound(x, 3, 4, 9, 14);
|
||||
}
|
||||
|
||||
for (var i = 0; i < 16; i++)
|
||||
{
|
||||
x[i] += input[i];
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void _QuarterRound(Span<uint> x, int a, int b, int c, int d)
|
||||
{
|
||||
x[a] += x[b]; x[d] ^= x[a]; x[d] = BitOperations.RotateLeft(x[d], 16);
|
||||
x[c] += x[d]; x[b] ^= x[c]; x[b] = BitOperations.RotateLeft(x[b], 12);
|
||||
x[a] += x[b]; x[d] ^= x[a]; x[d] = BitOperations.RotateLeft(x[d], 8);
|
||||
x[c] += x[d]; x[b] ^= x[c]; x[b] = BitOperations.RotateLeft(x[b], 7);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace PCL.Core.Utils.Encryption;
|
||||
|
||||
public interface IEncryptionProvider
|
||||
{
|
||||
public byte[] Encrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key);
|
||||
public byte[] Decrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key);
|
||||
|
||||
public static bool IsSupported { get; }
|
||||
}
|
||||
Reference in New Issue
Block a user