TinkStreamingAead 1.0.1
dotnet add package TinkStreamingAead --version 1.0.1
NuGet\Install-Package TinkStreamingAead -Version 1.0.1
<PackageReference Include="TinkStreamingAead" Version="1.0.1" />
<PackageVersion Include="TinkStreamingAead" Version="1.0.1" />
<PackageReference Include="TinkStreamingAead" />
paket add TinkStreamingAead --version 1.0.1
#r "nuget: TinkStreamingAead, 1.0.1"
#:package TinkStreamingAead@1.0.1
#addin nuget:?package=TinkStreamingAead&version=1.0.1
#tool nuget:?package=TinkStreamingAead&version=1.0.1
TinkStreamingAead
Open-source .NET port of Google Tink's AES256-GCM-HKDF Streaming AEAD.
Fully interoperable with Google Tink (Java, C++, Go, Python) — encrypt in .NET, decrypt in any Tink language, and vice versa.
Features
- Tink wire-format compatible — Uses the exact AES256-GCM-HKDF-1MB streaming format.
- Streaming — Encrypt/decrypt multi-gigabyte files without loading them into memory.
- Three API styles:
- Static convenience (
Encrypt/Decrypt) — straightforward sync/async calls - Lazy streams (
CreateEncryptStream/CreateDecryptStream) — compose withGZipStream,CryptoStream, etc. - Simplified facade (
TinkCrypto) — set the key once, one-liner file operations
- Static convenience (
- Associated Data (AAD) — Authenticate metadata alongside the ciphertext.
- Cross-framework — Targets
netstandard2.0(.NET Framework 4.8+) andnet6.0. - Zero dependencies on modern .NET — Uses
Portable.BouncyCastleonnetstandard2.0whereAesGcmisn't available; nativeAesGcmonnet6.0+.
Installation
dotnet add package TinkStreamingAead
Or via Package Manager:
Install-Package TinkStreamingAead
Quick Start
Encrypt and decrypt a file (one-liner with TinkCrypto)
using TinkStreamingAead;
// Generate a key (save this securely!)
byte[] key = TinkCrypto.GenerateKey();
// Create a crypto instance
var crypto = new TinkCrypto(key);
// Encrypt a file
crypto.EncryptFile("document.pdf", "document.pdf.enc");
// Decrypt it back
crypto.DecryptFile("document.pdf.enc", "document_decrypted.pdf");
// For multi-threaded scenarios, create one instance per thread
var workerCrypto = new TinkCrypto(key);
Async with progress reporting
var progress = new Progress<long>(bytes => Console.WriteLine($"Processed: {bytes} bytes"));
await Aes256GcmHkdf1MB.EncryptAsync(
key,
inputStream,
outputStream,
aad: null,
progress: progress,
cancellationToken: ct);
Lazy streaming (compose with other streams)
using var encryptor = Aes256GcmHkdf1MB.CreateEncryptStream(key, fileStream);
using var gzip = new System.IO.Compression.GZipStream(encryptor, CompressionMode.Compress);
// Write to gzip → automatically encrypted to fileStream
In-memory encryption
var crypto = new TinkCrypto(key);
byte[] ciphertext = crypto.EncryptBytes(Encoding.UTF8.GetBytes("Hello, World!"));
byte[] plaintext = crypto.DecryptBytes(ciphertext);
With associated data (AAD)
byte[] aad = Encoding.UTF8.GetBytes("session-12345");
// Encrypt with AAD
Aes256GcmHkdf1MB.Encrypt(key, plaintextStream, ciphertextStream, aad);
// Decrypt must use the same AAD, or it fails
Aes256GcmHkdf1MB.Decrypt(key, ciphertextStream, plaintextStream, aad);
Predict ciphertext size
long plainLen = new FileInfo("large_file.dat").Length;
long cipherLen = Aes256GcmHkdf1MB.GetCiphertextLength(plainLen);
Console.WriteLine($"Ciphertext will be {cipherLen} bytes");
Extract key from Tink JSON keyset
string keysetJson = File.ReadAllText("tink-keyset.json");
byte[] key = TinkCrypto.ExtractKeyFromJsonKeyset(keysetJson);
var crypto = new TinkCrypto(key);
crypto.DecryptFile("encrypted.bin", "decrypted.bin");
Lazy file streaming
var crypto = new TinkCrypto(key);
// Encrypt directly to a file without buffering everything
using var encryptor = crypto.CreateEncryptFileStream("output.enc");
encryptor.Write(plaintextBytes, 0, plaintextBytes.Length);
// File is finalized on Dispose
// Decrypt directly from a file
using var decryptor = crypto.CreateDecryptFileStream("output.enc");
using var ms = new MemoryStream();
decryptor.CopyTo(ms);
byte[] result = ms.ToArray();
API Reference
Aes256GcmHkdf1MB (core static class)
| Method | Description |
|---|---|
Encrypt(key, plaintext, ciphertext, aad?) |
Synchronous all-in-one encryption |
Decrypt(key, ciphertext, plaintext, aad?) |
Synchronous all-in-one decryption |
EncryptAsync(key, plaintext, ciphertext, aad?, progress?, ct) |
Async encryption with progress & cancellation |
DecryptAsync(key, ciphertext, plaintext, aad?, progress?, ct) |
Async decryption with progress & cancellation |
CreateEncryptStream(key, ciphertext, aad?) |
Returns a writable Stream — write plaintext, get ciphertext |
CreateDecryptStream(key, ciphertext, aad?) |
Returns a readable Stream — read plaintext from ciphertext |
GetCiphertextLength(plaintextLength) |
Predicts total ciphertext size for a given plaintext length |
TinkCrypto (instance-based wrapper, implements IDisposable)
Constructor: new TinkCrypto(key, associatedData?) — creates an instance with a 32-byte key.
| Method | Description |
|---|---|
Key / AssociatedData |
The key (read-only) and optional AAD (read-write) |
EncryptFile(inPath, outPath) / DecryptFile(inPath, outPath) |
File-to-file operations |
EncryptFileAsync(...) / DecryptFileAsync(...) |
Async file operations with progress & cancellation |
EncryptBytes(data) / DecryptBytes(data) |
In-memory byte array operations |
EncryptStream(src, dst) / DecryptStream(src, dst) |
Stream-to-stream operations |
EncryptStreamAsync(...) / DecryptStreamAsync(...) |
Async stream operations with progress & cancellation |
CreateEncryptStream(dst) / CreateDecryptStream(src) |
Lazy streaming wrappers (disposing them disposes the underlying stream) |
CreateEncryptFileStream(path) / CreateDecryptFileStream(path) |
Lazy file-to-file streaming wrappers |
Dispose() |
Zeroes the key and marks the instance as disposed |
Static utilities:
| Method | Description |
|---|---|
TinkCrypto.GenerateKey() |
Generates a random 32-byte AES-256 key |
TinkCrypto.ExtractKeyFromJsonKeyset(json) |
Extracts the raw key from a Google Tink JSON keyset |
Note:
TinkCryptoinstances are not thread-safe — use one instance per thread. For stateless multi-threaded use, callAes256GcmHkdf1MBmethods directly with per-call keys.
Cryptographic Details
| Property | Value |
|---|---|
| Algorithm | AES-256-GCM |
| Key size | 32 bytes (256 bits) |
| Key derivation | HKDF-SHA256 (Extract + Expand) |
| Segment size | 1,048,576 bytes (1 MiB) ciphertext |
| GCM tag | 16 bytes per segment |
| Nonce | 12 bytes: 7B prefix + 4B counter (BE) + 1B last-flag |
| Header | 40 bytes: 0x28 + 32B salt + 7B nonce prefix |
The implementation follows the Google Tink Streaming AEAD specification exactly.
Interoperability
Ciphertexts produced by this library can be decrypted by:
- Google Tink (Java, C++, Go, Python)
- Tinkey CLI tool
- The test suite verifies interoperability against ciphertexts generated by Google Tink (Python).
Testing
The test project (tests/TinkStreamingAead.Tests/) targets net8.0 and net48 and includes:
# Run all tests
dotnet test
# Run a specific category
dotnet test --filter "FullyQualifiedName~GoldenVectorTests"
dotnet test --filter "FullyQualifiedName~TinkInteropTests"
Test Categories
| Test Suite | What it covers |
|---|---|
GoldenVectorTests |
Deterministic byte-level verification against known ciphertext |
TinkInteropTests |
Decrypts ciphertexts produced by Google Tink (Python) |
RoundTripTests |
Encrypt-then-decrypt for 0 B → 50 MiB |
AsyncRoundTripTests |
Async encrypt/decrypt with progress reporting & cancellation |
LazyStreamTests |
Incremental reads/writes, GZipStream composition |
EdgeCaseTests |
Null/wrong-key/malformed/truncated/corrupted inputs |
HkdfTests |
RFC 5869 test vectors |
LargeFileRoundTripTests |
Powers of ten up to 4 GB, boundary cases, GetCiphertextLength, keyset extraction, lazy file streams, IDisposable semantics |
Building from Source
# Clone the repository
git clone https://github.com/salehalakhras/TinkStreamingAead.git
cd TinkStreamingAead
# Build
dotnet build
# Run tests
dotnet test
# Create NuGet package
dotnet pack src/TinkStreamingAead/TinkStreamingAead.csproj -c Release
License
MIT License
Copyright (c) 2026 Saleh Alakhras
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Portable.BouncyCastle (>= 1.9.0)
- System.Memory (>= 4.5.5)
-
net6.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
1.0.1:
- Hardened: constant-time key zeroing, CryptographicException on tag mismatch across all
targets, canonical full-final-segment encoding for exact-multiple inputs (lazy streams),
and a 2^32-segment nonce-overflow guard.
- Verified against Google Tink (Python) ciphertexts and RFC 5869 vectors on net8.0 and net48.