cryptopp
cryptopp copied to clipboard
Set salt length for RSA PSS signatures at run time
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
:
- for 1024 bits keys: 94 bytes
- for 2048 bits keys: 222 bytes
- for 3072 bits keys: 350 bytes
- for 4096 bytes keys: 478 bytes
- ...
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.
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?
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?
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.
@tex0l,
(b) which standard(s) you are trying to support?
Here's what I am finding:
- PKCS #1 v2.2, RFC 8017, Section 9.1 Note 4 says the salt length should match the underlying hash length.
- TLS 1.3, RFC 8446, Section 4.2.3 says the salt length must match the underlying hash length.
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.
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:
- node.js: https://github.com/seald/sscrypto/blob/2ceac1a58df3c9fc0599448b0cedf34565fb78d3/src/node/rsa.ts#L142
- subtle crypto: https://github.com/seald/sscrypto/blob/2ceac1a58df3c9fc0599448b0cedf34565fb78d3/src/webcrypto/rsa.ts#L182
- fallback in JS: https://github.com/seald/sscrypto/blob/2ceac1a58df3c9fc0599448b0cedf34565fb78d3/src/forge/rsa.ts#L146
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.
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:
- Automatically calculate salt size, use hash for signing and MGF:
-
RSASS<PSSR_RFC8017, SHA256>::Signer signer(...);
-
- Allow arbitrary salt sizes, use hash for signing and MGF:
-
RSASS<PSSR_RFC8017<477>, SHA256>::Signer signer(...);
-
- Allow arbitrary salt sizes and different hashes for signing and MGF:
-
RSASS<PSSR_RFC8017<SHA1, 477>, SHA256>::Signer signer(...);
-
What do you think?
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:
- if there is a trick I don't know of to pass a variable I get at runtime as a template parameter, I'm interested in learning the trick, it'd allow me to use
PSSR_MEM
directly to customize the salt length (and I'd be sorry to have wasted your time..). - design a
PSSR_MAX_SALT_LENGTH
SignatureStandard
that matches OpenSSL'sRSA_PSS_SALTLEN_MAX_SIGN
option that could be used out of the box.
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.
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?
@noloader I wanted to follow up on this, were you able to use the test vectors I produced?