Table of Contents

Class RsaFormat

Namespace
Yubico.YubiKey.Cryptography
Assembly
Yubico.YubiKey.dll

This class contains methods that can build and read data formatted for RSA sign/verify and encryption/decryption operations.

public static class RsaFormat
Inheritance
object
RsaFormat

Remarks

Currently this class will format data into only PKCS #1 v1.5 and PKCS #1 v.2 PSS and OAEP constructions. Furthermore, this class will only build specific subsets of PSS and OAEP.

Note that there are attacks on RSA decryption unpadding operations. To learn more about these attacks, whether the YubiKey is vulnerable, and mitigations, see the User's Manual entry on the topic.

Fields

KeySizeBits1024

Use this value to indicate the key size, in bits, is 1024. The KeySizeBits values listed in this class are the sizes supported and provided as a convenience to the user to verify the supported sizes.

public const int KeySizeBits1024 = 1024

Field Value

int

KeySizeBits2048

Use this value to indicate the key size, in bits, is 2048. The KeySizeBits values listed in this class are the sizes supported and provided as a convenience to the user to verify the supported sizes.

public const int KeySizeBits2048 = 2048

Field Value

int

KeySizeBits3072

Use this value to indicate the key size, in bits, is 3072. The KeySizeBits values listed in this class are the sizes supported and provided as a convenience to the user to verify the supported sizes.

public const int KeySizeBits3072 = 3072

Field Value

int

KeySizeBits4096

Use this value to indicate the key size, in bits, is 4096. The KeySizeBits values listed in this class are the sizes supported and provided as a convenience to the user to verify the supported sizes.

public const int KeySizeBits4096 = 4096

Field Value

int

Sha1

Use this value to indicate the digest algorithm is SHA-1.

public const int Sha1 = 1

Field Value

int

Sha256

Use this value to indicate the digest algorithm is SHA-256.

public const int Sha256 = 3

Field Value

int

Sha384

Use this value to indicate the digest algorithm is SHA-384.

public const int Sha384 = 4

Field Value

int

Sha512

Use this value to indicate the digest algorithm is SHA-512.

public const int Sha512 = 5

Field Value

int

Methods

FormatPkcs1Encrypt(ReadOnlySpan<byte>, int)

Build the input data into a PKCS #1 v1.5 formatted block for encryption (see RFC 8017).

public static byte[] FormatPkcs1Encrypt(ReadOnlySpan<byte> inputData, int keySizeBits)

Parameters

inputData ReadOnlySpan<byte>

The data to format.

keySizeBits int

The size of the key used, in bits. This value must be one of the RsaFormat.KeySizeBits-x- values.

Returns

byte[]

A new byte array containing the formatted data.

Remarks

This method will build a new buffer that is keySizeBits long and contains the following data.

00 || 02 || PS || 00 || input data

where PS consists of non-zero random bytes
that is:

00 || 02 || non-zero random bytes || 00 || input data

This method supports only keySizeBits values that are defined in this class as KeySizeBits-x-, such as RsaFormat.KeySizeBits1024 (x=1024). You can use one of these values or simply the actual key size in bits. For example, if the key size in bits is 1024, then either RsaFormat.KeySizeBits1024 or 1024 are valid input to this method.

The standard specifies that PS must be at least 8 bytes long. Hence, for a 1024-bit key, the maximum input data length is 117 bytes. For a 2048-bit key, the maximum input data length is 245 bytes.

1024-bit key:
128-byte buffer: 00 01 || x1 x2 x3 x4 x5 x6 x7 x8 || 00 || 117 bytes
         128 =     2    +             8            +  1  +  117

2048-bit key: 256-byte buffer: 00 01 || x1 x2 x3 x4 x5 x6 x7 x8 || 00 || 245 bytes 256 = 2 + 8 + 1 + 245

This method will use the random number generator from CryptographyProviders to generate the random bytes.

For example, if the inputData is 32 bytes long, and the keySizeBits is 1024, the result of this method will look like the following.

00 01 83 62 10 11 98 03 08 80 90 77 43 61 63 23
34 86 98 07 36 44 56 56 10 01 33 01 24 07 13 20
72 39 55 89 50 14 46 82 17 43 55 40 36 92 42 06
06 18 44 86 29 38 36 67 22 91 40 51 16 40 17 18
56 14 55 25 26 33 21 24 14 08 45 90 85 93 10 77
49 22 53 88 08 12 10 47 84 20 48 27 29 7A 14 00
01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10
11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20

Because this method creates a new byte array, and it contains sensitive data, it is a good idea to overwrite the buffer when done with it.

CryptographicOperations.ZeroMemory(formattedData);

Exceptions

ArgumentException

The data length is too long for the key size, or the keySizeBits is not supported.

FormatPkcs1Oaep(ReadOnlySpan<byte>, int, int)

Build the input data into a PKCS #1 v2 OAEP formatted block for encryption (see RFC 8017).

public static byte[] FormatPkcs1Oaep(ReadOnlySpan<byte> inputData, int digestAlgorithm, int keySizeBits)

Parameters

inputData ReadOnlySpan<byte>

The data to format.

digestAlgorithm int

The algorithm to use in the OAEP operations. It must be one of the digest algorithms defined in this class: RsaFormat.Sha1, RsaFormat.Sha256, and so on.

keySizeBits int

The size of the key used, in bits. This value must be either 1024 or 2048

Returns

byte[]

A new byte array containing the formatted data.

Remarks

The OAEP (Optimal Asymmetric Encryption Padding) operation has a number of parameters: hash function, mask generating function, and label (pSource). The caller supplies the digestAlgorithm as the hash function, this method will use MGF1 as the mask generating function, and the empty label.

This method will build a new buffer that is keySizeBits long and contains the following data.

00 || masked seed || masked DB

The seed is simply digest length random bytes. The masked seed is the same length.

The DB (data block) is originally

lHash || PS || 01 || input data
The lHash value is the digest of the label. The standard specifies the default label is the empty string. This method will only be able to build an lHash from the default empty string. The PS (padding string) is all 00 bytes. Its length is
length(PS) = block size - (input data length + (2 * digest length) + 2)

For example, if the block size is 128 (1024-bit RSA key), the input data is 16 bytes, and the digest algorithm is SHA-256, then

length(PS) = 128 - (16 + (2 * 32) + 2) = 128 - 82 = 46

The standard allows a PS of length 0.

Another way to look at this is the maximum input data length based on the block size and digest length.

max input length = block size - ((2 * digestLen) + 2)

For example, if the block size is 128 (1024-bit RSA key), and the digest algorithm is SHA-256, then

max input length = 128 - ((2 * 32) + 2) = 128 - 66 = 62.

With a block size of 128 and digest algorithm of SHA-384,

max input length = 128 - ((2 * 48) + 2) = 128 - 98 = 30.

Note that SHA-512 is simply not possible with a block size of 128.

max input length = 128 - ((2 * 64) + 2) = 128 - 130 = -2.

If the input data length and digest algorithm make a block too big for the keySizeBits, this method will throw an exception.

This method supports only keySizeBits values that are defined in this class as KeySizeBits-x-, such as RsaFormat.KeySizeBits1024 (x=1024). You can use one of these values or simply the actual key size in bits. For example, if the key size in bits is 1024, then either RsaFormat.KeySizeBits1024 or 1024 are valid input to this method.

This method will use the random number generator and message digest implementations from CryptographyProviders.

Because this method creates a new byte array, and it contains sensitive data, it is a good idea to overwrite the buffer when done with it.

CryptographicOperations.ZeroMemory(formattedData);

Exceptions

ArgumentException

The data length is too long for the key size, or the keySizeBits is not supported.

FormatPkcs1Pss(ReadOnlySpan<byte>, int, int)

Build the digest into a PKCS #1 v2 PSS formatted block for signing (see RFC 8017).

public static byte[] FormatPkcs1Pss(ReadOnlySpan<byte> digest, int digestAlgorithm, int keySizeBits)

Parameters

digest ReadOnlySpan<byte>

The message digest value to format.

digestAlgorithm int

The algorithm used to compute the message digest. It must be one of the digest algorithms defined in this class: RsaFormat.Sha1, RsaFormat.Sha256, and so on.

keySizeBits int

The size of the key used, in bits. This value must be one of the RsaFormat.KeySizeBits-x- values.

Returns

byte[]

A new byte array containing the formatted digest.

Remarks

The PSS (probabilistic signature scheme) padding operation has a number of parameters: hash function, mask generating function, salt length, and trailer field. This method will use the input digestAlgorithm as the hash function, MGF1 as the mask generating function, the digest length as the salt length, and 0xBC as the trailer field.

The default hash function is SHA-1, but the standard recommends using the same hash function in PSS operations as was used to digest the data to sign. Hence, this method will do so. The caller provides the digest (the data to format), along with a flag indicating the algorithm. The algorithm must be one supported by this class: RsaFormat.Sha1, RsaFormat.Sha256, and so on. Note that the length of the digest given must match the digestAlgorithm, otherwise the method will throw an exception.

The default salt length is 20, but the standard recommends using the digest length as the salt length. This method will do that. For example, if the digest is SHA-256, the salt length will be 32. 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.

Note that it is not possible to use SHA-512 as the digest algorithm with PSS and a 1024-bit key. The formatted data will be at least 2 times digest length plus two bytes long. So a PSS-formatted block with SHA-512 will be at a minimum (2 * 64) + 2 = 130 bytes long. But with a 1024-bit RSA key, the block is 128 bytes long.

This method will use the random number generator and message digest implementations from CryptographyProviders.

This method supports only keySizeBits values that are defined in this class as KeySizeBits-x-, such as RsaFormat.KeySizeBits1024 (x=1024). You can use one of these values or simply the actual key size in bits. For example, if the key size in bits is 1024, then either RsaFormat.KeySizeBits1024 or 1024 are valid input to this method.

Exceptions

ArgumentException

The digest length does not match the digestAlgorithm, or the digestAlgorithm is not supported, or the keySizeBits is not supported.

FormatPkcs1Sign(ReadOnlySpan<byte>, int, int)

Build the digest into a PKCS #1 v1.5 formatted block for signing (see RFC 8017).

public static byte[] FormatPkcs1Sign(ReadOnlySpan<byte> digest, int digestAlgorithm, int keySizeBits)

Parameters

digest ReadOnlySpan<byte>

The message digest value to format.

digestAlgorithm int

The algorithm used to compute the message digest. It must be one of the digest algorithms defined in this class: RsaFormat.Sha1, RsaFormat.Sha256, and so on.

keySizeBits int

The size of the key used, in bits. This value must be one of the RsaFormat.KeySizeBits-x- values.

Returns

byte[]

A new byte array containing the formatted digest.

Remarks

This method will build a new buffer that is keySizeBits long and contains the following data.

00 || 01 || FF FF ... FF || 00 || DigestInfo(digest)

The DigestInfo is the DER encoding of the ASN.1 definition

DigestInfo ::= SEQUENCE {
    digestAlgorithm   DigestAlgorithm,
    digest            OCTET STRING
}

This method supports only the following digest algorithms. Note that the length of the digest given must match the digestAlgorithm, otherwise the method will throw an exception.

SHA-1     RsaFormat.Sha1     20 bytes
SHA-256   RsaFormat.Sha256   32 bytes
SHA-384   RsaFormat.Sha384   48 bytes
SHA-512   RsaFormat.Sha512   64 bytes

This method supports only keySizeBits values that are defined in this class as KeySizeBits-x-, such as RsaFormat.KeySizeBits1024 (x=1024). You can use one of these values or simply the actual key size in bits. For example, if the key size in bits is 1024, then either RsaFormat.KeySizeBits1024 or 1024 are valid input to this method.

For example, if the digest is 32 bytes long, the digestAlgorithm is RsaFormat.Sha256 and the keySizeBits is 1024, the result of this method will look like the following.

00 01 FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF 00 30 31 30
0d 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20
01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10
11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20

Exceptions

ArgumentException

The digest length does not match the digestAlgorithm, or the digestAlgorithm is not supported, or the keySizeBits is not supported.

TryParsePkcs1Decrypt(ReadOnlySpan<byte>, out byte[])

Try to parse the formattedData as a PKCS #1 v1.5 block that was the result of decryption (see RFC 8017).

public static bool TryParsePkcs1Decrypt(ReadOnlySpan<byte> formattedData, out byte[] outputData)

Parameters

formattedData ReadOnlySpan<byte>

The data to parse.

outputData byte[]

An output argument, the method will return a new byte array containing the unpadded data portion of the block.

Returns

bool

True if the method is able to parse, false otherwise.

Remarks

This method will extract the data from the formatted data. This is generally the plaintext (the formatted data is the decrypted block). If it is successful, it will return true. If it cannot extract the information, it will return false. The caller will likely decrypt an RSA block, then try to parse it as PKCS #1 v2 OAEP. If successful, the data is collected. If not, try to parse it as PKCS #1 v1.5. Note that while unlikely, it is possible for an OAEP block to look like PKCS #1 v1.5. If you don't know which format was used, it is best to try OAEP first, and if it fails, then try PKCS #1 v1.5.

The method will verify that the first byte is 00, the second byte is 02, and that there are at least 8 padding bytes. It will then expect to find 00 and then the data to return.

Finally, the method will return a new byte array containing the actual data portion.

Because this method creates a new byte array, and it contains sensitive data, it is a good idea to overwrite the buffer when done with it.

CryptographicOperations.ZeroMemory(outputData);

This method only supports blocks 128 or 256 bytes (1024 or 2048 bits) long.

If any element fails (the length of the formattedData is not supported, an expected byte is not there, or so on), the method will return false. If there is an error, the method might set the outputData argument to an empty array, or it might contain the purported data. If the return is false and there is data in outputData, that data is meaningless.

TryParsePkcs1Oaep(ReadOnlySpan<byte>, int, out byte[])

Try to parse the formattedData as a PKCS #1 v2 OAEP block that was the result of decryption (see RFC 8017).

public static bool TryParsePkcs1Oaep(ReadOnlySpan<byte> formattedData, int digestAlgorithm, out byte[] outputData)

Parameters

formattedData ReadOnlySpan<byte>

The data to parse.

digestAlgorithm int

The algorithm to use in the OAEP operations. It must be one of the digest algorithms defined in this class: RsaFormat.Sha1, RsaFormat.Sha256, and so on.

outputData byte[]

An output argument, a new buffer containing the data portion of the block.

Returns

bool

True if the method is able to parse, false otherwise.

Remarks

This method will extract the data from the formatted data and return it in a new byte array. This is generally the plaintext (the formatted data is the decrypted block). If it is successful, it will return true. If it cannot extract the information, it will return false.

The method will verify that the first byte is 00, then it will perform MGF1 using the specified digest algorithm on the masked DB (data block) to unmask the salt, and then perform MGF1 on the salt to unmaskk the DB. It will then verify that the lHash and PS are correct.

00 || masked salt || masked DB
  MGF1(maskedDB)
00 || salt || masked DB
  MGF1(salt)
00 || salt || lHash || PS || 01 || output data

The caller supplies the digest algorithm to use in the MGF1. It must be one of the supported values: RsaFormat.Sha1, Sha256, or so on.

This method will use the message digest implementations from CryptographyProviders.

The lHash value is the digest of the label (pSource). The standard specifies the default label is the empty string. This method will only be able to build an lHash from the default empty string.

Finally, the method will return a new byte array containing the actual data portion.

Because this method creates a new byte array, and it contains sensitive data, it is a good idea to overwrite the buffer when done with it.

CryptographicOperations.ZeroMemory(outputData);

This method only supports blocks 128 or 256 bytes (1024 or 2048 bits) long.

If any element fails (the length of the formattedData is not supported, an expected byte is not there, or so on), the method will return false. If there is an error, the method might set the outputData argument to an empty array, or it might contain the purported data. If the return is false and there is data in outputData, that data is meaningless.

TryParsePkcs1Pss(ReadOnlySpan<byte>, ReadOnlySpan<byte>, int, out byte[], out bool)

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

formattedSignature ReadOnlySpan<byte>

The data to parse.

digest ReadOnlySpan<byte>

The computed digest, the digest of the message, the data to verify.

digestAlgorithm int

The digest algorithm used to compute the digest, it must be one of the supported algorithms: RsaFormat.Sha1, and so on.

mPrimeAndH byte[]

An output argument, a new byte array containing M-prime concatenated with the H value extracted from the formattedSignature.

isVerified bool

An output argument, a boolean reporting whether the signature verified or not.

Returns

bool

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.

TryParsePkcs1Verify(ReadOnlySpan<byte>, out int, out byte[])

Try to parse the formattedSignature as a PKCS #1 v1.5 block for verifying (see RFC 8017).

public static bool TryParsePkcs1Verify(ReadOnlySpan<byte> formattedSignature, out int digestAlgorithm, out byte[] digest)

Parameters

formattedSignature ReadOnlySpan<byte>

The data to parse.

digestAlgorithm int

An output argument, the method will set it to one of the values defined in this class representing the algorithm: RsaFormat.Sha1, and so on.

digest byte[]

An output argument, the method will set it to be a new byte array containing the digest portion of the signature.

Returns

bool

True if the method is able to parse, false otherwise.

Remarks

This method will extract the message digest algorithm and the message digest itself from the formatted signature. If it is successful, it will return true. If it cannot extract the information, it will return false. The caller will likely decrypt an RSA signature, then try to parse it as PKCS #1 v1.5. If successful, the digest is collected. If not, try to parse it as PKCS #1 v2 PSS.

The method will verify that the first byte is 00, the second byte is 01, and that the padding bytes are all FF. It will then expect to find 00 and then the DigestInfo.

It will read the DigestInfo to determine the algorithm. If the method recognizes the OID, it will set the output int digestAlgorithm to one of the supported values: RsaFormat.Sha1, Sha256, or so on.

Finally, the method will return a byte array containing the actual digest. This will be a new buffer.

This method only supports signatures 128 or 256 bytes (1024 or 2048 bits) long.

If any element fails (the length of the formattedSignature is not supported, an expected byte is not there, the OID does not represent a supported algorithm, the digest is not the proper length, or so on), the method will return false. If there is an error, the method might set the output digestAlgorithm to 0 and the output digest to an empty byte array. However, the algorithm and digest output arguments might contain the purported algorithm and digest.