weidai11 / cryptopp

free C++ class library of cryptographic schemes
https://cryptopp.com
Other
4.78k stars 1.48k forks source link

Set salt length for RSA PSS signatures at run time #1121

Open tex0l opened 2 years ago

tex0l commented 2 years ago

Hi,

I'm currently using the latest release of cryptopp (8.6.0) in react-native-cryptopp (see https://github.com/JiriHoffmann/react-native-cryptopp/issues/17).

I need to make RSA-PSS-SHA256 signatures with a max salt length (which is the default in many libraries, for example in node.js https://nodejs.org/api/crypto.html#cryptosignalgorithm-data-key-callback).

However, the only way to do it currently is through a template parameter of PSSR_MEM, which doesn't allow setting it at runtime.

This is problematic as the max salt length varies with the key size and the hashing algorithm with the formula keyModulusLengthInBytes - hashlength - 2, which gives for SHA256:

I found a question dating back from 2005 about this, which never got an answer: https://groups.google.com/g/cryptopp-users/c/UFVcV9Ml_Cs/m/NFYEfxhF0CEJ.

Enumerating the possibilities of key sizes & hash lengths at compile time is quite tedious, passing the saltLength as an argument at runtime would be preferable.

noloader commented 2 years ago

Hi @tex0l,

Here's the MEM_PSSR class:

template <bool ALLOW_RECOVERY, class MGF=P1363_MGF1, int SALT_LEN=-1, int MIN_PAD_LEN=0, bool USE_HASH_ID=false>
class PSSR_MEM : public PSSR_MEM_BaseWithHashId<USE_HASH_ID>
{
    virtual bool AllowRecovery() const {return ALLOW_RECOVERY;}
    virtual size_t SaltLen(size_t hashLen) const {return SALT_LEN < 0 ? hashLen : SALT_LEN;}
    virtual size_t MinPadLen(size_t hashLen) const {return MIN_PAD_LEN < 0 ? hashLen : MIN_PAD_LEN;}
    virtual const MaskGeneratingFunction & GetMGF() const {static MGF mgf; return mgf;}

public:
    static std::string CRYPTOPP_API StaticAlgorithmName() {return std::string(ALLOW_RECOVERY ? "PSSR-" : "PSS-") + MGF::StaticAlgorithmName();}
};

I think the class supports a runtime value. If SALT_LEN == -1, then the function SaltLen returns the value passed in as a parameter.

Does that not work for you?

tex0l commented 2 years ago

I can indeed set the SALT_LENGHT by defining a new struct:

struct PSS_CUSTOM_478 : public SignatureStandard
{
    typedef PSSR_MEM<false, P1363_MGF1, 478> SignatureMessageEncodingMethod;
};

Then I can use this struct when defining the signer:

  typename RSASS<PSS_CUSTOM_478, SHA_256>::Signer signer(*privateKey);

I manage to make it work against my reference implementation in JS for a 4096bits key and a SHA256 hash function, but this 478 value changes for every set of {key size, hash function}.

In my case, the salt length comes as a non-const int (because it'll be given as an argument of a JS function in react-native-cryptopp, very much like node.js's interface), and a non-const int cannot be injected as a template parameter of this PSSR_MEM class. Whenever I try, it fails with the error read of non-const variable 'saltLen' is not allowed in a constant expression, which seems to be normal from what I understand and from what I see on stackoverflow on the topic (https://stackoverflow.com/questions/2873802/specify-template-parameters-at-runtime).

It may be a question of cpp skills on my end really, am I wrong somewhere?

noloader commented 2 years ago

Thanks @tex0l,

Before I look at this in detail, can you provide (a) some sample code and (b) which standard(s) you are trying to support?

I think your struct PSS_CUSTOM_478 is a good choice. Templates only get instantiated when used so you don't have to worry about code bloat for unused combinations.

noloader commented 2 years ago

@tex0l,

(b) which standard(s) you are trying to support?

Here's what I am finding:

PKC #⁠1 v2.2 goes on to say nine hash functions should be supported: MD2, MD5, SHA-1, SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, and SHA-512/256. It is missing a number of hash functions, including SHA3 and BLAKE2.

It kind of surprising PKCS #⁠1 says MD2 and MD5 should be supported given the document's date is November 2016.

tex0l commented 2 years ago

Thank you for the quick reply @noloader:

(a) some sample code

this is extracted from react-native-cryptopp with some modifications to simplify it:


struct PSS_CUSTOM_478 : public SignatureStandard
{
    typedef PSSR_MEM<false, P1363_MGF1, 478> SignatureMessageEncodingMethod;
};
void sign(const jsi::Value *args, std::string *result) {
    int saltLen;
    std::privateKeyString;
    std::string data;
    // ... function that parses and validates `*args` and retrieves `&saltLen`, `privateKeyString` and `data` from it ...
    StringSource PKeyStringSource(privateKeyString, true);
    CryptoPP::RSA::PrivateKey privateKey;
    PEM_Load(PKeyStringSource, privateKey);
    AutoSeededRandomPool rng;
    // I would like to use dynamically `saltLen` here, but if I instantiate the struct here with `saltLen` as the `SALT_LENGTH`, it fails because it is not a const (which is normal)
    typename RSASS<PSS_CUSTOM_478, SHA256>>::Signer signer(*privateKey);
    StringSource(*data, true, new SignerFilter(rng, signer, new StringSink(*result)));
}

(b) which standard(s) you are trying to support ?

In the end, I'm trying to match the specification of sscrypto, a library we maintain for Seald, within this library we use a custom salt length (see specs here), which is the maximum salt length possible:

The RFC I'm following is 8017, and you are right the default value is the hash length (https://www.rfc-editor.org/rfc/rfc8017#appendix-A.2.3), but it can be modified. TLS specifies that it must be this hLen because there's no provable security benefit in making a salt longer (as far as I can tell), and it's one less parameter to set :).

Actually Node.js has this default behavior of setting the maximum length for PSS signatures (https://nodejs.org/api/crypto.html#signsignprivatekey-outputencoding), and even has a special constant to set it crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN which comes from OpenSSL (https://github.com/openssl/openssl/blob/31b7f23d2f958491d46c8a8e61c2b77b1b546f3e/crypto/rsa/rsa_pss.c#L179), and that's why we're using this salt length as a standard in sscrypto.

noloader commented 2 years ago

Thanks again @tex0l,

For userland usage, this is what I am thinking. That is, this is what I think users would like to do based on the way the current machinery works. I strongly prefer to keep things consistent rather than designing a new way. Consistency helps keep questions low, which saves me time.

RSASS<PSSR_RFC8017<478>, SHA256>::Signer signer(...);

We may need something like this, though:

RSASS<PSSR_RFC8017<SHA256, 478>, SHA256>::Signer signer(...);

The difference between PKCS1v15 and PSSR_RFC8017 is, PKCS1v15 uses MGF1 with SHA-1 and 20 bytes of salt. PSSR_RFC8017 will take a couple of template parameters to allow finer tuning of the algorithms. I would like to follow the "must use the same hash for MGF1", so we can drop the second example and use the first example:

RSASS<PSSR_RFC8017<478>, SHA256>::Signer signer(...);

I think we may be able to provide something like this, where we dynamically calculate salt size based on modulus and hash size:

RSASS<PSSR_RFC8017, SHA256>::Signer signer(...);

I'll need to write some code to ensure the various components that make up RSASS with PSSR_RFC8017 can access needed members, like PSSR_MEM can access the public key to get the modulus size.

The open question is, if we automatically calculate salt size, then do we still need to allow a custom salt size? That is, do we support a case where the user selects a 4096-bit modulus with a 477 salt size (instead of 478)? I am less inclined to support something like this out-of-the-box since it deviates from the standard.

To summarize, I think the options are:

What do you think?

tex0l commented 2 years ago

Hi @weidai11 !

Thank you for deep diving into this issue for me :).

The end goal for me is to have the same behavior as this RSA_PSS_SALTLEN_MAX_SIGN option of OpenSSL that would automatically calculate the max salt length for the given hash function & modulus size.

Note that this is not the default behavior of RFC 8017. Its default behavior is what you do: salt length is hash length. It can only be adjusted (very much like the hash function or the mask generation function). Therefore, defining a SignatureStandard named PSSR_RFC8017 that would have this behavior would be misleading.

The interface that would match best the standard would be your latest one RSASS<PSSR_RFC8017<MGF1_HASH_FUNCTION, SALT_LENGTH>, HASH_FUNCTION>::Signer signer(...), as it allows defining the mask generation hash function, the salt length and the hash function. This is actually very close to using directly PSSR_MEM and I don't see what's the added value of defining an extra class..

And it brings be back to the root of the issue: the salt length would need to be given as a template parameter (along with the hash functions), and my issue with that is that I don't know how to pass a variable I get at runtime as a template parameter (I'm more and more convinced this is a question of my — lack of — cpp skills).

The interface that would match best OpenSSL's option would be: RSASS<PSSR_MAX_SALT_LENGTH, SHA256>::Signer signer(...) where it would calculate automatically the maximum salt length (being modulus length in bytes, minus hash length, minus 2).

I see 2 paths from there:

noloader commented 2 years ago

Hi @tex0l,

I've had a chance to write some sample code. I've found several way that don't work. That's par for the course when trying to shoehorn new behavior into existing classes. We've encountered it before, like when trying to cut-in deterministic signatures.

It looks like the solution is to provide a RSASS_MAX_SALT_LEN. The class will eventually need to call ComputeMessageRepresentative and VerifyMessageRepresentative with a hashLen equal to the max salt length so the call to SaltLen produces the proper result.

I hope to have a sample this weekend.

Do you have some test vectors so we can add some self tests? I'd like to get some produced by another library for independence.


This is where I hoped to go with things, but I kind of knew it was not going to work. I knew the cast was going to fail because this pointer was a PSSR_MEM and not a signature scheme. As expected, the dynamic_cast returned a nullptr.

template <bool ALLOW_RECOVERY, class MGF=P1363_MGF1, bool USE_HASH_ID=false>
class PSSR_MEM_WITH_MAX_SALT_LEN : public PSSR_MEM<ALLOW_RECOVERY, MGF, -1, 0, USE_HASH_ID>
{
    virtual size_t SaltLen(size_t hashLen) const {
        const RSAFunction* rsaFn = dynamic_cast<const RSAFunction*>(this);
        return rsaFn->GetModulus().ByteCount() - hashLen - 1;
    }
    ...
};

Since we can't get to the outer objects due to composition, we're going to need to implement classes with the proper behavior down in the bowels of the signing code.

tex0l commented 2 years ago

Hi @noloader,

Thank you for digging into this! I'll be honest, I'm not familiar enough with Cryptopp nor with Cpp in general to be able to help you on exactly how to implement this feature.

I made a script in Node.js which internally uses OpenSSL to generate test signatures:

const { promisify } = require('util')
const crypto = require('crypto')

const keySizes = [1024, 2048, 3072, 4096]
const generateKeyPairAsync = promisify(crypto.generateKeyPair)
const generateKeyPair = (keySize) => generateKeyPairAsync(
  'rsa',
  {
    modulusLength: keySize,
    publicExponent: 65537
  }
)

const textToSign = Buffer.from('dadada')

const sign = (privateKey, textToSign) => {
  const sign = crypto.createSign('SHA256')
  sign.update(textToSign)
  return sign.sign({
    key: privateKey,
    padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
    saltLength: crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN
  })
}

const verify = (publicKey, textToSign, signature) => {
  const verify = crypto.createVerify('SHA256')
  verify.update(textToSign)
  return verify.verify(
    {
      key: publicKey,
      padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
      saltLength: crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN
    },
    signature
  )
}

const main = async () => {
  for (const keySize of keySizes) {
    console.log(`Generating key with modulus length ${keySize}`)
    const { privateKey, publicKey } = await generateKeyPair(keySize)
    console.log('Private key:')
    console.log(privateKey.export({ type: 'pkcs1', format: 'der' }).toString('base64'))
    console.log('Public key:')
    console.log(publicKey.export({ type: 'pkcs1', format: 'der' }).toString('base64'))
    console.log(`Signing '${textToSign}' with private key`)
    const signature = sign(privateKey, textToSign)
    console.log('Signature:')
    console.log(signature.toString('base64'))
    console.log(`Checking signature with public key`)
    const check = verify(publicKey, textToSign, signature)
    console.log('Check:')
    console.log(check)
  }
}

main()

A run gives:

➜  sandbox node rsa-pss.js
Generating key with modulus length 1024
Private key:
MIICXAIBAAKBgQCrUf1NEL5fEZycIUVe9GJ8x3ux4vUPlBR04eNKfGm55xbbVJTSGWBewuCIgRjFyuFLyNZTUkbiT+J1mclbpRm7C1qiGVTAKVUH/KHPdC8ouUGM7ekdPh/+BHJP3GJ75f41xrrTP1bR880TxYl8LBYQbz/uwDQjOrZ2LNra3Sb1qwIDAQABAoGAfdnB+K3XGyFl6W+rdSCThNRJsphlq2b9TAtwG5SfWhg/Oym8FUbc+1+u77feCdaKgFBt2VF0juVeF0O8nlz/M6rXG7dYUzVbkPNtrOqr7nPwaApOIEAZnlNxVu7VQjCtL10MffnPTtqjYhYB+JBsJyJj4DnnBjkJcYlAyOiPd1ECQQDej8TX0eH77zsxZrUfjV/K4NWMIn6SMHyzEMDAfrsxYJbOtqM6t5JKfrZFbJ/pkb0WdAsMlQFHTJtZr33X1/JZAkEAxQ9cetQGm6AJFsSQQTf3VRCLbHwB02BIFIC+fgBuC6PIoqvf4cD2nt3amQDssFR1uhR0OLupCTuh0VKQJCn/owJAJg4jNFv47iEb1/id48VCqegD27BSQCD2UY/9xWxmCa1gW/wysOmOpBpChGBsf32h/WLeOMqJq21X6t/s/qk4EQJBALDyejhQ6x4TNhYbquzlNFJN6OQg9gK4EgFXGbZK7IXHLAHmE8LDYrCExiVdjytGq+/LLhFDcSW5RjSPzp6ql8ECQB25Q1GBU2w8iaqpJabfZ+NrSHOKAbvhEIVKke41ilqWozP/xYpUqxavbF+zzz4myk488AJLZxQYqORybDHbVUU=
Public key:
MIGJAoGBAKtR/U0Qvl8RnJwhRV70YnzHe7Hi9Q+UFHTh40p8abnnFttUlNIZYF7C4IiBGMXK4UvI1lNSRuJP4nWZyVulGbsLWqIZVMApVQf8oc90Lyi5QYzt6R0+H/4Eck/cYnvl/jXGutM/VtHzzRPFiXwsFhBvP+7ANCM6tnYs2trdJvWrAgMBAAE=
Signing 'dadada' with private key
Signature:
aM8Vnnv/A+ku4imxR0drg3yl8znBGP6H8R/Rb0vuRXvOr60lkBADoRVu3De6fjCPRCkyZjtouCdFlIy63R6EJ0MSvpGOb1e7aYAXEuWUr+T34FjmqWQemNj1o5rbTwEfQw1UottqgspXNqaIw9Jv3Xl0ZtzmFTluGrLlU6NGwlU=
Checking signature with public key
Check:
true
Generating key with modulus length 2048
Private key:
MIIEpQIBAAKCAQEAz+PdcrQk+ceWT8quAJzsND2o4oaxr6EpbnFOGZnBNAjfA8U3eESrbcRyYuoYFNOuni4A035yaPR3Q1G9gJGIjDFDWqPpVOZtA9m8FsAlVEFnI8lS9Q5ift3F4R80Hty168XegrafySUQPrRV7e+nB6/mC69CmxSw/iH285KjqAKqxs7eel7d8Y4Jt7WnRiLcICEla3KMp3h/Vz30+Xf6zq3dSBcZJogxGr9ji8QHOSZiAldtcPs7BQt8hRa3aggv4krcHMKOlfujZZEiJPujxGXuPB+9tuv07x0BuCRUHIdm4ikiKtja+WtCJYiVniRb2Z0mucfdYwyP12o35ncMEwIDAQABAoIBAQDLXQ7OHGdnaNUjrRNWSivOVtsU7cp4EmGjRneuF3imXF89IvnGTvab5GY5WOLBzOmulzX7rmBhR3Q7iISmdrdq3O4Uhu/EO7083Qxf/QsHRwuMOG9MhgvYKz5doNatsh7MS74FhHz+maEwldzRa3Sr0mhp2cKATldt4u/fCf3KZT0U/GMEj8TRsIi0CVpCXLmr7CDHBi0hs2fFbwknXYFz3yd7UH608V8TsTWrCWdLx9tPfcdLhq2Flwu7GpZRaJNPV70wwQavqYJKYCcKeRmeki5Iq1GpWXlQ7In59L/dfZmYEOFmtl6Fz9WDvJtBRJiFf3pyM6ywWNwg62HX76MxAoGBAPdR+hrfRUaQwN7jrClWlrrobatZNoU/duxRQ+fJN+WwYPJ0bvSZVa1jrychT3SyXTNGvbn6ukgRrWaU2BePlEKUbHb1c5Ny/jYMGiz7tEqN2jp17R7x1DLD1wzsnsMLuXzQG4fXVJN0xF7zVAgZx4Lgaks5Tj1ydjo23ApGz5t/AoGBANcvodXqIxDFivm59CS44qkwdVU1wO2KCp/3Oxr4n5AoSfLwYCNxlqV1+WHALP11PlAYFcfWcs+mdyY619fotGvWygk2rmUvBDlBZnfk4C5yqMC9kvszSnXuM+ts1WXuSjFA5hKqP6Hl6lF686cCfZFyr5RMKh4rtiA9ZEuPFaltAoGBALJOTuOqzpYmbHcFa6zN+ZO9WLvtcMo2TDXqDOwB/SPCutJyMUB1f/im4rNyZ4d5xIngWY7I/h5RaFOJwhWSdVBfE6fcJDxM7ovmw8Rkn8IUbR4ywQbLULJc0SFHQtraDBu5KfAQxbAdwim2goHonBd2Vgvvv8G7URN9U7yw+qJfAoGBAKvrmljV334e1ZH/R1evfye9V4DkmWcuyp5TYB2EVbdO+QXnlme68KjxQHUgnNFDQq2rEHvAkanlTXx1ts0BVmRyDqidz2d30OANqFhRu+pgIQMccrnPmMXvsft90GDHqO8A8tAmxQAMONEwckoUa04xWqYY0+2W7sODSQY1IxFhAoGAPCEvALsVT+TH0ADBepTPXF0gRwhhM3Wl2HqaubTufDdUdQ/SBElqalGqejx1OttcDHxshVKI2b2SC4mGMyFCWc4igWyAZL/DkaEs6Keu4vV2Fvmgnh55T3BLOnEJ1NccWGmPYF0wRSHbQYHTvUh3U4Zap95fvo85f828NnOHdgY=
Public key:
MIIBCgKCAQEAz+PdcrQk+ceWT8quAJzsND2o4oaxr6EpbnFOGZnBNAjfA8U3eESrbcRyYuoYFNOuni4A035yaPR3Q1G9gJGIjDFDWqPpVOZtA9m8FsAlVEFnI8lS9Q5ift3F4R80Hty168XegrafySUQPrRV7e+nB6/mC69CmxSw/iH285KjqAKqxs7eel7d8Y4Jt7WnRiLcICEla3KMp3h/Vz30+Xf6zq3dSBcZJogxGr9ji8QHOSZiAldtcPs7BQt8hRa3aggv4krcHMKOlfujZZEiJPujxGXuPB+9tuv07x0BuCRUHIdm4ikiKtja+WtCJYiVniRb2Z0mucfdYwyP12o35ncMEwIDAQAB
Signing 'dadada' with private key
Signature:
pJbQ0HtIBdnWB2RMyJft4Ep3UPdCuTB7HizHpvvn/6VuDLpsaJ98xVp/Ld8orVbfcHpw979lZRC8Jb7OUUJtJVKtK3iOSyeGc44NdoEJyo+ARnF5nIhIRbP6kwpKV79LOenL8D5MueU1JAVyKMrOv1FuDo2TibinK0hZzQJqd70x+zwBBxicJgI1ZIcvg8gF0hsoSqlDMcoP1BoQQ0P/ambJSWKduGiFu3PKdiDFEvta0UDxMyF93Vf/w3jcBMO5oqGFBqH1ZKZ1957TcmcprGJfdXsDEW2NXdshiVjx6sRuT3/vWwokyKzgg9zJEdyOn4YU8F2JTLZkQsFcFOo/sA==
Checking signature with public key
Check:
true
Generating key with modulus length 3072
Private key:
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
Public key:
MIIBigKCAYEArpPKmtqtdvOJcoRZBwGFvO5lNd1uEoIh9Yputjy8GvXwJMeERSGlFpm84xKzUCCOkSRx6ZYhQZ5fN86E1Ls87GZ7kv/E8AqZzwB1LDPvg/AxOQGSqpIYSLe2kOtAwOUCSYw0FulF+Vqdh+61oRz2oYN29+sFfDlJ5sgShNbLlnZRnQ1OfFbdpQQVZbgQALJbZEdnge3wW5SAeq/nsx2T7DA7ht3kr+pcRc+Ef4cWmQoCznbNT9iv9ACFRTxd3Y6es4MpGIF/BwvFTP44KWtcpkqBT+x4Pi/1w07M6Y7UC+LylrTQSJgC4Yuahw1O1TxYIRQaxEpjxfTpXqFZ75xOWygCfG0DTIsMYq50sOYfdYGxzPrhWlXZSL5URbTOj3UGXwF4g3BYwLTkK7o6bVhujUwNoCKvHxEhuNhuAqzti6U1UxZ1xmrmRN5roA1fIM5svD/JKqtqkgc7EFuiQBH1NnNxyYHlNqO6gP0BmSi50g1BOdk5V0vOs/r4TmAZQPOtAgMBAAE=
Signing 'dadada' with private key
Signature:
rKSF7THxgtG1/kWICZMLKuh4kDdy05o7922MvdlgwjSr1eponwPkDxzFPczk+pK0wX3N232OwGXkssZQhUeE6m+j23tjfYMIt6ple5vb+qHK1cATZIcFI6JXxu04b5xtGjK7UZJtkMXZgTIkrjiXbsmlYVxq6VMqRQdVEfBvC9/iQWzzQzSGfNcgs4jwEftA/D3tO6rmERrzFiSnLGr4R6oELTilwU+sLKDJR30lyo0Z510oIigRLmRkH4uf9kpgRxGxYOmLdwVRxHMQKOuxC+9NEK7RuztlCJP9huFGmcxSlJQCLggQJnPLdd11r1lljWz3cgdhY7agsOC7TZmDxgrSO4mg+cIRAbqez/xwBE1HYEIy9ENbSozMPmn6MoumJB+pw3EodYFbxYEOy8eQucqI3L+X6HbMFJEIJpeNSUH/rOPSj7iHecwH0aD6SW2gizt4xxnnXJfLEJ9/rYMHXX2Z5SM3/9IaaWV1kLGayeqpQLNpiqYmVmxjfHfQ3cDM
Checking signature with public key
Check:
true
Generating key with modulus length 4096
Private key:
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
Public key:
MIICCgKCAgEAp/+k8zxWrQsjLwjrsYu5msiTg/1/KgJGbPwp+5uR4Wxdtu/r4N/0K6anlcyjxbPe2aTOqbAoXJi2oi851A2ZZkUSqTLAzXau4Ehw3X++eLVDcgW2IsZwziHd8siXz4erIt0tlyW90RiB7LZ+9agO0nXtdCQFH5VPch/BdsxInK4H6wI3evtpIhZjKxhLVaMuajUoXRD6TyVTcWd5pYrKtmKoEfAUsh2/H3a3qVGJRT0riCZyfFynbsdI2p/OpZCGEzhMPrKHNsbHO7JqKj6nqIgfG1/4dTN8mj0Q9swwgleBSwCFTM5krg2Z2FbdZqLkL3bVWdpr+VfEtupTtQBvIRxhU43tRiMicIv6cigAaCiidyXyz/9IG45HA693mSzV+xQKVzZeOhz9eTJBcjaKREaDur5+QFKlLMflVgY+rh2zFlSNEYcaRPrJkh8yHn3F06LNhh+HH6LONIfuMszzyEdVfDGfE8QkaOY2E0ifAtehOW8+XodKrEJMjCrGdlpOUyjt8Oq/YKP4V7nbmJSXWLTCdZoU2hvIAsA9w0A30lM9eeRJmTb9jfovDo6aQGxacwOk4bONgnWU89NDCMGEjsn0B7qOwWfDnveW69/c2o5bBpX4NAg/8oEYhiOESJZ6jmBXDa6WD2PiHXj+0JDABact7Lxm8wG4OVWpQzntTMECAwEAAQ==
Signing 'dadada' with private key
Signature:
kN8Eh9h2bBWcbwSvG4VFzJf/SWnoR45yCmQgyENpUWOuL+e26GRuxDOA5c4kmi5LwHdBfJkFajOZfw2+Wi0ccQ5kuSQuwjuPoWWMjYQcdsmepUVsGk0lJB7KgcQySN2JTBuAQ7+rQQRC9eiPpuwNZ0yXp5cz8Uu/65xRbUt5FGWHcXweYbDUoOMgLUlGZ6zFL/RZDFsGGJb/TFs8MNUcbc1DjJLEQMXKFGLU/kXW+43yeswaKdVldEMQjLP5eGYNX5pr3TRk17CCTdLoWAfwfSCc4cUviMcGV+SA2DGe8QdPbt3NA41xdyBZ6pG20UR79J6F7PqAW/5u0cLpXPlMoUOAKO6hjNZ9UeYQGHJ/b2rJSoAexSHzEni0seLMgifs0pj5svqc9Bn3YVZnAa30Xsicbv/NXWNI/J3E2YCfFeuMuZFE8R0U3klkMdQAs/ktyM85sgu9r82zmiN3SST00RCU38aYCfqovsYaHPGsrklz3rIN7cwLiEhD5/Km7MKqPNboavSLP1Y+6gtr6DgrJU/BB4MnQb+Ja0bJEu3js7FQjq+jqlKjiSGDOmbssDLPXxprSYTjxhJ6iKmxUASMZX0oFXNv67wJPa2pN/NCZIItblwSPvLQV9MlEUjN9NryftHl1joIiS22n9eq1EBtpH2Zwoo4zLc8XSXLiaIHLhY=
Checking signature with public key
Check:
true

If you don't want to bother installing node.js, here is a gist that uses Subtle crypto that you can copy/paste in any web browser console (FF / Chrome for example), which will internally use the crypto library of your browser:

(() => {
  const keySizes = [1024, 2048, 3072, 4096]
  const generateKeyPair = async (keySize) => window.crypto.subtle.generateKey(
    {
      name: 'RSA-PSS',
      modulusLength: keySize,
      publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
      hash: 'SHA-256'
    },
    true,
    ['sign', 'verify']
  )

  const textToSign = 'dadada'

  const sign = async (privateKey, textToSign) => window.crypto.subtle.sign(
    {
      name: 'RSA-PSS',
      saltLength: Math.ceil((privateKey.algorithm.modulusLength - 1) / 8) - 32 - 2
    },
    privateKey,
    stringToArrayBuffer(textToSign)
  )

  const verify = (publicKey, textToSign, signature) =>
    window.crypto.subtle.verify(
      {
        name: 'RSA-PSS',
        saltLength: Math.ceil((publicKey.algorithm.modulusLength - 1) / 8) - 32 - 2
      },
      publicKey,
      signature,
      stringToArrayBuffer(textToSign)
    )

  const b64ArrayBuffer = arrayBuffer => btoa(
    new Uint8Array(arrayBuffer)
      .reduce((data, byte) => data + String.fromCharCode(byte), '')
  )

  const stringToArrayBuffer = str => {
    const encoder = new TextEncoder()
    return encoder.encode(str)
  }

  const main = async () => {
    for (const keySize of keySizes) {
      console.log(`Generating key with modulus length ${keySize}`)
      const { privateKey, publicKey } = await generateKeyPair(keySize)
      console.log('Private key:')
      console.log(b64ArrayBuffer(await window.crypto.subtle.exportKey('pkcs8', privateKey)))
      console.log('Public key:')
      console.log(b64ArrayBuffer(await window.crypto.subtle.exportKey('spki', publicKey)))
      console.log(`Signing '${textToSign}' with private key`)
      const signature = await sign(privateKey, textToSign)
      console.log('Signature:')
      console.log(b64ArrayBuffer(signature))
      console.log(`Checking signature with public key`)
      const check = await verify(publicKey, textToSign, signature)
      console.log('Check:')
      console.log(check)
    }
  }

  main()
    .catch(error => console.error(error))
})()

Which gives a similar output.

Do you want me to produce more test vectors than with dadada as an input?

tex0l commented 2 years ago

@noloader I wanted to follow up on this, were you able to use the test vectors I produced?