TryParsePkcs1Pss Method
TryParsePkcs1Pss(ReadOnlySpan<Byte>, ReadOnlySpan<Byte>, Int32, out Byte[], out Boolean)
Try to parse the formattedSignature
as a PKCS #1 v2 PSS block
for verifying (see RFC 8017).
public static bool TryParsePkcs1Pss(ReadOnlySpan<byte> formattedSignature, ReadOnlySpan<byte> digest, int digestAlgorithm, out byte[] mPrimeAndH, out bool isVerified)
Parameters
Type | Name | Description |
---|---|---|
System.ReadOnlySpan<System.Byte> | formattedSignature | The data to parse. |
System.ReadOnlySpan<System.Byte> | digest | The computed digest, the digest of the message, the data to verify. |
System.Int32 | digestAlgorithm | The digest algorithm used to compute the digest, it must be one of
the supported algorithms: |
System.Byte[] | mPrimeAndH | An output argument, a new byte array containing M-prime concatenated
with the H value extracted from the |
System.Boolean | isVerified | An output argument, a boolean reporting whether the signature verified or not. |
Returns
True
if the method is able to parse, false
otherwise.
Remarks
Note that this method will not return the digest of the data. In fact, the caller must supply the digest. The reason is that the digest of the data (the foundation of the signature) is not part of the formatted data. Rather, the formatted data contains a value derived from the digest.
With earlier RSA padding schemes (such as PKCS #1 v1.5), the signer digested the message (call this the signer's digest), and formatted that digest into a block. That block was encrypted using the private key.
Verification required the verifier to digest the message (call this the verifier's digest), use the public key to decrypt the signature, parse the decrypted data to extract the digest (the signer's digest), then compare (memcmp) the two digests. If the signer's and verifier's digests match, the signature verified.
Signer:
message --> Digest(message) --> signer's digest
signer's digest --> format --> RSA(privateKey, formattedData) --> signature
send message + signature
Verifier:
message --> Digest(message) --> verifier's digest
signature --> RSA(publicKey, signature) --> formattedDigest
formattedDigest --> parse --> signer's digest
compare verifier's digest with signer's digest
In contrast, with PSS the signer digests the message, derives a Hash value (known as H) from the message digest, and builds a formatted block using H (and other information used in the derivation process). That block is encrypted using the private key, producing the signature.
To verify a signature, the verifier digests the message to produce the digest. Next, decrypt the signature to obtain the formatted data. Parse that data to obtain H (the signer's H, the value derived from the signer's digest) along with other information needed to perform the derivation operation. Next, the verifier performs the same derivation operation using the verifier's digest and information from the formatted signature (computing the verifier's H), and compares that result with the value from the parsed block (the signer's H).
Signer:
message --> Digest(message) --> signer's digest
signer's digest --> derivation(salt, digest) --> signer's H
signer's H --> format, includes salt and is masked --> formattedData
formattedData --> RSA(privateKey, formattedData) --> signature
send message + signature
Verifier:
message --> Digest(message) --> verifier's digest
signature --> RSA(publicKey, signature) --> formattedData
formattedData --> parse, unmask, extract elements --> salt and signer's H
verifier's digest --> derivation(salt, digest) --> verifier's H
compare verifier's H with signer's H
This method will perform the derivation operation, which is why the caller must supply the digest.
This method will parse the formatted block, obtaining the information
necessary to perform the derivation operation. It will use the digest
value provided to derive an H value. It will then compare the H value
it derived with the H value from the formatted block. If they are the
same, it will set the isVerified
argument to true
. If
not, the method will set that value to false
.
The method will verify that the parsed data matches the PSS
specifications. If not, then it might not derive the H value and
simply set isVerified
to false
. For example, the PSS
standard requires the most significant bit of the formatted data to
be 0, and the "unmasked" PS to be all 00 bytes.
This method will assume the salt length is the same as the digest
length. Although the salt length does not have to be the same as the
digest length, the standard recommends doing so. If the signature you
are verifying does not use the digest length as the salt length, you
cannot use this method. Note that the C# PSS implementation (see the
System.Security.Cryptography.RSA
class) uses the digest length
as the salt length exclusively, the same as this method.
This method will use the message digest implementations from CryptographyProviders.
The method will also return the "M-prime" value followed by the H value.
M-prime || H
M-prime is (2 * digest length) + 8 bytes long,
H is digest length bytes long
M-prime is the value used to derive the H value. It consists of
00 00 00 00 00 00 00 00 || digest || salt
eight 00 bytes, then the digest, then digest length bytes of salt
Hence, the mPrimeAndH
value returned is
00 00 00 00 00 00 00 00 || digest || salt || H
8 bytes dLen dLen dLen
For example, if the digest is SHA-256, then the
digest length is 32.
00 00 00 00 00 00 00 00 || digest || salt || H
8 bytes 32 32 32
If the caller wishes to compute the H value and make the comparison,
the data is made available. The H value is simply the message digest
of M-prime. For example, if the digest algorithm is SHA-256, then
compute
SHA-256(M-Prime)
This will be
SHA-256(mPrimeAndH, 0, (2 * digestLength) + 8)
or
SHA-256(mPrimeAndH, 0, 72) // offset 0, length 72
Compare the result with the last digest length bytes of
mPrimeAndH
.
This method only supports signatures 128 or 256 bytes (1024 or 2048 bits) long.
It is possible that the method was not able to parse the block enough
to build the mPrimeAndH
output. In that case, the return from
the method will be false
, isVerified
will be
false
, and mPrimeAndH
will be an empty byte array. It
is also possible that the method was able to build mPrimeAndH
and yet some parsing operation failed so the return is still
false
. That is, looking at only mPrimeAndH
will not be
sufficient to know what happened.
Note that the return value from this method only indicates whether
it was able to parse or not. The return value does not have anything
to do with whether the signature verifies or not. That is, it is
possible the return from this method is true
and the
isVerified
output argument is set to false. In this case, the
method was able to parse the signature, it's just that the signature
did not verify. In other words, the method successfully completed its
task, there was no error preventing it from parsing and making the H
value comparison. Its task is to determine if the signature verifies
or not. If it determines that the signature did not verify, the
method successfully completed its task, just as determining the
signature did verify is a successful completion of the task.