Discussion:
Bug: Broken signature
Marco หงุ่ยตระกูล-Schulze
2014-09-16 17:33:46 UTC
Permalink
Hello *,

I've encountered a very strange problem concerning a broken signature
which I could - after hours - finally isolate and reproduce in this
little test case:

https://codewizards.co/mn/tmp/2014-09-16/co.codewizards.bc20140916.tar.gz

The problem is demonstrated in short (simplified) as follows:

final Signer signer = ...
final byte[] signatureCreated = longToBytes(new Date(0).getTime());
signer.update(signatureCreated, 0, signatureCreated.length);
signer.update(plain, 0, plain.length);
final byte[] signature = signer.generateSignature();

final Signer verifier = ...
verifier.update(signatureCreated, 0, signatureCreated.length);
verifier.update(plain, 0, plain.length);
assertThat(verifier.verifySignature(signature)).isTrue();

The last assertion fails sometimes, which IMHO should *never* happen. If
I change any parameter, e.g. use a different 'signatureCreated' Date or
have a different payload to be signed or have different keys, the same
code works fine and the assertion succeeds.

Did I overlook anything? Or is this a bug in BouncyCastle 1.50 - maybe
even a known one?

Best regards, Marco :-)
Eckenfels. Bernd
2014-09-16 17:46:26 UTC
Permalink
Looking at your code, the difference is, the first 8 bytes are 00 00 00 00 00 00 00 00 if it does not work and 00 .. 00 01 otherwise. Could be a padding problem, hm. Not familiar with the generic signer, which padding is used?

Gruss
Bernd

-----Ursprüngliche Nachricht-----
Von: Marco หงุ่ยตระกูล-Schulze [mailto:***@nightlabs.de]
Gesendet: Dienstag, 16. September 2014 19:34
An: dev-***@bouncycastle.org
Betreff: [dev-crypto] Bug: Broken signature

Hello *,

I've encountered a very strange problem concerning a broken signature which I could - after hours - finally isolate and reproduce in this little test case:

https://codewizards.co/mn/tmp/2014-09-16/co.codewizards.bc20140916.tar.gz

The problem is demonstrated in short (simplified) as follows:

final Signer signer = ...
final byte[] signatureCreated = longToBytes(new Date(0).getTime());
signer.update(signatureCreated, 0, signatureCreated.length);
signer.update(plain, 0, plain.length);
final byte[] signature = signer.generateSignature();

final Signer verifier = ...
verifier.update(signatureCreated, 0, signatureCreated.length);
verifier.update(plain, 0, plain.length);
assertThat(verifier.verifySignature(signature)).isTrue();

The last assertion fails sometimes, which IMHO should *never* happen. If I change any parameter, e.g. use a different 'signatureCreated' Date or have a different payload to be signed or have different keys, the same code works fine and the assertion succeeds.

Did I overlook anything? Or is this a bug in BouncyCastle 1.50 - maybe even a known one?

Best regards, Marco :-)










SEEBURGER AG Vorstand/Seeburger Executive Board:
Sitz der Gesellschaft/Registered Office: Bernd Seeburger, Axel Haas, Michael Kleeberg
Edisonstr. 1
D-75015 Bretten Vorsitzender des Aufsichtsrats/Chairperson of the Seeburger Supervisory Board:
Tel.: 07252 / 96 - 0 Dr. Franz Scherer
Fax: 07252 / 96 - 2222
Internet: http://www.seeburger.de Registergericht/Commercial Register:
e-mail: ***@seeburger.de HRB 240708 Mannheim


Dieses E-Mail ist nur für den Empfänger bestimmt, an den es gerichtet ist und kann vertrauliches bzw. unter das Berufsgeheimnis fallendes Material enthalten. Jegliche darin enthaltene Ansicht oder Meinungsäußerung ist die des Autors und stellt nicht notwendigerweise die Ansicht oder Meinung der SEEBURGER AG dar. Sind Sie nicht der Empfänger, so haben Sie diese E-Mail irrtümlich erhalten und jegliche Verwendung, Veröffentlichung, Weiterleitung, Abschrift oder jeglicher Druck dieser E-Mail ist strengstens untersagt. Weder die SEEBURGER AG noch der Absender (Eckenfels. Bernd) übernehmen die Haftung für Viren; es obliegt Ihrer Verantwortung, die E-Mail und deren Anhänge auf Viren zu prüfen.


This email is intended only for the recipient(s) to whom it is addressed. This email may contain confidential material that may be protected by professional secrecy. Any fact or opinion contained, or expression of the material herein, does not necessarily reflect that of SEEBURGER AG. If you are not the addressee or if you have received this email in error, any use, publication or distribution including forwarding, copying or printing is strictly prohibited. Neither SEEBURGER AG, nor the sender (Eckenfels. Bernd) accept liability for viruses; it is your responsibility to check thi
Eckenfels. Bernd
2014-09-16 18:15:37 UTC
Permalink
Hello,

Pardon me, I guess no padding is used on the content side.

But what I noticed the failing test vector (9 bytes, last byte value 'H') produces a SHA1 checksum where the first byte is 0.

Anyway, this here is a smaller reproducer. When the 8. Byte is 42 or 0 the problem does not exist. If the byte is 'H' then the problem exists if byte 0-7 are zero:

public void signAndVerifySuccess() throws Exception {
final AsymmetricKeyParameter privateKey = PrivateKeyFactory.createKey(readResource("sign_verify_1_key.private"));
final AsymmetricKeyParameter publicKey = PublicKeyFactory.createKey(readResource("sign_verify_1_key.public"));

final Signer signer = new GenericSigner(new RSAEngine(), new SHA1Digest());
signer.init(true, privateKey);
final byte[] test = new byte[9]; test[8] = 'H'; // only fails with H
test[7] = 1; // THIS also makes a difference, 0 = failure
signer.update(test, 0, 9);
final byte[] signature = signer.generateSignature();

final Signer verifier = new GenericSigner(new RSAEngine(), new SHA1Digest());
verifier.init(false, publicKey);
verifier.update(test, 0, 9);
assertThat(verifier.verifySignature(signature)).isTrue();
}








SEEBURGER AG Vorstand/Seeburger Executive Board:
Sitz der Gesellschaft/Registered Office: Bernd Seeburger, Axel Haas, Michael Kleeberg
Edisonstr. 1
D-75015 Bretten Vorsitzender des Aufsichtsrats/Chairperson of the Seeburger Supervisory Board:
Tel.: 07252 / 96 - 0 Dr. Franz Scherer
Fax: 07252 / 96 - 2222
Internet: http://www.seeburger.de Registergericht/Commercial Register:
e-mail: ***@seeburger.de HRB 240708 Mannheim


Dieses E-Mail ist nur für den Empfänger bestimmt, an den es gerichtet ist und kann vertrauliches bzw. unter das Berufsgeheimnis fallendes Material enthalten. Jegliche darin enthaltene Ansicht oder Meinungsäußerung ist die des Autors und stellt nicht notwendigerweise die Ansicht oder Meinung der SEEBURGER AG dar. Sind Sie nicht der Empfänger, so haben Sie diese E-Mail irrtümlich erhalten und jegliche Verwendung, Veröffentlichung, Weiterleitung, Abschrift oder jeglicher Druck dieser E-Mail ist strengstens untersagt. Weder die SEEBURGER AG noch der Absender (Eckenfels. Bernd) übernehmen die Haftung für Viren; es obliegt Ihrer Verantwortung, die E-Mail und deren Anhänge auf Viren zu prüfen.


This email is intended only for the recipient(s) to whom it is addressed. This email may contain confidential material that may be protected by professional secrecy. Any fact or opinion contained, or expression of the material herein, does not necessarily reflect that of SEEBURGER AG. If you are not the addressee or if you have received this email in error, any use, publication or distribution including forwarding, copying or printing is strictly prohibited. Neither SEEBURGER AG, nor the sender (Eckenfels. Bernd) accept liability for viruses; it is your responsibility to check this email and its attachments for viruses.
Marco หงุ่ยตระกูล-Schulze
2014-09-17 03:35:30 UTC
Permalink
Hi Bernd,

thanks a lot for your analysis and further simplification! Yesterday, I
had no energy for such further debugging, anymore, after I first
searched for many hours in my own, quite complex code (working with
streams, combining encrypting and signing etc.). I was too sure that
BouncyCastle would not have a bug in such a fundamental and likely
well-tested code ;-)

Anyway, I just analysed this a bit further and found out that the key is
not relevant (I was wrong, yesterday):

public void signAndVerifyRSAManyRoundsGeneratedKey() throws Exception {
AsymmetricCipherKeyPairGenerator keyPairGenerator =
new RSAKeyPairGenerator();
keyPairGenerator.init(new RSAKeyGenerationParameters(
BigInteger.valueOf(0x10001), random, 4096, 12));
AsymmetricCipherKeyPair keyPair = keyPairGenerator.generateKeyPair();
Signer signer = new GenericSigner(new RSAEngine(), new SHA1Digest());
Signer verifier = new GenericSigner(
new RSAEngine(), new SHA1Digest());
signer.init(true, keyPair.getPrivate());
verifier.init(false, keyPair.getPublic());

for (long m = 0; m < 10000; ++m) {
System.out.println(m);
byte[] plain = longToBytes(m);

signer.reset();
signer.update(plain, 0, plain.length);
byte[] signature = signer.generateSignature();

verifier.reset();
verifier.update(plain, 0, plain.length);
assertThat(verifier.verifySignature(signature)).isTrue();
}
}

This test method generates a new key pair in every run. Still, it
reliably fails when m reaches 115. The byte array to be signed is then:
[0, 0, 0, 0, 0, 0, 0, 115]

I repeated this with different ranges and found that it fails as well
with m being:

10000000000046
100000000001135
1000000000000837

All of them have one thing in common: The first byte of their SHA1 hash
is 0 - as you already stated.

I wanted to try other CipherEngines to see whether this affects only the
RSAEngine, but I ran into another problem (maybe another bug) with the
ElGamalEngine. I'll write a separate e-mail about this, later.

Best regards, Marco :-)
Post by Eckenfels. Bernd
Hello,
Pardon me, I guess no padding is used on the content side.
But what I noticed the failing test vector (9 bytes, last byte value 'H') produces a SHA1 checksum where the first byte is 0.
public void signAndVerifySuccess() throws Exception {
final AsymmetricKeyParameter privateKey = PrivateKeyFactory.createKey(readResource("sign_verify_1_key.private"));
final AsymmetricKeyParameter publicKey = PublicKeyFactory.createKey(readResource("sign_verify_1_key.public"));
final Signer signer = new GenericSigner(new RSAEngine(), new SHA1Digest());
signer.init(true, privateKey);
final byte[] test = new byte[9]; test[8] = 'H'; // only fails with H
test[7] = 1; // THIS also makes a difference, 0 = failure
signer.update(test, 0, 9);
final byte[] signature = signer.generateSignature();
final Signer verifier = new GenericSigner(new RSAEngine(), new SHA1Digest());
verifier.init(false, publicKey);
verifier.update(test, 0, 9);
assertThat(verifier.verifySignature(signature)).isTrue();
}
[cut unnecessary footer]

Continue reading on narkive:
Loading...