Я понял это сейчас.
Я забыл, что в GCM зашифрованный текст имеет ту же длину, что и обычный текст ; в отличие от других режимов шифрования, таких как CB C, заполнение не требуется. Длина одноразового номера и тега определяется свойствами NonceByteSizes
и TagByteSizes
AesGcm
соответственно.
Используя это, шифрование может быть выполнено следующим образом:
public string Encrypt(string plain)
{
// Get bytes of plaintext string
byte[] plainBytes = Encoding.UTF8.GetBytes(plain);
// Get parameter sizes
int nonceSize = AesGcm.NonceByteSizes.MaxSize;
int tagSize = AesGcm.TagByteSizes.MaxSize;
int cipherSize = plainBytes.Length;
// We write everything into one big array for easier encoding
int encryptedDataLength = 4 + nonceSize + 4 + tagSize + cipherSize;
Span<byte> encryptedData = encryptedDataLength < 1024
? stackalloc byte[encryptedDataLength]
: new byte[encryptedDataLength].AsSpan();
// Copy parameters
BinaryPrimitives.WriteInt32LittleEndian(encryptedData.Slice(0, 4), nonceSize);
BinaryPrimitives.WriteInt32LittleEndian(encryptedData.Slice(4 + nonceSize, 4), tagSize);
var nonce = encryptedData.Slice(4, nonceSize);
var tag = encryptedData.Slice(4 + nonceSize + 4, tagSize);
var cipherBytes = encryptedData.Slice(4 + nonceSize + 4 + tagSize, cipherSize);
// Generate secure nonce
RandomNumberGenerator.Fill(nonce);
// Encrypt
new AesGcm(_key).Encrypt(nonce, plainBytes.AsSpan(), cipherBytes, tag);
// Encode for transmission
return Convert.ToBase64String(encryptedData);
}
Соответственно, расшифровка выполняется следующим образом:
public string Decrypt(string cipher)
{
// Decode
Span<byte> encryptedData = Convert.FromBase64String(cipher).AsSpan();
// Extract parameter sizes
int nonceSize = BinaryPrimitives.ReadInt32LittleEndian(encryptedData.Slice(0, 4));
int tagSize = BinaryPrimitives.ReadInt32LittleEndian(encryptedData.Slice(4 + nonceSize, 4));
int cipherSize = encryptedData.Length - 4 - nonceSize - 4 - tagSize;
// Extract parameters
var nonce = encryptedData.Slice(4, nonceSize);
var tag = encryptedData.Slice(4 + nonceSize + 4, tagSize);
var cipherBytes = encryptedData.Slice(4 + nonceSize + 4 + tagSize, cipherSize);
// Decrypt
Span<byte> plainBytes = cipherSize < 1024
? stackalloc byte[cipherSize]
: new byte[cipherSize];
new AesGcm(_key).Decrypt(nonce, cipherBytes, tag, plainBytes);
// Convert plain bytes back into string
return Encoding.UTF8.GetString(plainBytes);
}
См. dotnetfiddle для полной реализации и примера.
Обратите внимание, что я написал это для передачи по сети, так что все закодировано в одну большую строку base-64; В качестве альтернативы вы можете вернуть nonce
, tag
и cipherBytes
отдельно через параметры out
.
Сетевые настройки также являются причиной, по которой я отправляю одноразовые номера и размеры тегов: Возможно, используется класс различными приложениями с разными средами выполнения, которые могут иметь разные поддерживаемые размеры параметров.