Skip to content

dsa: expose signing and verifying of prehashed hash value - #558

Merged
tarcieri merged 11 commits into
RustCrypto:masterfrom
cobratbq:master
Oct 22, 2022
Merged

dsa: expose signing and verifying of prehashed hash value#558
tarcieri merged 11 commits into
RustCrypto:masterfrom
cobratbq:master

Conversation

@cobratbq

Copy link
Copy Markdown
Contributor

Expose the ability for the user to provide a prehashed hash-value for signing/verification. In rare cases, this is needed in other protocols. I have attempted to solve this with a custom Digest implementation, but eventually ran into (forced) finalization which affects the hash value.

@aumetra

Copy link
Copy Markdown
Contributor

Would you mind changing the code to expose the prehashed signing/verifying functionality via the hazmat traits of the signature crate (just for the sake of having a unified interface)?
You would need to adjust the version requirements of signature to a minimum version of 1.6 (1.6.4 specifically if you want to implement RandomizedPrehashSigner, too).

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

Sure, I'll have a look. Thanks for pointing out a direction.

@cobratbq

cobratbq commented Oct 20, 2022

Copy link
Copy Markdown
ContributorAuthor

@aumetra can you confirm that there is no component of randomness in this logic? The crate::generate::secret_number_rfc6979 does not seem to depend on randomness.

I will follow up later: I've got some things to figure out, e.g. whether the trait needs the D trait (Digest + OutputSize + ..), whether the randomness should be in and I'm currently using an undesirable implementation, etc.

@tarcieri

Copy link
Copy Markdown
Member

@aumetra for now you can just do the deterministic API.

RFC6979 supports additional randomness, but that can be added as a followup.

@aumetra

aumetra commented Oct 20, 2022

Copy link
Copy Markdown
Contributor

can you confirm that there is no component of randomness in this logic?

Yes, the logic of the function (as it currently is) is fully deterministic

@tarcieri

Copy link
Copy Markdown
Member

@cobratbq looks like one clippy nit for a needless borrow

@tarcieri

Copy link
Copy Markdown
Member

Yes, the logic of the function (as it currently is) is fully deterministic

@aumetra it actually also supports using an RNG:

impl<D>RandomizedDigestSigner<D,Signature>forSigningKey
where
D:Digest,
{
fntry_sign_digest_with_rng(
&self,
mutrng:implCryptoRng + RngCore,
digest:D,
) -> Result<Signature, signature::Error>{
let ks = crate::generate::secret_number(&mut rng,self.verifying_key().components())
.ok_or_else(signature::Error::new)?;
let hash = digest.finalize();
self.sign_prehashed(ks,&hash)
.ok_or_else(signature::Error::new)
}
}

@aumetra

aumetra commented Oct 20, 2022

Copy link
Copy Markdown
Contributor

Oh yeah, I was only talking about the function crate::generate::secret_number_rfc6979 since they asked explicitly about that one

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@tarcieri@aumetra quick follow-up, I'd appreciate your feedback

  1. I added an assertion to ensure hash length is at least the same strength as modulus. Before it just took the minimum of two numbers.
  2. verify_prehash is implemented, seems straight-forward enough. No issue there.
  3. sign_prehash, IIUC, cannot be implemented with this combination of trait PrehashSigner<Signature> and the requirement of a D: Digest + BlockSizeUser + FixedOutputReset for crate::generate::secret_number_rfc6979.

Do you have an idea what to do with (3)? The type for D has to be somewhere, and on the trait seems to make most sense. (Branch, currently, does not build but does illustrate the choke point.)

@tarcieri

Copy link
Copy Markdown
Member

@cobratbqDigestSigner is already the trait which has a D parameter.

The ECDSA crate uses a curve-specific digest when implementing PrehashSigner:

https://github.com/RustCrypto/signatures/blob/3a13e77/ecdsa/src/sign.rs#L268

Failing that you could just use e.g. SHA-256, or some other SHA2-family digest which has test vectors in RFC6979.

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@tarcieri I've been looking into the ecdsa implementation. From what I can tell, I suspect that the PrehashSigner trait is currently ill-defined: you rely on that trait to define deterministic signing behavior that needs to have a Digest defined, however it is not part of this trait.

It works for ecdsa because of luck, i.e. it is coincidentally already defined on the implementation. For dsa that won't work, because the spec allows for any number of approved digests, while PrehashSigner requests/requires an input parameter prehash with a run-time bound: bit-length at least as large as bit-length of digest. (Also, DigestSigner is not necessarily present if itself not required.)

Is this something you would want to change/fix or should I work around it, like effectively (probably by accident) happened with ecdsa?

@aumetra

aumetra commented Oct 21, 2022

Copy link
Copy Markdown
Contributor

The logic in ecdsa isn't really ill-defined. Incompatible (in specific cases) with implementations that allow free definition of the used digest function, sure, but not wrong.
The crux is that ECDSA has recommended hash digests for each curve. P256 -> SHA-256, P384 -> SHA-384, you get the picture.
To cite the FIPS 186-4:

It is recommended that the security strength associated with the bit length of n and the security
strength of the hash function be the same unless an agreement has been made between
participating entities to use a stronger hash function

The ecdsa crate uses the associated type of the recommended digest to generate the ad-hoc key with.

DSA however doesn't really have a "recommended" hash digest. Most of the standards reference SHA-1 which is actually pretty awful because, you know.
This is actually tangentially related to a discussion in #520, where we are practically still trying to figure out which digest should be used for a potential Signer/Verifier implementation.


Also regarding your change where you added the assertion on the hash length and the size of parameter q, if I read the standard correctly, this restriction only applies to RSA (specifically RSA key pair generation).

The full quote is as follows:

An approved hash function, as specified in FIPS 180, shall be used during the generation of key
pairs and digital signatures. When used during the generation of an RSA key pair (as specified in
this Standard), the length in bits of the hash function output block shall meet or exceed the
security strength associated with the bit length of the modulus n (see SP 800-57).

The quote can be found in FIPS 186-4, under section 5.1 "RSA Key Pair Generation"

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

@cobratbq the current shape of the ecdsa crate is based on years of iteration and hard-won knowledge about how RFC6979 is deployed alongside ECDSA in many real-world scenarios. In fact, we have to accommodate cases where the digest used for instantiating HMAC-DRBG is different from the one used to hash the message.

We previously did what I suspect you're suggesting: always use the D parameter of DigestSigner to instantiate HMAC-DRBG. That felt great at first, but we ended up with many, many requests to match existing test vectors for systems which use two different hashes, namely Keccak256 for message hashing and SHA-256 for RFC6979 as commonly used in the Ethereum ecosystem.

Unless you have such specific requirements as well, you can instantiate RFC6979 with the secure cryptographic digest of your choice, which is why I recommended SHA-256 as a reasonable default. Again, it doesn't have to match what was used to hash the message, and really the only party who can discern what hash was used was the signer. The only time it really matters at all is if you are trying to match specific deterministic test vectors.

PrehashSigner doesn't have a D parameter by design: the DigestSigner trait provides that. If you really, really want to have a generic parameter for D when using PrehashSigner, it needs to be defined on the type, not the trait.

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@aumetra: okay, you are right. Let me rephrase. Given the way DSA is defined, there is a open variable for the digest to be used. (Instead of a single prescribed digest, a set of possible values is defined.) So, this is effectively requires extending the trait, i.e. introducing a new variable component. For ecdsa these can be hard-coded to the prescribed digest, just not for dsa. For practical convenience, maybe it needs to be structured differently so that the definition of PrehashSigner itself isn't changed. I'm questioning the trait definition, because:

  1. I'd like to avoid introducing a specific hash-implementation where only a abstract concept is needed. (Sha1 i.s.o. Digest, for example, as sha1 is only a dev-dependency now.)
  2. From my limited understanding, it seems that PrehashSigner closely relates to the secret_number_rfc6979, except that the one "variable" is exposed.
  3. I'd like to avoid contributing an incomplete workaround, if possible/reasonable.

@aumetra thanks for catching that, I see that in traversing the spec, I accidentally ended up with something RSA specific. That's my fuck-up.

@tarcieri actually, it's the inverse: with my current understanding, I would define a D type on PrehashSigner. In implementations of ecdsa it may end up "hard-coded", in other implementations it may correspond to D in DigestSigner, however in the basis it is a free variable. (And if I understand your examples, this should not be an issue given there is no constraint to DigestSigner.)
You mentioned: "... no D defined by design, because the DigestSigner provides that." Doesn't that mean that you are creating a dependency that is often not valid? (As per some mentioned examples.) DigestSigner may (in all cases?) depend on PrehashSigner, but the reverse is not true, IIUC.

edit@tarcieri comment regarding evolution of ecdsa should not be contradicted or negated by my comments. If you think so, LMK.

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

I would define a D type on PrehashSigner.

Again, PrehashSigner doesn't have a D parameter by design. PrehashSigner is explicitly designed to be able to handle different hash inputs at runtime. The ecdsa crate implements this logic:

See:

Even if we were to abandon that requirement, a D parameter doesn't make sense in all of the potential contexts where PrehashSigner would be used.

Doesn't that mean that you are creating a dependency that is often not valid? (As per some mentioned examples.) DigestSigner may (in all cases?)

Adding a D parameter to PrehashSigner would do just that: it's primarily useful for RFC6979, and unless you're using a digest to instantiate HMAC-DRBG or some similar KDF, it provides no value, and indeed there would be nothing for a user to pass, so what are they supposed to pass in that case?

And again, there's already a similar trait with a D parameter: DigestSigner.

That's why I suggested that if you really, really want to expose it as a user-configurable parameter, it needs to be on the type, not the trait.

If you want a concrete example, PrehashSigner could be used to support Ed25519ph. Internally Ed25519ph is explicitly mandated to use SHA-512 to perform RFC6979-like deterministic derivation of a once-per-key-message-pair epehemeral scalar. Use anything else but SHA-512, and the construction ceases to be "Ed25519ph".

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@tarcieri just to check, I propose a D to pass on to crate::generate::secret_number_rfc6979::<D>(&self, prehash). The value prehash is a slice of unknown length and that is fine, i.e. works exactly as expected.

edit if your suggestion is to hard-code SHA-256 for it and leave it at that, then that's fine by me. (IIUC that works for me)

@tarcieri

Copy link
Copy Markdown
Member

@cobratbq aah, that's fine

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@cobratbq aah, that's fine

I'm glad this is clarified. I had wanted to align the solution direction, but discussions got a bit more complicated. To get back to my original questions/concerns. So,

  • regarding the HMAC-DRBG, would it be better to: (1.) hardcode for DSA? Or (2.) define a generic D that is specific to the use-cases of variable or multiple digests (as discussed above)?
  • And are there preferences regarding: modifying PrehashSigner vs. introducing an (intermediary) trait such that the generic type can be defined there? (given interoperability, backwards-compatibility, etc.)

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

regarding the HMAC-DRBG, would it be better to: (1.) hardcode for DSA? Or (2.) define a generic D that is specific to the use-cases of variable or multiple digests (as discussed above)?

If #520 is resolved, you can use the same approach as ECDSA, leveraging dsa::Signature::Digest.

Alternatively, you could add a generic parameter to SigningKey for the digest to instantiate HMAC-DRBG with for RFC6979, with a reasonable default (e.g. Sha256).

And are there preferences regarding: modifying PrehashSigner vs. introducing an (intermediary) trait such that the generic type can be defined there? (given interoperability, backwards-compatibility, etc.)

If you really feel strongly about this, please open an issue on https://github.com/RustCrypto/traits/issues with your concrete proposal.

If you're suggesting adding an additional trait, please note there's something of a "trait explosion" to handle all possible cases: PrehashSigner has an accompanying RandomizedPrehashSigner that is parameterized by an RNG, DigestSigner also has a RandomizedDigestSigner, and all of these need accompanying traits in async-signature.

Note that there are other things we'd potentially like to add, like domain separation, which would further compound this trait explosion.

So I think the bar would be pretty high to add an additional trait, especially given DigestSigner and PrehashSigner already exist.

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

I get the impression you have some reservations. I understand, especially given another issue is involved.

My goal is to have prehash signing and verification exposed as part of the API. Given the complications as discussed above, what do you recommend?

@tarcieri

Copy link
Copy Markdown
Member

Use Sha256 for now and we can circle back on a more generalized solution in #520

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@aumetra@tarcieri I made the necessary changes. I think my changes have not impacted the other build failures. Can you let me know if there is anything left to do? Also, consider squashing the merge, because the history contains mostly useless confusing changes.

Comment threaddsa/Cargo.toml Outdated
@tarcieri
tarcieri merged commit ace98c8 into RustCrypto:masterOct 22, 2022
@tarcieritarcieri mentioned this pull request Oct 29, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@cobratbq@aumetra@tarcieri
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
dsa: expose signing and verifying of prehashed hash value by cobratbq · Pull Request #558 · RustCrypto/signatures · GitHub
Skip to content

dsa: expose signing and verifying of prehashed hash value - #558

Merged
tarcieri merged 11 commits into
RustCrypto:masterfrom
cobratbq:master
Oct 22, 2022
Merged

dsa: expose signing and verifying of prehashed hash value#558
tarcieri merged 11 commits into
RustCrypto:masterfrom
cobratbq:master

Conversation

@cobratbq

Copy link
Copy Markdown
Contributor

Expose the ability for the user to provide a prehashed hash-value for signing/verification. In rare cases, this is needed in other protocols. I have attempted to solve this with a custom Digest implementation, but eventually ran into (forced) finalization which affects the hash value.

@aumetra

Copy link
Copy Markdown
Contributor

Would you mind changing the code to expose the prehashed signing/verifying functionality via the hazmat traits of the signature crate (just for the sake of having a unified interface)?
You would need to adjust the version requirements of signature to a minimum version of 1.6 (1.6.4 specifically if you want to implement RandomizedPrehashSigner, too).

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

Sure, I'll have a look. Thanks for pointing out a direction.

@cobratbq

cobratbq commented Oct 20, 2022

Copy link
Copy Markdown
ContributorAuthor

@aumetra can you confirm that there is no component of randomness in this logic? The crate::generate::secret_number_rfc6979 does not seem to depend on randomness.

I will follow up later: I've got some things to figure out, e.g. whether the trait needs the D trait (Digest + OutputSize + ..), whether the randomness should be in and I'm currently using an undesirable implementation, etc.

@tarcieri

Copy link
Copy Markdown
Member

@aumetra for now you can just do the deterministic API.

RFC6979 supports additional randomness, but that can be added as a followup.

@aumetra

aumetra commented Oct 20, 2022

Copy link
Copy Markdown
Contributor

can you confirm that there is no component of randomness in this logic?

Yes, the logic of the function (as it currently is) is fully deterministic

@tarcieri

Copy link
Copy Markdown
Member

@cobratbq looks like one clippy nit for a needless borrow

@tarcieri

Copy link
Copy Markdown
Member

Yes, the logic of the function (as it currently is) is fully deterministic

@aumetra it actually also supports using an RNG:

impl<D>RandomizedDigestSigner<D,Signature>forSigningKey
where
D:Digest,
{
fntry_sign_digest_with_rng(
&self,
mutrng:implCryptoRng + RngCore,
digest:D,
) -> Result<Signature, signature::Error>{
let ks = crate::generate::secret_number(&mut rng,self.verifying_key().components())
.ok_or_else(signature::Error::new)?;
let hash = digest.finalize();
self.sign_prehashed(ks,&hash)
.ok_or_else(signature::Error::new)
}
}

@aumetra

aumetra commented Oct 20, 2022

Copy link
Copy Markdown
Contributor

Oh yeah, I was only talking about the function crate::generate::secret_number_rfc6979 since they asked explicitly about that one

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@tarcieri@aumetra quick follow-up, I'd appreciate your feedback

  1. I added an assertion to ensure hash length is at least the same strength as modulus. Before it just took the minimum of two numbers.
  2. verify_prehash is implemented, seems straight-forward enough. No issue there.
  3. sign_prehash, IIUC, cannot be implemented with this combination of trait PrehashSigner<Signature> and the requirement of a D: Digest + BlockSizeUser + FixedOutputReset for crate::generate::secret_number_rfc6979.

Do you have an idea what to do with (3)? The type for D has to be somewhere, and on the trait seems to make most sense. (Branch, currently, does not build but does illustrate the choke point.)

@tarcieri

Copy link
Copy Markdown
Member

@cobratbqDigestSigner is already the trait which has a D parameter.

The ECDSA crate uses a curve-specific digest when implementing PrehashSigner:

https://github.com/RustCrypto/signatures/blob/3a13e77/ecdsa/src/sign.rs#L268

Failing that you could just use e.g. SHA-256, or some other SHA2-family digest which has test vectors in RFC6979.

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@tarcieri I've been looking into the ecdsa implementation. From what I can tell, I suspect that the PrehashSigner trait is currently ill-defined: you rely on that trait to define deterministic signing behavior that needs to have a Digest defined, however it is not part of this trait.

It works for ecdsa because of luck, i.e. it is coincidentally already defined on the implementation. For dsa that won't work, because the spec allows for any number of approved digests, while PrehashSigner requests/requires an input parameter prehash with a run-time bound: bit-length at least as large as bit-length of digest. (Also, DigestSigner is not necessarily present if itself not required.)

Is this something you would want to change/fix or should I work around it, like effectively (probably by accident) happened with ecdsa?

@aumetra

aumetra commented Oct 21, 2022

Copy link
Copy Markdown
Contributor

The logic in ecdsa isn't really ill-defined. Incompatible (in specific cases) with implementations that allow free definition of the used digest function, sure, but not wrong.
The crux is that ECDSA has recommended hash digests for each curve. P256 -> SHA-256, P384 -> SHA-384, you get the picture.
To cite the FIPS 186-4:

It is recommended that the security strength associated with the bit length of n and the security
strength of the hash function be the same unless an agreement has been made between
participating entities to use a stronger hash function

The ecdsa crate uses the associated type of the recommended digest to generate the ad-hoc key with.

DSA however doesn't really have a "recommended" hash digest. Most of the standards reference SHA-1 which is actually pretty awful because, you know.
This is actually tangentially related to a discussion in #520, where we are practically still trying to figure out which digest should be used for a potential Signer/Verifier implementation.


Also regarding your change where you added the assertion on the hash length and the size of parameter q, if I read the standard correctly, this restriction only applies to RSA (specifically RSA key pair generation).

The full quote is as follows:

An approved hash function, as specified in FIPS 180, shall be used during the generation of key
pairs and digital signatures. When used during the generation of an RSA key pair (as specified in
this Standard), the length in bits of the hash function output block shall meet or exceed the
security strength associated with the bit length of the modulus n (see SP 800-57).

The quote can be found in FIPS 186-4, under section 5.1 "RSA Key Pair Generation"

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

@cobratbq the current shape of the ecdsa crate is based on years of iteration and hard-won knowledge about how RFC6979 is deployed alongside ECDSA in many real-world scenarios. In fact, we have to accommodate cases where the digest used for instantiating HMAC-DRBG is different from the one used to hash the message.

We previously did what I suspect you're suggesting: always use the D parameter of DigestSigner to instantiate HMAC-DRBG. That felt great at first, but we ended up with many, many requests to match existing test vectors for systems which use two different hashes, namely Keccak256 for message hashing and SHA-256 for RFC6979 as commonly used in the Ethereum ecosystem.

Unless you have such specific requirements as well, you can instantiate RFC6979 with the secure cryptographic digest of your choice, which is why I recommended SHA-256 as a reasonable default. Again, it doesn't have to match what was used to hash the message, and really the only party who can discern what hash was used was the signer. The only time it really matters at all is if you are trying to match specific deterministic test vectors.

PrehashSigner doesn't have a D parameter by design: the DigestSigner trait provides that. If you really, really want to have a generic parameter for D when using PrehashSigner, it needs to be defined on the type, not the trait.

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@aumetra: okay, you are right. Let me rephrase. Given the way DSA is defined, there is a open variable for the digest to be used. (Instead of a single prescribed digest, a set of possible values is defined.) So, this is effectively requires extending the trait, i.e. introducing a new variable component. For ecdsa these can be hard-coded to the prescribed digest, just not for dsa. For practical convenience, maybe it needs to be structured differently so that the definition of PrehashSigner itself isn't changed. I'm questioning the trait definition, because:

  1. I'd like to avoid introducing a specific hash-implementation where only a abstract concept is needed. (Sha1 i.s.o. Digest, for example, as sha1 is only a dev-dependency now.)
  2. From my limited understanding, it seems that PrehashSigner closely relates to the secret_number_rfc6979, except that the one "variable" is exposed.
  3. I'd like to avoid contributing an incomplete workaround, if possible/reasonable.

@aumetra thanks for catching that, I see that in traversing the spec, I accidentally ended up with something RSA specific. That's my fuck-up.

@tarcieri actually, it's the inverse: with my current understanding, I would define a D type on PrehashSigner. In implementations of ecdsa it may end up "hard-coded", in other implementations it may correspond to D in DigestSigner, however in the basis it is a free variable. (And if I understand your examples, this should not be an issue given there is no constraint to DigestSigner.)
You mentioned: "... no D defined by design, because the DigestSigner provides that." Doesn't that mean that you are creating a dependency that is often not valid? (As per some mentioned examples.) DigestSigner may (in all cases?) depend on PrehashSigner, but the reverse is not true, IIUC.

edit@tarcieri comment regarding evolution of ecdsa should not be contradicted or negated by my comments. If you think so, LMK.

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

I would define a D type on PrehashSigner.

Again, PrehashSigner doesn't have a D parameter by design. PrehashSigner is explicitly designed to be able to handle different hash inputs at runtime. The ecdsa crate implements this logic:

See:

Even if we were to abandon that requirement, a D parameter doesn't make sense in all of the potential contexts where PrehashSigner would be used.

Doesn't that mean that you are creating a dependency that is often not valid? (As per some mentioned examples.) DigestSigner may (in all cases?)

Adding a D parameter to PrehashSigner would do just that: it's primarily useful for RFC6979, and unless you're using a digest to instantiate HMAC-DRBG or some similar KDF, it provides no value, and indeed there would be nothing for a user to pass, so what are they supposed to pass in that case?

And again, there's already a similar trait with a D parameter: DigestSigner.

That's why I suggested that if you really, really want to expose it as a user-configurable parameter, it needs to be on the type, not the trait.

If you want a concrete example, PrehashSigner could be used to support Ed25519ph. Internally Ed25519ph is explicitly mandated to use SHA-512 to perform RFC6979-like deterministic derivation of a once-per-key-message-pair epehemeral scalar. Use anything else but SHA-512, and the construction ceases to be "Ed25519ph".

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@tarcieri just to check, I propose a D to pass on to crate::generate::secret_number_rfc6979::<D>(&self, prehash). The value prehash is a slice of unknown length and that is fine, i.e. works exactly as expected.

edit if your suggestion is to hard-code SHA-256 for it and leave it at that, then that's fine by me. (IIUC that works for me)

@tarcieri

Copy link
Copy Markdown
Member

@cobratbq aah, that's fine

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@cobratbq aah, that's fine

I'm glad this is clarified. I had wanted to align the solution direction, but discussions got a bit more complicated. To get back to my original questions/concerns. So,

  • regarding the HMAC-DRBG, would it be better to: (1.) hardcode for DSA? Or (2.) define a generic D that is specific to the use-cases of variable or multiple digests (as discussed above)?
  • And are there preferences regarding: modifying PrehashSigner vs. introducing an (intermediary) trait such that the generic type can be defined there? (given interoperability, backwards-compatibility, etc.)

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

regarding the HMAC-DRBG, would it be better to: (1.) hardcode for DSA? Or (2.) define a generic D that is specific to the use-cases of variable or multiple digests (as discussed above)?

If #520 is resolved, you can use the same approach as ECDSA, leveraging dsa::Signature::Digest.

Alternatively, you could add a generic parameter to SigningKey for the digest to instantiate HMAC-DRBG with for RFC6979, with a reasonable default (e.g. Sha256).

And are there preferences regarding: modifying PrehashSigner vs. introducing an (intermediary) trait such that the generic type can be defined there? (given interoperability, backwards-compatibility, etc.)

If you really feel strongly about this, please open an issue on https://github.com/RustCrypto/traits/issues with your concrete proposal.

If you're suggesting adding an additional trait, please note there's something of a "trait explosion" to handle all possible cases: PrehashSigner has an accompanying RandomizedPrehashSigner that is parameterized by an RNG, DigestSigner also has a RandomizedDigestSigner, and all of these need accompanying traits in async-signature.

Note that there are other things we'd potentially like to add, like domain separation, which would further compound this trait explosion.

So I think the bar would be pretty high to add an additional trait, especially given DigestSigner and PrehashSigner already exist.

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

I get the impression you have some reservations. I understand, especially given another issue is involved.

My goal is to have prehash signing and verification exposed as part of the API. Given the complications as discussed above, what do you recommend?

@tarcieri

Copy link
Copy Markdown
Member

Use Sha256 for now and we can circle back on a more generalized solution in #520

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@aumetra@tarcieri I made the necessary changes. I think my changes have not impacted the other build failures. Can you let me know if there is anything left to do? Also, consider squashing the merge, because the history contains mostly useless confusing changes.

Comment threaddsa/Cargo.toml Outdated
@tarcieri
tarcieri merged commit ace98c8 into RustCrypto:masterOct 22, 2022
@tarcieritarcieri mentioned this pull request Oct 29, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@cobratbq@aumetra@tarcieri
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' dsa: expose signing and verifying of prehashed hash value by cobratbq · Pull Request #558 · RustCrypto/signatures · GitHub
Skip to content

dsa: expose signing and verifying of prehashed hash value - #558

Merged
tarcieri merged 11 commits into
RustCrypto:masterfrom
cobratbq:master
Oct 22, 2022
Merged

dsa: expose signing and verifying of prehashed hash value#558
tarcieri merged 11 commits into
RustCrypto:masterfrom
cobratbq:master

Conversation

@cobratbq

Copy link
Copy Markdown
Contributor

Expose the ability for the user to provide a prehashed hash-value for signing/verification. In rare cases, this is needed in other protocols. I have attempted to solve this with a custom Digest implementation, but eventually ran into (forced) finalization which affects the hash value.

@aumetra

Copy link
Copy Markdown
Contributor

Would you mind changing the code to expose the prehashed signing/verifying functionality via the hazmat traits of the signature crate (just for the sake of having a unified interface)?
You would need to adjust the version requirements of signature to a minimum version of 1.6 (1.6.4 specifically if you want to implement RandomizedPrehashSigner, too).

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

Sure, I'll have a look. Thanks for pointing out a direction.

@cobratbq

cobratbq commented Oct 20, 2022

Copy link
Copy Markdown
ContributorAuthor

@aumetra can you confirm that there is no component of randomness in this logic? The crate::generate::secret_number_rfc6979 does not seem to depend on randomness.

I will follow up later: I've got some things to figure out, e.g. whether the trait needs the D trait (Digest + OutputSize + ..), whether the randomness should be in and I'm currently using an undesirable implementation, etc.

@tarcieri

Copy link
Copy Markdown
Member

@aumetra for now you can just do the deterministic API.

RFC6979 supports additional randomness, but that can be added as a followup.

@aumetra

aumetra commented Oct 20, 2022

Copy link
Copy Markdown
Contributor

can you confirm that there is no component of randomness in this logic?

Yes, the logic of the function (as it currently is) is fully deterministic

@tarcieri

Copy link
Copy Markdown
Member

@cobratbq looks like one clippy nit for a needless borrow

@tarcieri

Copy link
Copy Markdown
Member

Yes, the logic of the function (as it currently is) is fully deterministic

@aumetra it actually also supports using an RNG:

impl<D>RandomizedDigestSigner<D,Signature>forSigningKey
where
D:Digest,
{
fntry_sign_digest_with_rng(
&self,
mutrng:implCryptoRng + RngCore,
digest:D,
) -> Result<Signature, signature::Error>{
let ks = crate::generate::secret_number(&mut rng,self.verifying_key().components())
.ok_or_else(signature::Error::new)?;
let hash = digest.finalize();
self.sign_prehashed(ks,&hash)
.ok_or_else(signature::Error::new)
}
}

@aumetra

aumetra commented Oct 20, 2022

Copy link
Copy Markdown
Contributor

Oh yeah, I was only talking about the function crate::generate::secret_number_rfc6979 since they asked explicitly about that one

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@tarcieri@aumetra quick follow-up, I'd appreciate your feedback

  1. I added an assertion to ensure hash length is at least the same strength as modulus. Before it just took the minimum of two numbers.
  2. verify_prehash is implemented, seems straight-forward enough. No issue there.
  3. sign_prehash, IIUC, cannot be implemented with this combination of trait PrehashSigner<Signature> and the requirement of a D: Digest + BlockSizeUser + FixedOutputReset for crate::generate::secret_number_rfc6979.

Do you have an idea what to do with (3)? The type for D has to be somewhere, and on the trait seems to make most sense. (Branch, currently, does not build but does illustrate the choke point.)

@tarcieri

Copy link
Copy Markdown
Member

@cobratbqDigestSigner is already the trait which has a D parameter.

The ECDSA crate uses a curve-specific digest when implementing PrehashSigner:

https://github.com/RustCrypto/signatures/blob/3a13e77/ecdsa/src/sign.rs#L268

Failing that you could just use e.g. SHA-256, or some other SHA2-family digest which has test vectors in RFC6979.

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@tarcieri I've been looking into the ecdsa implementation. From what I can tell, I suspect that the PrehashSigner trait is currently ill-defined: you rely on that trait to define deterministic signing behavior that needs to have a Digest defined, however it is not part of this trait.

It works for ecdsa because of luck, i.e. it is coincidentally already defined on the implementation. For dsa that won't work, because the spec allows for any number of approved digests, while PrehashSigner requests/requires an input parameter prehash with a run-time bound: bit-length at least as large as bit-length of digest. (Also, DigestSigner is not necessarily present if itself not required.)

Is this something you would want to change/fix or should I work around it, like effectively (probably by accident) happened with ecdsa?

@aumetra

aumetra commented Oct 21, 2022

Copy link
Copy Markdown
Contributor

The logic in ecdsa isn't really ill-defined. Incompatible (in specific cases) with implementations that allow free definition of the used digest function, sure, but not wrong.
The crux is that ECDSA has recommended hash digests for each curve. P256 -> SHA-256, P384 -> SHA-384, you get the picture.
To cite the FIPS 186-4:

It is recommended that the security strength associated with the bit length of n and the security
strength of the hash function be the same unless an agreement has been made between
participating entities to use a stronger hash function

The ecdsa crate uses the associated type of the recommended digest to generate the ad-hoc key with.

DSA however doesn't really have a "recommended" hash digest. Most of the standards reference SHA-1 which is actually pretty awful because, you know.
This is actually tangentially related to a discussion in #520, where we are practically still trying to figure out which digest should be used for a potential Signer/Verifier implementation.


Also regarding your change where you added the assertion on the hash length and the size of parameter q, if I read the standard correctly, this restriction only applies to RSA (specifically RSA key pair generation).

The full quote is as follows:

An approved hash function, as specified in FIPS 180, shall be used during the generation of key
pairs and digital signatures. When used during the generation of an RSA key pair (as specified in
this Standard), the length in bits of the hash function output block shall meet or exceed the
security strength associated with the bit length of the modulus n (see SP 800-57).

The quote can be found in FIPS 186-4, under section 5.1 "RSA Key Pair Generation"

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

@cobratbq the current shape of the ecdsa crate is based on years of iteration and hard-won knowledge about how RFC6979 is deployed alongside ECDSA in many real-world scenarios. In fact, we have to accommodate cases where the digest used for instantiating HMAC-DRBG is different from the one used to hash the message.

We previously did what I suspect you're suggesting: always use the D parameter of DigestSigner to instantiate HMAC-DRBG. That felt great at first, but we ended up with many, many requests to match existing test vectors for systems which use two different hashes, namely Keccak256 for message hashing and SHA-256 for RFC6979 as commonly used in the Ethereum ecosystem.

Unless you have such specific requirements as well, you can instantiate RFC6979 with the secure cryptographic digest of your choice, which is why I recommended SHA-256 as a reasonable default. Again, it doesn't have to match what was used to hash the message, and really the only party who can discern what hash was used was the signer. The only time it really matters at all is if you are trying to match specific deterministic test vectors.

PrehashSigner doesn't have a D parameter by design: the DigestSigner trait provides that. If you really, really want to have a generic parameter for D when using PrehashSigner, it needs to be defined on the type, not the trait.

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@aumetra: okay, you are right. Let me rephrase. Given the way DSA is defined, there is a open variable for the digest to be used. (Instead of a single prescribed digest, a set of possible values is defined.) So, this is effectively requires extending the trait, i.e. introducing a new variable component. For ecdsa these can be hard-coded to the prescribed digest, just not for dsa. For practical convenience, maybe it needs to be structured differently so that the definition of PrehashSigner itself isn't changed. I'm questioning the trait definition, because:

  1. I'd like to avoid introducing a specific hash-implementation where only a abstract concept is needed. (Sha1 i.s.o. Digest, for example, as sha1 is only a dev-dependency now.)
  2. From my limited understanding, it seems that PrehashSigner closely relates to the secret_number_rfc6979, except that the one "variable" is exposed.
  3. I'd like to avoid contributing an incomplete workaround, if possible/reasonable.

@aumetra thanks for catching that, I see that in traversing the spec, I accidentally ended up with something RSA specific. That's my fuck-up.

@tarcieri actually, it's the inverse: with my current understanding, I would define a D type on PrehashSigner. In implementations of ecdsa it may end up "hard-coded", in other implementations it may correspond to D in DigestSigner, however in the basis it is a free variable. (And if I understand your examples, this should not be an issue given there is no constraint to DigestSigner.)
You mentioned: "... no D defined by design, because the DigestSigner provides that." Doesn't that mean that you are creating a dependency that is often not valid? (As per some mentioned examples.) DigestSigner may (in all cases?) depend on PrehashSigner, but the reverse is not true, IIUC.

edit@tarcieri comment regarding evolution of ecdsa should not be contradicted or negated by my comments. If you think so, LMK.

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

I would define a D type on PrehashSigner.

Again, PrehashSigner doesn't have a D parameter by design. PrehashSigner is explicitly designed to be able to handle different hash inputs at runtime. The ecdsa crate implements this logic:

See:

Even if we were to abandon that requirement, a D parameter doesn't make sense in all of the potential contexts where PrehashSigner would be used.

Doesn't that mean that you are creating a dependency that is often not valid? (As per some mentioned examples.) DigestSigner may (in all cases?)

Adding a D parameter to PrehashSigner would do just that: it's primarily useful for RFC6979, and unless you're using a digest to instantiate HMAC-DRBG or some similar KDF, it provides no value, and indeed there would be nothing for a user to pass, so what are they supposed to pass in that case?

And again, there's already a similar trait with a D parameter: DigestSigner.

That's why I suggested that if you really, really want to expose it as a user-configurable parameter, it needs to be on the type, not the trait.

If you want a concrete example, PrehashSigner could be used to support Ed25519ph. Internally Ed25519ph is explicitly mandated to use SHA-512 to perform RFC6979-like deterministic derivation of a once-per-key-message-pair epehemeral scalar. Use anything else but SHA-512, and the construction ceases to be "Ed25519ph".

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@tarcieri just to check, I propose a D to pass on to crate::generate::secret_number_rfc6979::<D>(&self, prehash). The value prehash is a slice of unknown length and that is fine, i.e. works exactly as expected.

edit if your suggestion is to hard-code SHA-256 for it and leave it at that, then that's fine by me. (IIUC that works for me)

@tarcieri

Copy link
Copy Markdown
Member

@cobratbq aah, that's fine

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@cobratbq aah, that's fine

I'm glad this is clarified. I had wanted to align the solution direction, but discussions got a bit more complicated. To get back to my original questions/concerns. So,

  • regarding the HMAC-DRBG, would it be better to: (1.) hardcode for DSA? Or (2.) define a generic D that is specific to the use-cases of variable or multiple digests (as discussed above)?
  • And are there preferences regarding: modifying PrehashSigner vs. introducing an (intermediary) trait such that the generic type can be defined there? (given interoperability, backwards-compatibility, etc.)

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

regarding the HMAC-DRBG, would it be better to: (1.) hardcode for DSA? Or (2.) define a generic D that is specific to the use-cases of variable or multiple digests (as discussed above)?

If #520 is resolved, you can use the same approach as ECDSA, leveraging dsa::Signature::Digest.

Alternatively, you could add a generic parameter to SigningKey for the digest to instantiate HMAC-DRBG with for RFC6979, with a reasonable default (e.g. Sha256).

And are there preferences regarding: modifying PrehashSigner vs. introducing an (intermediary) trait such that the generic type can be defined there? (given interoperability, backwards-compatibility, etc.)

If you really feel strongly about this, please open an issue on https://github.com/RustCrypto/traits/issues with your concrete proposal.

If you're suggesting adding an additional trait, please note there's something of a "trait explosion" to handle all possible cases: PrehashSigner has an accompanying RandomizedPrehashSigner that is parameterized by an RNG, DigestSigner also has a RandomizedDigestSigner, and all of these need accompanying traits in async-signature.

Note that there are other things we'd potentially like to add, like domain separation, which would further compound this trait explosion.

So I think the bar would be pretty high to add an additional trait, especially given DigestSigner and PrehashSigner already exist.

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

I get the impression you have some reservations. I understand, especially given another issue is involved.

My goal is to have prehash signing and verification exposed as part of the API. Given the complications as discussed above, what do you recommend?

@tarcieri

Copy link
Copy Markdown
Member

Use Sha256 for now and we can circle back on a more generalized solution in #520

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@aumetra@tarcieri I made the necessary changes. I think my changes have not impacted the other build failures. Can you let me know if there is anything left to do? Also, consider squashing the merge, because the history contains mostly useless confusing changes.

Comment threaddsa/Cargo.toml Outdated
@tarcieri
tarcieri merged commit ace98c8 into RustCrypto:masterOct 22, 2022
@tarcieritarcieri mentioned this pull request Oct 29, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@cobratbq@aumetra@tarcieri
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' dsa: expose signing and verifying of prehashed hash value by cobratbq · Pull Request #558 · RustCrypto/signatures · GitHub
Skip to content

dsa: expose signing and verifying of prehashed hash value - #558

Merged
tarcieri merged 11 commits into
RustCrypto:masterfrom
cobratbq:master
Oct 22, 2022
Merged

dsa: expose signing and verifying of prehashed hash value#558
tarcieri merged 11 commits into
RustCrypto:masterfrom
cobratbq:master

Conversation

@cobratbq

Copy link
Copy Markdown
Contributor

Expose the ability for the user to provide a prehashed hash-value for signing/verification. In rare cases, this is needed in other protocols. I have attempted to solve this with a custom Digest implementation, but eventually ran into (forced) finalization which affects the hash value.

@aumetra

Copy link
Copy Markdown
Contributor

Would you mind changing the code to expose the prehashed signing/verifying functionality via the hazmat traits of the signature crate (just for the sake of having a unified interface)?
You would need to adjust the version requirements of signature to a minimum version of 1.6 (1.6.4 specifically if you want to implement RandomizedPrehashSigner, too).

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

Sure, I'll have a look. Thanks for pointing out a direction.

@cobratbq

cobratbq commented Oct 20, 2022

Copy link
Copy Markdown
ContributorAuthor

@aumetra can you confirm that there is no component of randomness in this logic? The crate::generate::secret_number_rfc6979 does not seem to depend on randomness.

I will follow up later: I've got some things to figure out, e.g. whether the trait needs the D trait (Digest + OutputSize + ..), whether the randomness should be in and I'm currently using an undesirable implementation, etc.

@tarcieri

Copy link
Copy Markdown
Member

@aumetra for now you can just do the deterministic API.

RFC6979 supports additional randomness, but that can be added as a followup.

@aumetra

aumetra commented Oct 20, 2022

Copy link
Copy Markdown
Contributor

can you confirm that there is no component of randomness in this logic?

Yes, the logic of the function (as it currently is) is fully deterministic

@tarcieri

Copy link
Copy Markdown
Member

@cobratbq looks like one clippy nit for a needless borrow

@tarcieri

Copy link
Copy Markdown
Member

Yes, the logic of the function (as it currently is) is fully deterministic

@aumetra it actually also supports using an RNG:

impl<D>RandomizedDigestSigner<D,Signature>forSigningKey
where
D:Digest,
{
fntry_sign_digest_with_rng(
&self,
mutrng:implCryptoRng + RngCore,
digest:D,
) -> Result<Signature, signature::Error>{
let ks = crate::generate::secret_number(&mut rng,self.verifying_key().components())
.ok_or_else(signature::Error::new)?;
let hash = digest.finalize();
self.sign_prehashed(ks,&hash)
.ok_or_else(signature::Error::new)
}
}

@aumetra

aumetra commented Oct 20, 2022

Copy link
Copy Markdown
Contributor

Oh yeah, I was only talking about the function crate::generate::secret_number_rfc6979 since they asked explicitly about that one

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@tarcieri@aumetra quick follow-up, I'd appreciate your feedback

  1. I added an assertion to ensure hash length is at least the same strength as modulus. Before it just took the minimum of two numbers.
  2. verify_prehash is implemented, seems straight-forward enough. No issue there.
  3. sign_prehash, IIUC, cannot be implemented with this combination of trait PrehashSigner<Signature> and the requirement of a D: Digest + BlockSizeUser + FixedOutputReset for crate::generate::secret_number_rfc6979.

Do you have an idea what to do with (3)? The type for D has to be somewhere, and on the trait seems to make most sense. (Branch, currently, does not build but does illustrate the choke point.)

@tarcieri

Copy link
Copy Markdown
Member

@cobratbqDigestSigner is already the trait which has a D parameter.

The ECDSA crate uses a curve-specific digest when implementing PrehashSigner:

https://github.com/RustCrypto/signatures/blob/3a13e77/ecdsa/src/sign.rs#L268

Failing that you could just use e.g. SHA-256, or some other SHA2-family digest which has test vectors in RFC6979.

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@tarcieri I've been looking into the ecdsa implementation. From what I can tell, I suspect that the PrehashSigner trait is currently ill-defined: you rely on that trait to define deterministic signing behavior that needs to have a Digest defined, however it is not part of this trait.

It works for ecdsa because of luck, i.e. it is coincidentally already defined on the implementation. For dsa that won't work, because the spec allows for any number of approved digests, while PrehashSigner requests/requires an input parameter prehash with a run-time bound: bit-length at least as large as bit-length of digest. (Also, DigestSigner is not necessarily present if itself not required.)

Is this something you would want to change/fix or should I work around it, like effectively (probably by accident) happened with ecdsa?

@aumetra

aumetra commented Oct 21, 2022

Copy link
Copy Markdown
Contributor

The logic in ecdsa isn't really ill-defined. Incompatible (in specific cases) with implementations that allow free definition of the used digest function, sure, but not wrong.
The crux is that ECDSA has recommended hash digests for each curve. P256 -> SHA-256, P384 -> SHA-384, you get the picture.
To cite the FIPS 186-4:

It is recommended that the security strength associated with the bit length of n and the security
strength of the hash function be the same unless an agreement has been made between
participating entities to use a stronger hash function

The ecdsa crate uses the associated type of the recommended digest to generate the ad-hoc key with.

DSA however doesn't really have a "recommended" hash digest. Most of the standards reference SHA-1 which is actually pretty awful because, you know.
This is actually tangentially related to a discussion in #520, where we are practically still trying to figure out which digest should be used for a potential Signer/Verifier implementation.


Also regarding your change where you added the assertion on the hash length and the size of parameter q, if I read the standard correctly, this restriction only applies to RSA (specifically RSA key pair generation).

The full quote is as follows:

An approved hash function, as specified in FIPS 180, shall be used during the generation of key
pairs and digital signatures. When used during the generation of an RSA key pair (as specified in
this Standard), the length in bits of the hash function output block shall meet or exceed the
security strength associated with the bit length of the modulus n (see SP 800-57).

The quote can be found in FIPS 186-4, under section 5.1 "RSA Key Pair Generation"

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

@cobratbq the current shape of the ecdsa crate is based on years of iteration and hard-won knowledge about how RFC6979 is deployed alongside ECDSA in many real-world scenarios. In fact, we have to accommodate cases where the digest used for instantiating HMAC-DRBG is different from the one used to hash the message.

We previously did what I suspect you're suggesting: always use the D parameter of DigestSigner to instantiate HMAC-DRBG. That felt great at first, but we ended up with many, many requests to match existing test vectors for systems which use two different hashes, namely Keccak256 for message hashing and SHA-256 for RFC6979 as commonly used in the Ethereum ecosystem.

Unless you have such specific requirements as well, you can instantiate RFC6979 with the secure cryptographic digest of your choice, which is why I recommended SHA-256 as a reasonable default. Again, it doesn't have to match what was used to hash the message, and really the only party who can discern what hash was used was the signer. The only time it really matters at all is if you are trying to match specific deterministic test vectors.

PrehashSigner doesn't have a D parameter by design: the DigestSigner trait provides that. If you really, really want to have a generic parameter for D when using PrehashSigner, it needs to be defined on the type, not the trait.

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@aumetra: okay, you are right. Let me rephrase. Given the way DSA is defined, there is a open variable for the digest to be used. (Instead of a single prescribed digest, a set of possible values is defined.) So, this is effectively requires extending the trait, i.e. introducing a new variable component. For ecdsa these can be hard-coded to the prescribed digest, just not for dsa. For practical convenience, maybe it needs to be structured differently so that the definition of PrehashSigner itself isn't changed. I'm questioning the trait definition, because:

  1. I'd like to avoid introducing a specific hash-implementation where only a abstract concept is needed. (Sha1 i.s.o. Digest, for example, as sha1 is only a dev-dependency now.)
  2. From my limited understanding, it seems that PrehashSigner closely relates to the secret_number_rfc6979, except that the one "variable" is exposed.
  3. I'd like to avoid contributing an incomplete workaround, if possible/reasonable.

@aumetra thanks for catching that, I see that in traversing the spec, I accidentally ended up with something RSA specific. That's my fuck-up.

@tarcieri actually, it's the inverse: with my current understanding, I would define a D type on PrehashSigner. In implementations of ecdsa it may end up "hard-coded", in other implementations it may correspond to D in DigestSigner, however in the basis it is a free variable. (And if I understand your examples, this should not be an issue given there is no constraint to DigestSigner.)
You mentioned: "... no D defined by design, because the DigestSigner provides that." Doesn't that mean that you are creating a dependency that is often not valid? (As per some mentioned examples.) DigestSigner may (in all cases?) depend on PrehashSigner, but the reverse is not true, IIUC.

edit@tarcieri comment regarding evolution of ecdsa should not be contradicted or negated by my comments. If you think so, LMK.

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

I would define a D type on PrehashSigner.

Again, PrehashSigner doesn't have a D parameter by design. PrehashSigner is explicitly designed to be able to handle different hash inputs at runtime. The ecdsa crate implements this logic:

See:

Even if we were to abandon that requirement, a D parameter doesn't make sense in all of the potential contexts where PrehashSigner would be used.

Doesn't that mean that you are creating a dependency that is often not valid? (As per some mentioned examples.) DigestSigner may (in all cases?)

Adding a D parameter to PrehashSigner would do just that: it's primarily useful for RFC6979, and unless you're using a digest to instantiate HMAC-DRBG or some similar KDF, it provides no value, and indeed there would be nothing for a user to pass, so what are they supposed to pass in that case?

And again, there's already a similar trait with a D parameter: DigestSigner.

That's why I suggested that if you really, really want to expose it as a user-configurable parameter, it needs to be on the type, not the trait.

If you want a concrete example, PrehashSigner could be used to support Ed25519ph. Internally Ed25519ph is explicitly mandated to use SHA-512 to perform RFC6979-like deterministic derivation of a once-per-key-message-pair epehemeral scalar. Use anything else but SHA-512, and the construction ceases to be "Ed25519ph".

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@tarcieri just to check, I propose a D to pass on to crate::generate::secret_number_rfc6979::<D>(&self, prehash). The value prehash is a slice of unknown length and that is fine, i.e. works exactly as expected.

edit if your suggestion is to hard-code SHA-256 for it and leave it at that, then that's fine by me. (IIUC that works for me)

@tarcieri

Copy link
Copy Markdown
Member

@cobratbq aah, that's fine

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@cobratbq aah, that's fine

I'm glad this is clarified. I had wanted to align the solution direction, but discussions got a bit more complicated. To get back to my original questions/concerns. So,

  • regarding the HMAC-DRBG, would it be better to: (1.) hardcode for DSA? Or (2.) define a generic D that is specific to the use-cases of variable or multiple digests (as discussed above)?
  • And are there preferences regarding: modifying PrehashSigner vs. introducing an (intermediary) trait such that the generic type can be defined there? (given interoperability, backwards-compatibility, etc.)

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

regarding the HMAC-DRBG, would it be better to: (1.) hardcode for DSA? Or (2.) define a generic D that is specific to the use-cases of variable or multiple digests (as discussed above)?

If #520 is resolved, you can use the same approach as ECDSA, leveraging dsa::Signature::Digest.

Alternatively, you could add a generic parameter to SigningKey for the digest to instantiate HMAC-DRBG with for RFC6979, with a reasonable default (e.g. Sha256).

And are there preferences regarding: modifying PrehashSigner vs. introducing an (intermediary) trait such that the generic type can be defined there? (given interoperability, backwards-compatibility, etc.)

If you really feel strongly about this, please open an issue on https://github.com/RustCrypto/traits/issues with your concrete proposal.

If you're suggesting adding an additional trait, please note there's something of a "trait explosion" to handle all possible cases: PrehashSigner has an accompanying RandomizedPrehashSigner that is parameterized by an RNG, DigestSigner also has a RandomizedDigestSigner, and all of these need accompanying traits in async-signature.

Note that there are other things we'd potentially like to add, like domain separation, which would further compound this trait explosion.

So I think the bar would be pretty high to add an additional trait, especially given DigestSigner and PrehashSigner already exist.

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

I get the impression you have some reservations. I understand, especially given another issue is involved.

My goal is to have prehash signing and verification exposed as part of the API. Given the complications as discussed above, what do you recommend?

@tarcieri

Copy link
Copy Markdown
Member

Use Sha256 for now and we can circle back on a more generalized solution in #520

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@aumetra@tarcieri I made the necessary changes. I think my changes have not impacted the other build failures. Can you let me know if there is anything left to do? Also, consider squashing the merge, because the history contains mostly useless confusing changes.

Comment threaddsa/Cargo.toml Outdated
@tarcieri
tarcieri merged commit ace98c8 into RustCrypto:masterOct 22, 2022
@tarcieritarcieri mentioned this pull request Oct 29, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@cobratbq@aumetra@tarcieri
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' dsa: expose signing and verifying of prehashed hash value by cobratbq · Pull Request #558 · RustCrypto/signatures · GitHub
Skip to content

dsa: expose signing and verifying of prehashed hash value - #558

Merged
tarcieri merged 11 commits into
RustCrypto:masterfrom
cobratbq:master
Oct 22, 2022
Merged

dsa: expose signing and verifying of prehashed hash value#558
tarcieri merged 11 commits into
RustCrypto:masterfrom
cobratbq:master

Conversation

@cobratbq

Copy link
Copy Markdown
Contributor

Expose the ability for the user to provide a prehashed hash-value for signing/verification. In rare cases, this is needed in other protocols. I have attempted to solve this with a custom Digest implementation, but eventually ran into (forced) finalization which affects the hash value.

@aumetra

Copy link
Copy Markdown
Contributor

Would you mind changing the code to expose the prehashed signing/verifying functionality via the hazmat traits of the signature crate (just for the sake of having a unified interface)?
You would need to adjust the version requirements of signature to a minimum version of 1.6 (1.6.4 specifically if you want to implement RandomizedPrehashSigner, too).

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

Sure, I'll have a look. Thanks for pointing out a direction.

@cobratbq

cobratbq commented Oct 20, 2022

Copy link
Copy Markdown
ContributorAuthor

@aumetra can you confirm that there is no component of randomness in this logic? The crate::generate::secret_number_rfc6979 does not seem to depend on randomness.

I will follow up later: I've got some things to figure out, e.g. whether the trait needs the D trait (Digest + OutputSize + ..), whether the randomness should be in and I'm currently using an undesirable implementation, etc.

@tarcieri

Copy link
Copy Markdown
Member

@aumetra for now you can just do the deterministic API.

RFC6979 supports additional randomness, but that can be added as a followup.

@aumetra

aumetra commented Oct 20, 2022

Copy link
Copy Markdown
Contributor

can you confirm that there is no component of randomness in this logic?

Yes, the logic of the function (as it currently is) is fully deterministic

@tarcieri

Copy link
Copy Markdown
Member

@cobratbq looks like one clippy nit for a needless borrow

@tarcieri

Copy link
Copy Markdown
Member

Yes, the logic of the function (as it currently is) is fully deterministic

@aumetra it actually also supports using an RNG:

impl<D>RandomizedDigestSigner<D,Signature>forSigningKey
where
D:Digest,
{
fntry_sign_digest_with_rng(
&self,
mutrng:implCryptoRng + RngCore,
digest:D,
) -> Result<Signature, signature::Error>{
let ks = crate::generate::secret_number(&mut rng,self.verifying_key().components())
.ok_or_else(signature::Error::new)?;
let hash = digest.finalize();
self.sign_prehashed(ks,&hash)
.ok_or_else(signature::Error::new)
}
}

@aumetra

aumetra commented Oct 20, 2022

Copy link
Copy Markdown
Contributor

Oh yeah, I was only talking about the function crate::generate::secret_number_rfc6979 since they asked explicitly about that one

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@tarcieri@aumetra quick follow-up, I'd appreciate your feedback

  1. I added an assertion to ensure hash length is at least the same strength as modulus. Before it just took the minimum of two numbers.
  2. verify_prehash is implemented, seems straight-forward enough. No issue there.
  3. sign_prehash, IIUC, cannot be implemented with this combination of trait PrehashSigner<Signature> and the requirement of a D: Digest + BlockSizeUser + FixedOutputReset for crate::generate::secret_number_rfc6979.

Do you have an idea what to do with (3)? The type for D has to be somewhere, and on the trait seems to make most sense. (Branch, currently, does not build but does illustrate the choke point.)

@tarcieri

Copy link
Copy Markdown
Member

@cobratbqDigestSigner is already the trait which has a D parameter.

The ECDSA crate uses a curve-specific digest when implementing PrehashSigner:

https://github.com/RustCrypto/signatures/blob/3a13e77/ecdsa/src/sign.rs#L268

Failing that you could just use e.g. SHA-256, or some other SHA2-family digest which has test vectors in RFC6979.

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@tarcieri I've been looking into the ecdsa implementation. From what I can tell, I suspect that the PrehashSigner trait is currently ill-defined: you rely on that trait to define deterministic signing behavior that needs to have a Digest defined, however it is not part of this trait.

It works for ecdsa because of luck, i.e. it is coincidentally already defined on the implementation. For dsa that won't work, because the spec allows for any number of approved digests, while PrehashSigner requests/requires an input parameter prehash with a run-time bound: bit-length at least as large as bit-length of digest. (Also, DigestSigner is not necessarily present if itself not required.)

Is this something you would want to change/fix or should I work around it, like effectively (probably by accident) happened with ecdsa?

@aumetra

aumetra commented Oct 21, 2022

Copy link
Copy Markdown
Contributor

The logic in ecdsa isn't really ill-defined. Incompatible (in specific cases) with implementations that allow free definition of the used digest function, sure, but not wrong.
The crux is that ECDSA has recommended hash digests for each curve. P256 -> SHA-256, P384 -> SHA-384, you get the picture.
To cite the FIPS 186-4:

It is recommended that the security strength associated with the bit length of n and the security
strength of the hash function be the same unless an agreement has been made between
participating entities to use a stronger hash function

The ecdsa crate uses the associated type of the recommended digest to generate the ad-hoc key with.

DSA however doesn't really have a "recommended" hash digest. Most of the standards reference SHA-1 which is actually pretty awful because, you know.
This is actually tangentially related to a discussion in #520, where we are practically still trying to figure out which digest should be used for a potential Signer/Verifier implementation.


Also regarding your change where you added the assertion on the hash length and the size of parameter q, if I read the standard correctly, this restriction only applies to RSA (specifically RSA key pair generation).

The full quote is as follows:

An approved hash function, as specified in FIPS 180, shall be used during the generation of key
pairs and digital signatures. When used during the generation of an RSA key pair (as specified in
this Standard), the length in bits of the hash function output block shall meet or exceed the
security strength associated with the bit length of the modulus n (see SP 800-57).

The quote can be found in FIPS 186-4, under section 5.1 "RSA Key Pair Generation"

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

@cobratbq the current shape of the ecdsa crate is based on years of iteration and hard-won knowledge about how RFC6979 is deployed alongside ECDSA in many real-world scenarios. In fact, we have to accommodate cases where the digest used for instantiating HMAC-DRBG is different from the one used to hash the message.

We previously did what I suspect you're suggesting: always use the D parameter of DigestSigner to instantiate HMAC-DRBG. That felt great at first, but we ended up with many, many requests to match existing test vectors for systems which use two different hashes, namely Keccak256 for message hashing and SHA-256 for RFC6979 as commonly used in the Ethereum ecosystem.

Unless you have such specific requirements as well, you can instantiate RFC6979 with the secure cryptographic digest of your choice, which is why I recommended SHA-256 as a reasonable default. Again, it doesn't have to match what was used to hash the message, and really the only party who can discern what hash was used was the signer. The only time it really matters at all is if you are trying to match specific deterministic test vectors.

PrehashSigner doesn't have a D parameter by design: the DigestSigner trait provides that. If you really, really want to have a generic parameter for D when using PrehashSigner, it needs to be defined on the type, not the trait.

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@aumetra: okay, you are right. Let me rephrase. Given the way DSA is defined, there is a open variable for the digest to be used. (Instead of a single prescribed digest, a set of possible values is defined.) So, this is effectively requires extending the trait, i.e. introducing a new variable component. For ecdsa these can be hard-coded to the prescribed digest, just not for dsa. For practical convenience, maybe it needs to be structured differently so that the definition of PrehashSigner itself isn't changed. I'm questioning the trait definition, because:

  1. I'd like to avoid introducing a specific hash-implementation where only a abstract concept is needed. (Sha1 i.s.o. Digest, for example, as sha1 is only a dev-dependency now.)
  2. From my limited understanding, it seems that PrehashSigner closely relates to the secret_number_rfc6979, except that the one "variable" is exposed.
  3. I'd like to avoid contributing an incomplete workaround, if possible/reasonable.

@aumetra thanks for catching that, I see that in traversing the spec, I accidentally ended up with something RSA specific. That's my fuck-up.

@tarcieri actually, it's the inverse: with my current understanding, I would define a D type on PrehashSigner. In implementations of ecdsa it may end up "hard-coded", in other implementations it may correspond to D in DigestSigner, however in the basis it is a free variable. (And if I understand your examples, this should not be an issue given there is no constraint to DigestSigner.)
You mentioned: "... no D defined by design, because the DigestSigner provides that." Doesn't that mean that you are creating a dependency that is often not valid? (As per some mentioned examples.) DigestSigner may (in all cases?) depend on PrehashSigner, but the reverse is not true, IIUC.

edit@tarcieri comment regarding evolution of ecdsa should not be contradicted or negated by my comments. If you think so, LMK.

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

I would define a D type on PrehashSigner.

Again, PrehashSigner doesn't have a D parameter by design. PrehashSigner is explicitly designed to be able to handle different hash inputs at runtime. The ecdsa crate implements this logic:

See:

Even if we were to abandon that requirement, a D parameter doesn't make sense in all of the potential contexts where PrehashSigner would be used.

Doesn't that mean that you are creating a dependency that is often not valid? (As per some mentioned examples.) DigestSigner may (in all cases?)

Adding a D parameter to PrehashSigner would do just that: it's primarily useful for RFC6979, and unless you're using a digest to instantiate HMAC-DRBG or some similar KDF, it provides no value, and indeed there would be nothing for a user to pass, so what are they supposed to pass in that case?

And again, there's already a similar trait with a D parameter: DigestSigner.

That's why I suggested that if you really, really want to expose it as a user-configurable parameter, it needs to be on the type, not the trait.

If you want a concrete example, PrehashSigner could be used to support Ed25519ph. Internally Ed25519ph is explicitly mandated to use SHA-512 to perform RFC6979-like deterministic derivation of a once-per-key-message-pair epehemeral scalar. Use anything else but SHA-512, and the construction ceases to be "Ed25519ph".

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@tarcieri just to check, I propose a D to pass on to crate::generate::secret_number_rfc6979::<D>(&self, prehash). The value prehash is a slice of unknown length and that is fine, i.e. works exactly as expected.

edit if your suggestion is to hard-code SHA-256 for it and leave it at that, then that's fine by me. (IIUC that works for me)

@tarcieri

Copy link
Copy Markdown
Member

@cobratbq aah, that's fine

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@cobratbq aah, that's fine

I'm glad this is clarified. I had wanted to align the solution direction, but discussions got a bit more complicated. To get back to my original questions/concerns. So,

  • regarding the HMAC-DRBG, would it be better to: (1.) hardcode for DSA? Or (2.) define a generic D that is specific to the use-cases of variable or multiple digests (as discussed above)?
  • And are there preferences regarding: modifying PrehashSigner vs. introducing an (intermediary) trait such that the generic type can be defined there? (given interoperability, backwards-compatibility, etc.)

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

regarding the HMAC-DRBG, would it be better to: (1.) hardcode for DSA? Or (2.) define a generic D that is specific to the use-cases of variable or multiple digests (as discussed above)?

If #520 is resolved, you can use the same approach as ECDSA, leveraging dsa::Signature::Digest.

Alternatively, you could add a generic parameter to SigningKey for the digest to instantiate HMAC-DRBG with for RFC6979, with a reasonable default (e.g. Sha256).

And are there preferences regarding: modifying PrehashSigner vs. introducing an (intermediary) trait such that the generic type can be defined there? (given interoperability, backwards-compatibility, etc.)

If you really feel strongly about this, please open an issue on https://github.com/RustCrypto/traits/issues with your concrete proposal.

If you're suggesting adding an additional trait, please note there's something of a "trait explosion" to handle all possible cases: PrehashSigner has an accompanying RandomizedPrehashSigner that is parameterized by an RNG, DigestSigner also has a RandomizedDigestSigner, and all of these need accompanying traits in async-signature.

Note that there are other things we'd potentially like to add, like domain separation, which would further compound this trait explosion.

So I think the bar would be pretty high to add an additional trait, especially given DigestSigner and PrehashSigner already exist.

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

I get the impression you have some reservations. I understand, especially given another issue is involved.

My goal is to have prehash signing and verification exposed as part of the API. Given the complications as discussed above, what do you recommend?

@tarcieri

Copy link
Copy Markdown
Member

Use Sha256 for now and we can circle back on a more generalized solution in #520

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@aumetra@tarcieri I made the necessary changes. I think my changes have not impacted the other build failures. Can you let me know if there is anything left to do? Also, consider squashing the merge, because the history contains mostly useless confusing changes.

Comment threaddsa/Cargo.toml Outdated
@tarcieri
tarcieri merged commit ace98c8 into RustCrypto:masterOct 22, 2022
@tarcieritarcieri mentioned this pull request Oct 29, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@cobratbq@aumetra@tarcieri
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' dsa: expose signing and verifying of prehashed hash value by cobratbq · Pull Request #558 · RustCrypto/signatures · GitHub
Skip to content

dsa: expose signing and verifying of prehashed hash value - #558

Merged
tarcieri merged 11 commits into
RustCrypto:masterfrom
cobratbq:master
Oct 22, 2022
Merged

dsa: expose signing and verifying of prehashed hash value#558
tarcieri merged 11 commits into
RustCrypto:masterfrom
cobratbq:master

Conversation

@cobratbq

Copy link
Copy Markdown
Contributor

Expose the ability for the user to provide a prehashed hash-value for signing/verification. In rare cases, this is needed in other protocols. I have attempted to solve this with a custom Digest implementation, but eventually ran into (forced) finalization which affects the hash value.

@aumetra

Copy link
Copy Markdown
Contributor

Would you mind changing the code to expose the prehashed signing/verifying functionality via the hazmat traits of the signature crate (just for the sake of having a unified interface)?
You would need to adjust the version requirements of signature to a minimum version of 1.6 (1.6.4 specifically if you want to implement RandomizedPrehashSigner, too).

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

Sure, I'll have a look. Thanks for pointing out a direction.

@cobratbq

cobratbq commented Oct 20, 2022

Copy link
Copy Markdown
ContributorAuthor

@aumetra can you confirm that there is no component of randomness in this logic? The crate::generate::secret_number_rfc6979 does not seem to depend on randomness.

I will follow up later: I've got some things to figure out, e.g. whether the trait needs the D trait (Digest + OutputSize + ..), whether the randomness should be in and I'm currently using an undesirable implementation, etc.

@tarcieri

Copy link
Copy Markdown
Member

@aumetra for now you can just do the deterministic API.

RFC6979 supports additional randomness, but that can be added as a followup.

@aumetra

aumetra commented Oct 20, 2022

Copy link
Copy Markdown
Contributor

can you confirm that there is no component of randomness in this logic?

Yes, the logic of the function (as it currently is) is fully deterministic

@tarcieri

Copy link
Copy Markdown
Member

@cobratbq looks like one clippy nit for a needless borrow

@tarcieri

Copy link
Copy Markdown
Member

Yes, the logic of the function (as it currently is) is fully deterministic

@aumetra it actually also supports using an RNG:

impl<D>RandomizedDigestSigner<D,Signature>forSigningKey
where
D:Digest,
{
fntry_sign_digest_with_rng(
&self,
mutrng:implCryptoRng + RngCore,
digest:D,
) -> Result<Signature, signature::Error>{
let ks = crate::generate::secret_number(&mut rng,self.verifying_key().components())
.ok_or_else(signature::Error::new)?;
let hash = digest.finalize();
self.sign_prehashed(ks,&hash)
.ok_or_else(signature::Error::new)
}
}

@aumetra

aumetra commented Oct 20, 2022

Copy link
Copy Markdown
Contributor

Oh yeah, I was only talking about the function crate::generate::secret_number_rfc6979 since they asked explicitly about that one

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@tarcieri@aumetra quick follow-up, I'd appreciate your feedback

  1. I added an assertion to ensure hash length is at least the same strength as modulus. Before it just took the minimum of two numbers.
  2. verify_prehash is implemented, seems straight-forward enough. No issue there.
  3. sign_prehash, IIUC, cannot be implemented with this combination of trait PrehashSigner<Signature> and the requirement of a D: Digest + BlockSizeUser + FixedOutputReset for crate::generate::secret_number_rfc6979.

Do you have an idea what to do with (3)? The type for D has to be somewhere, and on the trait seems to make most sense. (Branch, currently, does not build but does illustrate the choke point.)

@tarcieri

Copy link
Copy Markdown
Member

@cobratbqDigestSigner is already the trait which has a D parameter.

The ECDSA crate uses a curve-specific digest when implementing PrehashSigner:

https://github.com/RustCrypto/signatures/blob/3a13e77/ecdsa/src/sign.rs#L268

Failing that you could just use e.g. SHA-256, or some other SHA2-family digest which has test vectors in RFC6979.

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@tarcieri I've been looking into the ecdsa implementation. From what I can tell, I suspect that the PrehashSigner trait is currently ill-defined: you rely on that trait to define deterministic signing behavior that needs to have a Digest defined, however it is not part of this trait.

It works for ecdsa because of luck, i.e. it is coincidentally already defined on the implementation. For dsa that won't work, because the spec allows for any number of approved digests, while PrehashSigner requests/requires an input parameter prehash with a run-time bound: bit-length at least as large as bit-length of digest. (Also, DigestSigner is not necessarily present if itself not required.)

Is this something you would want to change/fix or should I work around it, like effectively (probably by accident) happened with ecdsa?

@aumetra

aumetra commented Oct 21, 2022

Copy link
Copy Markdown
Contributor

The logic in ecdsa isn't really ill-defined. Incompatible (in specific cases) with implementations that allow free definition of the used digest function, sure, but not wrong.
The crux is that ECDSA has recommended hash digests for each curve. P256 -> SHA-256, P384 -> SHA-384, you get the picture.
To cite the FIPS 186-4:

It is recommended that the security strength associated with the bit length of n and the security
strength of the hash function be the same unless an agreement has been made between
participating entities to use a stronger hash function

The ecdsa crate uses the associated type of the recommended digest to generate the ad-hoc key with.

DSA however doesn't really have a "recommended" hash digest. Most of the standards reference SHA-1 which is actually pretty awful because, you know.
This is actually tangentially related to a discussion in #520, where we are practically still trying to figure out which digest should be used for a potential Signer/Verifier implementation.


Also regarding your change where you added the assertion on the hash length and the size of parameter q, if I read the standard correctly, this restriction only applies to RSA (specifically RSA key pair generation).

The full quote is as follows:

An approved hash function, as specified in FIPS 180, shall be used during the generation of key
pairs and digital signatures. When used during the generation of an RSA key pair (as specified in
this Standard), the length in bits of the hash function output block shall meet or exceed the
security strength associated with the bit length of the modulus n (see SP 800-57).

The quote can be found in FIPS 186-4, under section 5.1 "RSA Key Pair Generation"

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

@cobratbq the current shape of the ecdsa crate is based on years of iteration and hard-won knowledge about how RFC6979 is deployed alongside ECDSA in many real-world scenarios. In fact, we have to accommodate cases where the digest used for instantiating HMAC-DRBG is different from the one used to hash the message.

We previously did what I suspect you're suggesting: always use the D parameter of DigestSigner to instantiate HMAC-DRBG. That felt great at first, but we ended up with many, many requests to match existing test vectors for systems which use two different hashes, namely Keccak256 for message hashing and SHA-256 for RFC6979 as commonly used in the Ethereum ecosystem.

Unless you have such specific requirements as well, you can instantiate RFC6979 with the secure cryptographic digest of your choice, which is why I recommended SHA-256 as a reasonable default. Again, it doesn't have to match what was used to hash the message, and really the only party who can discern what hash was used was the signer. The only time it really matters at all is if you are trying to match specific deterministic test vectors.

PrehashSigner doesn't have a D parameter by design: the DigestSigner trait provides that. If you really, really want to have a generic parameter for D when using PrehashSigner, it needs to be defined on the type, not the trait.

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@aumetra: okay, you are right. Let me rephrase. Given the way DSA is defined, there is a open variable for the digest to be used. (Instead of a single prescribed digest, a set of possible values is defined.) So, this is effectively requires extending the trait, i.e. introducing a new variable component. For ecdsa these can be hard-coded to the prescribed digest, just not for dsa. For practical convenience, maybe it needs to be structured differently so that the definition of PrehashSigner itself isn't changed. I'm questioning the trait definition, because:

  1. I'd like to avoid introducing a specific hash-implementation where only a abstract concept is needed. (Sha1 i.s.o. Digest, for example, as sha1 is only a dev-dependency now.)
  2. From my limited understanding, it seems that PrehashSigner closely relates to the secret_number_rfc6979, except that the one "variable" is exposed.
  3. I'd like to avoid contributing an incomplete workaround, if possible/reasonable.

@aumetra thanks for catching that, I see that in traversing the spec, I accidentally ended up with something RSA specific. That's my fuck-up.

@tarcieri actually, it's the inverse: with my current understanding, I would define a D type on PrehashSigner. In implementations of ecdsa it may end up "hard-coded", in other implementations it may correspond to D in DigestSigner, however in the basis it is a free variable. (And if I understand your examples, this should not be an issue given there is no constraint to DigestSigner.)
You mentioned: "... no D defined by design, because the DigestSigner provides that." Doesn't that mean that you are creating a dependency that is often not valid? (As per some mentioned examples.) DigestSigner may (in all cases?) depend on PrehashSigner, but the reverse is not true, IIUC.

edit@tarcieri comment regarding evolution of ecdsa should not be contradicted or negated by my comments. If you think so, LMK.

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

I would define a D type on PrehashSigner.

Again, PrehashSigner doesn't have a D parameter by design. PrehashSigner is explicitly designed to be able to handle different hash inputs at runtime. The ecdsa crate implements this logic:

See:

Even if we were to abandon that requirement, a D parameter doesn't make sense in all of the potential contexts where PrehashSigner would be used.

Doesn't that mean that you are creating a dependency that is often not valid? (As per some mentioned examples.) DigestSigner may (in all cases?)

Adding a D parameter to PrehashSigner would do just that: it's primarily useful for RFC6979, and unless you're using a digest to instantiate HMAC-DRBG or some similar KDF, it provides no value, and indeed there would be nothing for a user to pass, so what are they supposed to pass in that case?

And again, there's already a similar trait with a D parameter: DigestSigner.

That's why I suggested that if you really, really want to expose it as a user-configurable parameter, it needs to be on the type, not the trait.

If you want a concrete example, PrehashSigner could be used to support Ed25519ph. Internally Ed25519ph is explicitly mandated to use SHA-512 to perform RFC6979-like deterministic derivation of a once-per-key-message-pair epehemeral scalar. Use anything else but SHA-512, and the construction ceases to be "Ed25519ph".

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@tarcieri just to check, I propose a D to pass on to crate::generate::secret_number_rfc6979::<D>(&self, prehash). The value prehash is a slice of unknown length and that is fine, i.e. works exactly as expected.

edit if your suggestion is to hard-code SHA-256 for it and leave it at that, then that's fine by me. (IIUC that works for me)

@tarcieri

Copy link
Copy Markdown
Member

@cobratbq aah, that's fine

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@cobratbq aah, that's fine

I'm glad this is clarified. I had wanted to align the solution direction, but discussions got a bit more complicated. To get back to my original questions/concerns. So,

  • regarding the HMAC-DRBG, would it be better to: (1.) hardcode for DSA? Or (2.) define a generic D that is specific to the use-cases of variable or multiple digests (as discussed above)?
  • And are there preferences regarding: modifying PrehashSigner vs. introducing an (intermediary) trait such that the generic type can be defined there? (given interoperability, backwards-compatibility, etc.)

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

regarding the HMAC-DRBG, would it be better to: (1.) hardcode for DSA? Or (2.) define a generic D that is specific to the use-cases of variable or multiple digests (as discussed above)?

If #520 is resolved, you can use the same approach as ECDSA, leveraging dsa::Signature::Digest.

Alternatively, you could add a generic parameter to SigningKey for the digest to instantiate HMAC-DRBG with for RFC6979, with a reasonable default (e.g. Sha256).

And are there preferences regarding: modifying PrehashSigner vs. introducing an (intermediary) trait such that the generic type can be defined there? (given interoperability, backwards-compatibility, etc.)

If you really feel strongly about this, please open an issue on https://github.com/RustCrypto/traits/issues with your concrete proposal.

If you're suggesting adding an additional trait, please note there's something of a "trait explosion" to handle all possible cases: PrehashSigner has an accompanying RandomizedPrehashSigner that is parameterized by an RNG, DigestSigner also has a RandomizedDigestSigner, and all of these need accompanying traits in async-signature.

Note that there are other things we'd potentially like to add, like domain separation, which would further compound this trait explosion.

So I think the bar would be pretty high to add an additional trait, especially given DigestSigner and PrehashSigner already exist.

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

I get the impression you have some reservations. I understand, especially given another issue is involved.

My goal is to have prehash signing and verification exposed as part of the API. Given the complications as discussed above, what do you recommend?

@tarcieri

Copy link
Copy Markdown
Member

Use Sha256 for now and we can circle back on a more generalized solution in #520

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@aumetra@tarcieri I made the necessary changes. I think my changes have not impacted the other build failures. Can you let me know if there is anything left to do? Also, consider squashing the merge, because the history contains mostly useless confusing changes.

Comment threaddsa/Cargo.toml Outdated
@tarcieri
tarcieri merged commit ace98c8 into RustCrypto:masterOct 22, 2022
@tarcieritarcieri mentioned this pull request Oct 29, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@cobratbq@aumetra@tarcieri
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' dsa: expose signing and verifying of prehashed hash value by cobratbq · Pull Request #558 · RustCrypto/signatures · GitHub
Skip to content

dsa: expose signing and verifying of prehashed hash value - #558

Merged
tarcieri merged 11 commits into
RustCrypto:masterfrom
cobratbq:master
Oct 22, 2022
Merged

dsa: expose signing and verifying of prehashed hash value#558
tarcieri merged 11 commits into
RustCrypto:masterfrom
cobratbq:master

Conversation

@cobratbq

Copy link
Copy Markdown
Contributor

Expose the ability for the user to provide a prehashed hash-value for signing/verification. In rare cases, this is needed in other protocols. I have attempted to solve this with a custom Digest implementation, but eventually ran into (forced) finalization which affects the hash value.

@aumetra

Copy link
Copy Markdown
Contributor

Would you mind changing the code to expose the prehashed signing/verifying functionality via the hazmat traits of the signature crate (just for the sake of having a unified interface)?
You would need to adjust the version requirements of signature to a minimum version of 1.6 (1.6.4 specifically if you want to implement RandomizedPrehashSigner, too).

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

Sure, I'll have a look. Thanks for pointing out a direction.

@cobratbq

cobratbq commented Oct 20, 2022

Copy link
Copy Markdown
ContributorAuthor

@aumetra can you confirm that there is no component of randomness in this logic? The crate::generate::secret_number_rfc6979 does not seem to depend on randomness.

I will follow up later: I've got some things to figure out, e.g. whether the trait needs the D trait (Digest + OutputSize + ..), whether the randomness should be in and I'm currently using an undesirable implementation, etc.

@tarcieri

Copy link
Copy Markdown
Member

@aumetra for now you can just do the deterministic API.

RFC6979 supports additional randomness, but that can be added as a followup.

@aumetra

aumetra commented Oct 20, 2022

Copy link
Copy Markdown
Contributor

can you confirm that there is no component of randomness in this logic?

Yes, the logic of the function (as it currently is) is fully deterministic

@tarcieri

Copy link
Copy Markdown
Member

@cobratbq looks like one clippy nit for a needless borrow

@tarcieri

Copy link
Copy Markdown
Member

Yes, the logic of the function (as it currently is) is fully deterministic

@aumetra it actually also supports using an RNG:

impl<D>RandomizedDigestSigner<D,Signature>forSigningKey
where
D:Digest,
{
fntry_sign_digest_with_rng(
&self,
mutrng:implCryptoRng + RngCore,
digest:D,
) -> Result<Signature, signature::Error>{
let ks = crate::generate::secret_number(&mut rng,self.verifying_key().components())
.ok_or_else(signature::Error::new)?;
let hash = digest.finalize();
self.sign_prehashed(ks,&hash)
.ok_or_else(signature::Error::new)
}
}

@aumetra

aumetra commented Oct 20, 2022

Copy link
Copy Markdown
Contributor

Oh yeah, I was only talking about the function crate::generate::secret_number_rfc6979 since they asked explicitly about that one

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@tarcieri@aumetra quick follow-up, I'd appreciate your feedback

  1. I added an assertion to ensure hash length is at least the same strength as modulus. Before it just took the minimum of two numbers.
  2. verify_prehash is implemented, seems straight-forward enough. No issue there.
  3. sign_prehash, IIUC, cannot be implemented with this combination of trait PrehashSigner<Signature> and the requirement of a D: Digest + BlockSizeUser + FixedOutputReset for crate::generate::secret_number_rfc6979.

Do you have an idea what to do with (3)? The type for D has to be somewhere, and on the trait seems to make most sense. (Branch, currently, does not build but does illustrate the choke point.)

@tarcieri

Copy link
Copy Markdown
Member

@cobratbqDigestSigner is already the trait which has a D parameter.

The ECDSA crate uses a curve-specific digest when implementing PrehashSigner:

https://github.com/RustCrypto/signatures/blob/3a13e77/ecdsa/src/sign.rs#L268

Failing that you could just use e.g. SHA-256, or some other SHA2-family digest which has test vectors in RFC6979.

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@tarcieri I've been looking into the ecdsa implementation. From what I can tell, I suspect that the PrehashSigner trait is currently ill-defined: you rely on that trait to define deterministic signing behavior that needs to have a Digest defined, however it is not part of this trait.

It works for ecdsa because of luck, i.e. it is coincidentally already defined on the implementation. For dsa that won't work, because the spec allows for any number of approved digests, while PrehashSigner requests/requires an input parameter prehash with a run-time bound: bit-length at least as large as bit-length of digest. (Also, DigestSigner is not necessarily present if itself not required.)

Is this something you would want to change/fix or should I work around it, like effectively (probably by accident) happened with ecdsa?

@aumetra

aumetra commented Oct 21, 2022

Copy link
Copy Markdown
Contributor

The logic in ecdsa isn't really ill-defined. Incompatible (in specific cases) with implementations that allow free definition of the used digest function, sure, but not wrong.
The crux is that ECDSA has recommended hash digests for each curve. P256 -> SHA-256, P384 -> SHA-384, you get the picture.
To cite the FIPS 186-4:

It is recommended that the security strength associated with the bit length of n and the security
strength of the hash function be the same unless an agreement has been made between
participating entities to use a stronger hash function

The ecdsa crate uses the associated type of the recommended digest to generate the ad-hoc key with.

DSA however doesn't really have a "recommended" hash digest. Most of the standards reference SHA-1 which is actually pretty awful because, you know.
This is actually tangentially related to a discussion in #520, where we are practically still trying to figure out which digest should be used for a potential Signer/Verifier implementation.


Also regarding your change where you added the assertion on the hash length and the size of parameter q, if I read the standard correctly, this restriction only applies to RSA (specifically RSA key pair generation).

The full quote is as follows:

An approved hash function, as specified in FIPS 180, shall be used during the generation of key
pairs and digital signatures. When used during the generation of an RSA key pair (as specified in
this Standard), the length in bits of the hash function output block shall meet or exceed the
security strength associated with the bit length of the modulus n (see SP 800-57).

The quote can be found in FIPS 186-4, under section 5.1 "RSA Key Pair Generation"

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

@cobratbq the current shape of the ecdsa crate is based on years of iteration and hard-won knowledge about how RFC6979 is deployed alongside ECDSA in many real-world scenarios. In fact, we have to accommodate cases where the digest used for instantiating HMAC-DRBG is different from the one used to hash the message.

We previously did what I suspect you're suggesting: always use the D parameter of DigestSigner to instantiate HMAC-DRBG. That felt great at first, but we ended up with many, many requests to match existing test vectors for systems which use two different hashes, namely Keccak256 for message hashing and SHA-256 for RFC6979 as commonly used in the Ethereum ecosystem.

Unless you have such specific requirements as well, you can instantiate RFC6979 with the secure cryptographic digest of your choice, which is why I recommended SHA-256 as a reasonable default. Again, it doesn't have to match what was used to hash the message, and really the only party who can discern what hash was used was the signer. The only time it really matters at all is if you are trying to match specific deterministic test vectors.

PrehashSigner doesn't have a D parameter by design: the DigestSigner trait provides that. If you really, really want to have a generic parameter for D when using PrehashSigner, it needs to be defined on the type, not the trait.

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@aumetra: okay, you are right. Let me rephrase. Given the way DSA is defined, there is a open variable for the digest to be used. (Instead of a single prescribed digest, a set of possible values is defined.) So, this is effectively requires extending the trait, i.e. introducing a new variable component. For ecdsa these can be hard-coded to the prescribed digest, just not for dsa. For practical convenience, maybe it needs to be structured differently so that the definition of PrehashSigner itself isn't changed. I'm questioning the trait definition, because:

  1. I'd like to avoid introducing a specific hash-implementation where only a abstract concept is needed. (Sha1 i.s.o. Digest, for example, as sha1 is only a dev-dependency now.)
  2. From my limited understanding, it seems that PrehashSigner closely relates to the secret_number_rfc6979, except that the one "variable" is exposed.
  3. I'd like to avoid contributing an incomplete workaround, if possible/reasonable.

@aumetra thanks for catching that, I see that in traversing the spec, I accidentally ended up with something RSA specific. That's my fuck-up.

@tarcieri actually, it's the inverse: with my current understanding, I would define a D type on PrehashSigner. In implementations of ecdsa it may end up "hard-coded", in other implementations it may correspond to D in DigestSigner, however in the basis it is a free variable. (And if I understand your examples, this should not be an issue given there is no constraint to DigestSigner.)
You mentioned: "... no D defined by design, because the DigestSigner provides that." Doesn't that mean that you are creating a dependency that is often not valid? (As per some mentioned examples.) DigestSigner may (in all cases?) depend on PrehashSigner, but the reverse is not true, IIUC.

edit@tarcieri comment regarding evolution of ecdsa should not be contradicted or negated by my comments. If you think so, LMK.

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

I would define a D type on PrehashSigner.

Again, PrehashSigner doesn't have a D parameter by design. PrehashSigner is explicitly designed to be able to handle different hash inputs at runtime. The ecdsa crate implements this logic:

See:

Even if we were to abandon that requirement, a D parameter doesn't make sense in all of the potential contexts where PrehashSigner would be used.

Doesn't that mean that you are creating a dependency that is often not valid? (As per some mentioned examples.) DigestSigner may (in all cases?)

Adding a D parameter to PrehashSigner would do just that: it's primarily useful for RFC6979, and unless you're using a digest to instantiate HMAC-DRBG or some similar KDF, it provides no value, and indeed there would be nothing for a user to pass, so what are they supposed to pass in that case?

And again, there's already a similar trait with a D parameter: DigestSigner.

That's why I suggested that if you really, really want to expose it as a user-configurable parameter, it needs to be on the type, not the trait.

If you want a concrete example, PrehashSigner could be used to support Ed25519ph. Internally Ed25519ph is explicitly mandated to use SHA-512 to perform RFC6979-like deterministic derivation of a once-per-key-message-pair epehemeral scalar. Use anything else but SHA-512, and the construction ceases to be "Ed25519ph".

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@tarcieri just to check, I propose a D to pass on to crate::generate::secret_number_rfc6979::<D>(&self, prehash). The value prehash is a slice of unknown length and that is fine, i.e. works exactly as expected.

edit if your suggestion is to hard-code SHA-256 for it and leave it at that, then that's fine by me. (IIUC that works for me)

@tarcieri

Copy link
Copy Markdown
Member

@cobratbq aah, that's fine

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@cobratbq aah, that's fine

I'm glad this is clarified. I had wanted to align the solution direction, but discussions got a bit more complicated. To get back to my original questions/concerns. So,

  • regarding the HMAC-DRBG, would it be better to: (1.) hardcode for DSA? Or (2.) define a generic D that is specific to the use-cases of variable or multiple digests (as discussed above)?
  • And are there preferences regarding: modifying PrehashSigner vs. introducing an (intermediary) trait such that the generic type can be defined there? (given interoperability, backwards-compatibility, etc.)

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

regarding the HMAC-DRBG, would it be better to: (1.) hardcode for DSA? Or (2.) define a generic D that is specific to the use-cases of variable or multiple digests (as discussed above)?

If #520 is resolved, you can use the same approach as ECDSA, leveraging dsa::Signature::Digest.

Alternatively, you could add a generic parameter to SigningKey for the digest to instantiate HMAC-DRBG with for RFC6979, with a reasonable default (e.g. Sha256).

And are there preferences regarding: modifying PrehashSigner vs. introducing an (intermediary) trait such that the generic type can be defined there? (given interoperability, backwards-compatibility, etc.)

If you really feel strongly about this, please open an issue on https://github.com/RustCrypto/traits/issues with your concrete proposal.

If you're suggesting adding an additional trait, please note there's something of a "trait explosion" to handle all possible cases: PrehashSigner has an accompanying RandomizedPrehashSigner that is parameterized by an RNG, DigestSigner also has a RandomizedDigestSigner, and all of these need accompanying traits in async-signature.

Note that there are other things we'd potentially like to add, like domain separation, which would further compound this trait explosion.

So I think the bar would be pretty high to add an additional trait, especially given DigestSigner and PrehashSigner already exist.

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

I get the impression you have some reservations. I understand, especially given another issue is involved.

My goal is to have prehash signing and verification exposed as part of the API. Given the complications as discussed above, what do you recommend?

@tarcieri

Copy link
Copy Markdown
Member

Use Sha256 for now and we can circle back on a more generalized solution in #520

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@aumetra@tarcieri I made the necessary changes. I think my changes have not impacted the other build failures. Can you let me know if there is anything left to do? Also, consider squashing the merge, because the history contains mostly useless confusing changes.

Comment threaddsa/Cargo.toml Outdated
@tarcieri
tarcieri merged commit ace98c8 into RustCrypto:masterOct 22, 2022
@tarcieritarcieri mentioned this pull request Oct 29, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@cobratbq@aumetra@tarcieri
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); dsa: expose signing and verifying of prehashed hash value by cobratbq · Pull Request #558 · RustCrypto/signatures · GitHub
Skip to content

dsa: expose signing and verifying of prehashed hash value - #558

Merged
tarcieri merged 11 commits into
RustCrypto:masterfrom
cobratbq:master
Oct 22, 2022
Merged

dsa: expose signing and verifying of prehashed hash value#558
tarcieri merged 11 commits into
RustCrypto:masterfrom
cobratbq:master

Conversation

@cobratbq

Copy link
Copy Markdown
Contributor

Expose the ability for the user to provide a prehashed hash-value for signing/verification. In rare cases, this is needed in other protocols. I have attempted to solve this with a custom Digest implementation, but eventually ran into (forced) finalization which affects the hash value.

@aumetra

Copy link
Copy Markdown
Contributor

Would you mind changing the code to expose the prehashed signing/verifying functionality via the hazmat traits of the signature crate (just for the sake of having a unified interface)?
You would need to adjust the version requirements of signature to a minimum version of 1.6 (1.6.4 specifically if you want to implement RandomizedPrehashSigner, too).

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

Sure, I'll have a look. Thanks for pointing out a direction.

@cobratbq

cobratbq commented Oct 20, 2022

Copy link
Copy Markdown
ContributorAuthor

@aumetra can you confirm that there is no component of randomness in this logic? The crate::generate::secret_number_rfc6979 does not seem to depend on randomness.

I will follow up later: I've got some things to figure out, e.g. whether the trait needs the D trait (Digest + OutputSize + ..), whether the randomness should be in and I'm currently using an undesirable implementation, etc.

@tarcieri

Copy link
Copy Markdown
Member

@aumetra for now you can just do the deterministic API.

RFC6979 supports additional randomness, but that can be added as a followup.

@aumetra

aumetra commented Oct 20, 2022

Copy link
Copy Markdown
Contributor

can you confirm that there is no component of randomness in this logic?

Yes, the logic of the function (as it currently is) is fully deterministic

@tarcieri

Copy link
Copy Markdown
Member

@cobratbq looks like one clippy nit for a needless borrow

@tarcieri

Copy link
Copy Markdown
Member

Yes, the logic of the function (as it currently is) is fully deterministic

@aumetra it actually also supports using an RNG:

impl<D>RandomizedDigestSigner<D,Signature>forSigningKey
where
D:Digest,
{
fntry_sign_digest_with_rng(
&self,
mutrng:implCryptoRng + RngCore,
digest:D,
) -> Result<Signature, signature::Error>{
let ks = crate::generate::secret_number(&mut rng,self.verifying_key().components())
.ok_or_else(signature::Error::new)?;
let hash = digest.finalize();
self.sign_prehashed(ks,&hash)
.ok_or_else(signature::Error::new)
}
}

@aumetra

aumetra commented Oct 20, 2022

Copy link
Copy Markdown
Contributor

Oh yeah, I was only talking about the function crate::generate::secret_number_rfc6979 since they asked explicitly about that one

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@tarcieri@aumetra quick follow-up, I'd appreciate your feedback

  1. I added an assertion to ensure hash length is at least the same strength as modulus. Before it just took the minimum of two numbers.
  2. verify_prehash is implemented, seems straight-forward enough. No issue there.
  3. sign_prehash, IIUC, cannot be implemented with this combination of trait PrehashSigner<Signature> and the requirement of a D: Digest + BlockSizeUser + FixedOutputReset for crate::generate::secret_number_rfc6979.

Do you have an idea what to do with (3)? The type for D has to be somewhere, and on the trait seems to make most sense. (Branch, currently, does not build but does illustrate the choke point.)

@tarcieri

Copy link
Copy Markdown
Member

@cobratbqDigestSigner is already the trait which has a D parameter.

The ECDSA crate uses a curve-specific digest when implementing PrehashSigner:

https://github.com/RustCrypto/signatures/blob/3a13e77/ecdsa/src/sign.rs#L268

Failing that you could just use e.g. SHA-256, or some other SHA2-family digest which has test vectors in RFC6979.

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@tarcieri I've been looking into the ecdsa implementation. From what I can tell, I suspect that the PrehashSigner trait is currently ill-defined: you rely on that trait to define deterministic signing behavior that needs to have a Digest defined, however it is not part of this trait.

It works for ecdsa because of luck, i.e. it is coincidentally already defined on the implementation. For dsa that won't work, because the spec allows for any number of approved digests, while PrehashSigner requests/requires an input parameter prehash with a run-time bound: bit-length at least as large as bit-length of digest. (Also, DigestSigner is not necessarily present if itself not required.)

Is this something you would want to change/fix or should I work around it, like effectively (probably by accident) happened with ecdsa?

@aumetra

aumetra commented Oct 21, 2022

Copy link
Copy Markdown
Contributor

The logic in ecdsa isn't really ill-defined. Incompatible (in specific cases) with implementations that allow free definition of the used digest function, sure, but not wrong.
The crux is that ECDSA has recommended hash digests for each curve. P256 -> SHA-256, P384 -> SHA-384, you get the picture.
To cite the FIPS 186-4:

It is recommended that the security strength associated with the bit length of n and the security
strength of the hash function be the same unless an agreement has been made between
participating entities to use a stronger hash function

The ecdsa crate uses the associated type of the recommended digest to generate the ad-hoc key with.

DSA however doesn't really have a "recommended" hash digest. Most of the standards reference SHA-1 which is actually pretty awful because, you know.
This is actually tangentially related to a discussion in #520, where we are practically still trying to figure out which digest should be used for a potential Signer/Verifier implementation.


Also regarding your change where you added the assertion on the hash length and the size of parameter q, if I read the standard correctly, this restriction only applies to RSA (specifically RSA key pair generation).

The full quote is as follows:

An approved hash function, as specified in FIPS 180, shall be used during the generation of key
pairs and digital signatures. When used during the generation of an RSA key pair (as specified in
this Standard), the length in bits of the hash function output block shall meet or exceed the
security strength associated with the bit length of the modulus n (see SP 800-57).

The quote can be found in FIPS 186-4, under section 5.1 "RSA Key Pair Generation"

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

@cobratbq the current shape of the ecdsa crate is based on years of iteration and hard-won knowledge about how RFC6979 is deployed alongside ECDSA in many real-world scenarios. In fact, we have to accommodate cases where the digest used for instantiating HMAC-DRBG is different from the one used to hash the message.

We previously did what I suspect you're suggesting: always use the D parameter of DigestSigner to instantiate HMAC-DRBG. That felt great at first, but we ended up with many, many requests to match existing test vectors for systems which use two different hashes, namely Keccak256 for message hashing and SHA-256 for RFC6979 as commonly used in the Ethereum ecosystem.

Unless you have such specific requirements as well, you can instantiate RFC6979 with the secure cryptographic digest of your choice, which is why I recommended SHA-256 as a reasonable default. Again, it doesn't have to match what was used to hash the message, and really the only party who can discern what hash was used was the signer. The only time it really matters at all is if you are trying to match specific deterministic test vectors.

PrehashSigner doesn't have a D parameter by design: the DigestSigner trait provides that. If you really, really want to have a generic parameter for D when using PrehashSigner, it needs to be defined on the type, not the trait.

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@aumetra: okay, you are right. Let me rephrase. Given the way DSA is defined, there is a open variable for the digest to be used. (Instead of a single prescribed digest, a set of possible values is defined.) So, this is effectively requires extending the trait, i.e. introducing a new variable component. For ecdsa these can be hard-coded to the prescribed digest, just not for dsa. For practical convenience, maybe it needs to be structured differently so that the definition of PrehashSigner itself isn't changed. I'm questioning the trait definition, because:

  1. I'd like to avoid introducing a specific hash-implementation where only a abstract concept is needed. (Sha1 i.s.o. Digest, for example, as sha1 is only a dev-dependency now.)
  2. From my limited understanding, it seems that PrehashSigner closely relates to the secret_number_rfc6979, except that the one "variable" is exposed.
  3. I'd like to avoid contributing an incomplete workaround, if possible/reasonable.

@aumetra thanks for catching that, I see that in traversing the spec, I accidentally ended up with something RSA specific. That's my fuck-up.

@tarcieri actually, it's the inverse: with my current understanding, I would define a D type on PrehashSigner. In implementations of ecdsa it may end up "hard-coded", in other implementations it may correspond to D in DigestSigner, however in the basis it is a free variable. (And if I understand your examples, this should not be an issue given there is no constraint to DigestSigner.)
You mentioned: "... no D defined by design, because the DigestSigner provides that." Doesn't that mean that you are creating a dependency that is often not valid? (As per some mentioned examples.) DigestSigner may (in all cases?) depend on PrehashSigner, but the reverse is not true, IIUC.

edit@tarcieri comment regarding evolution of ecdsa should not be contradicted or negated by my comments. If you think so, LMK.

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

I would define a D type on PrehashSigner.

Again, PrehashSigner doesn't have a D parameter by design. PrehashSigner is explicitly designed to be able to handle different hash inputs at runtime. The ecdsa crate implements this logic:

See:

Even if we were to abandon that requirement, a D parameter doesn't make sense in all of the potential contexts where PrehashSigner would be used.

Doesn't that mean that you are creating a dependency that is often not valid? (As per some mentioned examples.) DigestSigner may (in all cases?)

Adding a D parameter to PrehashSigner would do just that: it's primarily useful for RFC6979, and unless you're using a digest to instantiate HMAC-DRBG or some similar KDF, it provides no value, and indeed there would be nothing for a user to pass, so what are they supposed to pass in that case?

And again, there's already a similar trait with a D parameter: DigestSigner.

That's why I suggested that if you really, really want to expose it as a user-configurable parameter, it needs to be on the type, not the trait.

If you want a concrete example, PrehashSigner could be used to support Ed25519ph. Internally Ed25519ph is explicitly mandated to use SHA-512 to perform RFC6979-like deterministic derivation of a once-per-key-message-pair epehemeral scalar. Use anything else but SHA-512, and the construction ceases to be "Ed25519ph".

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@tarcieri just to check, I propose a D to pass on to crate::generate::secret_number_rfc6979::<D>(&self, prehash). The value prehash is a slice of unknown length and that is fine, i.e. works exactly as expected.

edit if your suggestion is to hard-code SHA-256 for it and leave it at that, then that's fine by me. (IIUC that works for me)

@tarcieri

Copy link
Copy Markdown
Member

@cobratbq aah, that's fine

@cobratbq

cobratbq commented Oct 21, 2022

Copy link
Copy Markdown
ContributorAuthor

@cobratbq aah, that's fine

I'm glad this is clarified. I had wanted to align the solution direction, but discussions got a bit more complicated. To get back to my original questions/concerns. So,

  • regarding the HMAC-DRBG, would it be better to: (1.) hardcode for DSA? Or (2.) define a generic D that is specific to the use-cases of variable or multiple digests (as discussed above)?
  • And are there preferences regarding: modifying PrehashSigner vs. introducing an (intermediary) trait such that the generic type can be defined there? (given interoperability, backwards-compatibility, etc.)

@tarcieri

tarcieri commented Oct 21, 2022

Copy link
Copy Markdown
Member

regarding the HMAC-DRBG, would it be better to: (1.) hardcode for DSA? Or (2.) define a generic D that is specific to the use-cases of variable or multiple digests (as discussed above)?

If #520 is resolved, you can use the same approach as ECDSA, leveraging dsa::Signature::Digest.

Alternatively, you could add a generic parameter to SigningKey for the digest to instantiate HMAC-DRBG with for RFC6979, with a reasonable default (e.g. Sha256).

And are there preferences regarding: modifying PrehashSigner vs. introducing an (intermediary) trait such that the generic type can be defined there? (given interoperability, backwards-compatibility, etc.)

If you really feel strongly about this, please open an issue on https://github.com/RustCrypto/traits/issues with your concrete proposal.

If you're suggesting adding an additional trait, please note there's something of a "trait explosion" to handle all possible cases: PrehashSigner has an accompanying RandomizedPrehashSigner that is parameterized by an RNG, DigestSigner also has a RandomizedDigestSigner, and all of these need accompanying traits in async-signature.

Note that there are other things we'd potentially like to add, like domain separation, which would further compound this trait explosion.

So I think the bar would be pretty high to add an additional trait, especially given DigestSigner and PrehashSigner already exist.

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

I get the impression you have some reservations. I understand, especially given another issue is involved.

My goal is to have prehash signing and verification exposed as part of the API. Given the complications as discussed above, what do you recommend?

@tarcieri

Copy link
Copy Markdown
Member

Use Sha256 for now and we can circle back on a more generalized solution in #520

@cobratbq

Copy link
Copy Markdown
ContributorAuthor

@aumetra@tarcieri I made the necessary changes. I think my changes have not impacted the other build failures. Can you let me know if there is anything left to do? Also, consider squashing the merge, because the history contains mostly useless confusing changes.

Comment threaddsa/Cargo.toml Outdated
@tarcieri
tarcieri merged commit ace98c8 into RustCrypto:masterOct 22, 2022
@tarcieritarcieri mentioned this pull request Oct 29, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@cobratbq@aumetra@tarcieri