3

I have identical code being compiled and run under Mono (Unity 4.5) and MS .NET:

DSAParameters privateKey;
...
DSACryptoServiceProvider dsa = new DSACryptoServiceProvider(csp);
dsa.PersistKeyInCsp = false;
dsa.ImportParameters(privateKey);

The private key is loaded from disk and the bytes being loaded, and the integers being set, in "privateKey" are identical in the Mono and MS .NET versions.

In the .NET version dsa.ImportParameters throws a "Bad Data" exception.

I have tried

  • different .NET settings (3.5 minimal, AnyCPU/x86/x64)
  • different keys (some throw, some don't but the .NET signatures are always (?) invalid when I validate them, whereas the ones generated with the same keys and for the same message in Mono work fine.)

Is there some fundamental underlying difference between the way MS DSA implementation and the one from Mono work, under the hood, which could cause this..?

Any light shed is most welcome...this is a bit of a head-scratcher...

Some more info:

  • The key is generated in Java and exported after being converted from DER format to work with .NET (P1363; just the big-integers one after the other)
  • The key is loaded from disc using BinaryReader and the integer values loaded as byte[] into the DSAParameters (P,Q,G and X)
  • The public key is loaded in a Java app which uses the Oracle implementation to verify the signature
  • All of the C# code is "symmetric", i.e. used as-is in both the Mono and the MS .NET apps.

Update 2

Alright, I am not going mad; this morning I tried again; same .NET app run on a Mac (using Mono Develop, plain vanilla) and one on Windows (Visual Studio 2010). Same private key bytes loaded, same code path (at least as far as C# is concerned) - same "bad data" exception on Windows only. Here's the key:

P: 17801190547854226652823756245015999014523215636912067427327445031444286578873
70207706126952521234630795671567847784664499706507709207278570500096683881440341
29745221171818506047231150039301079959358067395348717066319802262019714966524135
060945913707594956514672855690606794135837542707371727429551343320695239

Q: 864205495604807476120572616017955259175325408501

G: 17406820753240209518581198012352343653860449079456135097849583104059995348845
58231478515974089409507253077970949157594923683005742524387610370844734671801488
76118103083043754985190983472601550494691329488083395492313850000361646482644608
492304078721818959999056496097769368017749273708962006689187956744210730

X: 3505625379966178555918512548923624458026758122

...

SonarJetLens
  • 386
  • 1
  • 9
  • Some quick sanity checks I did with .NET and mono didn't find any problems. I do note that an invalid `Seed` property can result in the "Bad Data" error, but I'd expect that with mono too. Some more info may help: (1) How are you generating the private key (e.g. in one or other of .NET or mono's `DSACryptoServiceProvider`)? (2) Can you provide some more of the code (e.g. that for loading the `DSAParameters`, perhaps the problem is there). (3) You say the .NET signatures are always invalid, how are you verifying them? – softwariness Mar 13 '15 at 18:45
  • @softwariness I've updated my question with some more info. So, specifically; 1) key is not generated in .NET but in Java 2) loading code is exactly the same and when I did a byte-byte comparison of the resulting raw private key bytes they are *exactly* the same before I load them with the ImportParameters – SonarJetLens Mar 13 '15 at 19:23
  • What OS and architecture are the Java and mono code running on? I wonder if the problem is wrong endianness. – softwariness Mar 13 '15 at 20:08
  • Java code runs on Linux & Windows (both tested) - .NET code (Mono & MS) both run on PC (and Mac, for the Mono part) so, given that the Mono->Java path works regardless of platform it doesn't seem like an endianness issue. In particular since the raw bytes are identical prior to being loaded. I am starting to wonder if MS .NET has some sort of rules for what integers it lets you use for DSA which are hidden away somewhere...Does that make sense? – SonarJetLens Mar 13 '15 at 20:13
  • 2
    It was endianness in the Mono vs .NET implementation of `DSACryptoServiceProvider` I was thinking of actually, not in the loading of the parameters from the file (i.e. the bytes are identical going into `ImportParameters`, but interpreted differently by that method), but it probably isn't that. Re .NET restricting values, it's possible, or it could be a bug. Can you provide the hex or base64 of some example parameters which fail to be imported into .NET so I can attempt to reproduce it? – softwariness Mar 13 '15 at 20:42
  • @softwariness I'll do that when I'm back in the office on Monday, good plan – SonarJetLens Mar 14 '15 at 09:03

2 Answers2

1

.NET Framework expects some size relationships to be maintained:

  • Q: 20 bytes
  • G: same length as P
  • Y, if present: same length as P
  • seed, if present: 20 bytes
  • X, if present: 20 bytes

These are just pass-through requirements of the underlying cryptographic provider: https://msdn.microsoft.com/en-us/library/windows/desktop/aa381987(v=vs.85).aspx#PRIVK_BLOB.

Based on the length of your values above, you probably need to left-pad X with zeros.

bartonjs
  • 30,352
  • 2
  • 71
  • 111
  • It's been a while since this issue and the project since moved in a very different direction but your suggestion seems reasonable. If I manage to dig up the project again I'll try this out to confirm. – SonarJetLens Aug 07 '16 at 07:37
0

the short reply is that hash codes may differ between .NET Framework versions and platforms, such as 32-bit and 64-bit platforms; or between Microsoft's and Mono.

This can be read here: Object.GetHashCode , read the Remarks.

I have experienced this situation before when developing cross platform multiplayer games in Unity3D. So you will need to use your own hash function, or use a symmetric algorithm to cypher your data.

From documentation I read that DSACryptoServiceProvider is asymmetric in nature and uses hashes to create digital signatures. Here is the link: DSACryptoServiceProvider Class , read the Remarks.

Baltico
  • 483
  • 3
  • 9
  • But the hashing algorithm used will be SHA1, which doesn't depend on the object hash codes (which aren't used anyway, since this is the hash of a sequence of bytes, not an object)...right? – SonarJetLens Mar 30 '15 at 09:49