-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[API Proposal]: Add Span<byte> support to System.Security.Cryptography.ProtectedData #108734
Comments
Tagging subscribers to this area: @dotnet/area-system-security, @bartonjs, @vcsjones |
I believe that Unprotect always reduces the size of the input, so byte[] dest = new byte[source.Length];
ProtectedData.TryUnprotect(source, dest, ...); is always guaranteed to return true. In that case, it's probably goodness to have the non-Try variant... and it might also make sense for Protect. public partial class ProtectedData
{
public static int Protect(ReadOnlySpan<byte> source, Span<byte> destination, DataProtectionScope scope);
public static int Unprotect(ReadOnlySpan<byte> source, Span<byte> destination, DataProtectionScope scope);
} |
Looks like the optionalEntropy parameter got dropped? It can move to the final position and default to the default (empty) ReadOnlySpan |
@bartonjs the S.S.C.ProtectedData API and package are marked as "accepts critical fixes only" So, we could consider Span APIs here, but, at first glance doesn't seem like it meets the bar. |
Given the simplicity of it, and that the ability to write to pre-pinned memory might be critical... I think it's goodness to take it. |
Fair enough. It sounds like you are proposing this. To throw it out there, if we are adding two of them, do we want to go for our "quadfecta" and add a span in, allocating byte out? namespace System.Security.Cryptography;
public partial class ProtectedData {
public static bool TryProtect(
ReadOnlySpan<byte> userData,
DataProtectionScope scope,
Span<byte> destination,
out int bytesWritten,
ReadOnlySpan<byte> optionalEntropy = default);
public static bool TryUnprotect(
ReadOnlySpan<byte> userData,
DataProtectionScope scope,
Span<byte> destination,
out int bytesWritten,
ReadOnlySpan<byte> optionalEntropy = default);
public static int Protect(
ReadOnlySpan<byte> userData,
DataProtectionScope scope,
Span<byte> destination,
ReadOnlySpan<byte> optionalEntropy = default);
public static int Unprotect(
ReadOnlySpan<byte> userData,
DataProtectionScope scope,
Span<byte> destination,
ReadOnlySpan<byte> optionalEntropy = default);
// Below: Maybe?
public static byte[] Protect(
ReadOnlySpan<byte> userData,
DataProtectionScope scope,
ReadOnlySpan<byte> optionalEntropy = default);
public static byte[] Unprotect(
ReadOnlySpan<byte> userData,
DataProtectionScope scope,
ReadOnlySpan<byte> optionalEntropy = default);
} |
It's starting to feel like overkill, but may as well take the full set to review. Was flipping destination and scope intentional? (source, destination, scope, out written, optionalEntropy=default) looks like the right order to me given RSA.TrySignHash (the example in FDGv3 for how to order the parameters), but maybe we've recently felt differently about that order and you're remembering something I'm not? |
I waffled on that. I used AesGcm and SP800108 as inspiration. In order, I end up thinking things as
Which is how GCM looks public void Decrypt (ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> ciphertext,
ReadOnlySpan<byte> tag,
Span<byte> plaintext, ReadOnlySpan<byte> associatedData = default); Granted, scope feels a little different because it isn't an input into an algorithm itself, but I didn't care to make the distinction. |
One thing to consider here, is that
@ChadNedzlek was your expectation to be able to use the span-overloads from a down level .NET platform? |
@vcsjones No, it was not. Only forward versions of .NET |
namespace System.Security.Cryptography;
public partial class ProtectedData {
public static bool TryProtect(
ReadOnlySpan<byte> userData,
Span<byte> destination,
DataProtectionScope scope,
out int bytesWritten,
ReadOnlySpan<byte> optionalEntropy = default);
public static bool TryUnprotect(
ReadOnlySpan<byte> encryptedData,
Span<byte> destination,
DataProtectionScope scope,
out int bytesWritten,
ReadOnlySpan<byte> optionalEntropy = default);
public static int Protect(
ReadOnlySpan<byte> userData,
Span<byte> destination,
DataProtectionScope scope,
ReadOnlySpan<byte> optionalEntropy = default);
public static int Unprotect(
ReadOnlySpan<byte> encryptedData,
Span<byte> destination,
DataProtectionScope scope,
ReadOnlySpan<byte> optionalEntropy = default);
public static byte[] Protect(
ReadOnlySpan<byte> userData,
DataProtectionScope scope,
ReadOnlySpan<byte> optionalEntropy = default);
public static byte[] Unprotect(
ReadOnlySpan<byte> encryptedData,
DataProtectionScope scope,
ReadOnlySpan<byte> optionalEntropy = default);
} |
@ChadNedzlek Jeremy mentioned you were looking to implement this, so I'll assign this to you for now. Feel free to unassign yourself if that is no the case. |
Got the code written, now just need to copy paste the existing tests 6 times. :-) |
Simple functional tests can avoid duplication by changing the current class to an abstract, making all of the test methods instance, and deciding on a common shape to do the verification with. Then use derived classes to adapt the one form to another. Usually our "least common denominator" is the array-in/array-out version. Like in https://github.com/dotnet/runtime/blob/main/src/libraries/System.Security.Cryptography/tests/HKDFTests.cs. Then, for things like "the Try returns false/0 for too small" (and the non-Try throws) and a particular test show that it works for a goldilocks-sized buffer and a too-big buffer (accuracy of length was verified by the other tests, really), methods on the appropriate derived type (which the derived types at the bottom of the HKDFTests do). |
Background and motivation
Most cryptographic operations in .NET support passing spans for input and output, however, the ProtectedData type does not,
and still requires allocation of arrays for both input and output.
API Proposal
API Usage
Alternative Designs
No response
Risks
Should be low risk, it's adding Span overrides to a byte[] input/output API much like has been done for most other crypto operations.
The text was updated successfully, but these errors were encountered: