sign(data, SHA256withRSA) != sign(hash, NONEwithRSA)

Quite funny that after a few months offline I find the fun in writing a small article about an investigation which is still about security.

My task was to find how I could replicate the signature performed with an old applet and a proprietary library which used a hardware token in javascript using a new api provided by a different party.

The old applet just signed a hash and I had to find the way to implement this with the new api.

signOld(hash) = signJS(hash, mechanism) where mechanism is a CKM_* (CKM_SHA256_RSA_PKCS, CKM_RSA_PKCS, etc) in PKCS terms.

I am skipping the part where I had to confirm that the same keys are used.

The ebics applet does sign(hash) where hash is a precomputed SHA-256 hash of the data. This led me first to try the CKM_SHA256_RSA_PKCS and CKM_RSA_PKCS mechanisms to replicate the signature in the new api however:

signOld(hash) != signjs(hash, mechanism)

After some investigation (applet decompilation) I confirmed that:

sign(hash) = Signature(hash, "SHA256withRSA")

but still:

signOld(hash) != signjs(hash, CKM_SHA256_RSA_PKCS)

The applet used a custom JCE to connect to the hardware token and after more digging I realized
the Digest impl was replaced with a custom digest which did if fact only an Identity. So:

signOld(hash) = Signature(hash, "SHA256withRSA") where SHA256(data) = data*. 

Not even now:

signOld(hash) != signjs(hash, CKM_RSA_PKCS)

this is due to the fact (EUREKA) that in fact:

Signature(data, SHA256withRSA) = RSA(OID(SHA256) || SHA256(data)) 

(see the RFC https://tools.ietf.org/html/rfc3447#section-9.2) which in our case was:

signOld(hash) = RSA(OID(SHA256) || Identity(data)).

This solved the issue as:

signOld(hash) = signjs(OID(SHA256) || hash, CKM_RSA_PKCS)

and OID(SHA256) = “3031300d060960864801650304020105000420”

So instead of signing the first line I should have signed the second in order to replicate the old behaviour

fb059955cdc2d5d8f2a3ee78664966576696b7173f14df67031ebe9b63074c84
3031300d060960864801650304020105000420fb059955cdc2d5d8f2a3ee78664966576696b7173f14df67031ebe9b63074c84

So the interesting result is that:

sign(data, SHA256withRSA) != sign(SHA256(data), NONEwithRSA)

Leave a Reply

*