Data signed in C#, verifies in C# but not in Java [duplicate] - java

I'm attempting to generate an RSA SHA512 signature on my server application (written in C#) and verify that signature on a client application (written in Java). Prior to the signature exchange, the server generates a public- and private-key pair and shares the public key with the client app by giving it the modulus and exponent values that were generated. The client app stores the values for later, when the signature needs to be verified.
The server is generating a 128-byte modulus and a 128-byte signature. I'm just using a byte array of [0x00, 0x01, 0x02, 0x03] as my source data for the signature while I'm testing.
For some reason, my Java application always fails when validating the signature.
My C# code (creates the signature):
RSACryptoServiceProvider crypto = new RSACryptoServiceProvider();
crypto.FromXmlString(this.myStoredPrivateKeyXml);
List<byte> signatureBytes = new List<byte>();
signatureBytes.Add(0x00);
signatureBytes.Add(0x01);
signatureBytes.Add(0x02);
signatureBytes.Add(0x03);
byte[] signature = crypto.SignData(signatureBytes.ToArray(), "SHA512");
My Java code (that receives/validates the signature):
byte[] expectedData = new byte[4];
expectedData[0] = 0;
expectedData[1] = 1;
expectedData[2] = 2;
expectedData[3] = 3;
byte[] exponent = getStoredExponent(); // three-byte value from server
byte[] modulus = getStoredModulus(); // 128-byte value from server
RSAPublicKeySpec spec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(exponent));
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(spec);
Signature verifier = Signature.getInstance("SHA512withRSA");
verifier.initVerify(publicKey);
verifier.update(expectedData);
if(verifier.verify(signature)) {
System.out.println("signature verified");
} else {
System.out.println("signature failed verification"); // always ends up here :(
}
The crypto objects on the C# side are from the System.Security.Cryptography namespace and java.security on the Java side.
The bytes are passed between the apps via web services and base64 strings. I've gone through and printed the values of the byte arrays themselves to make sure the values are correct for the exponent, modulus, and signature between the two applications. Is there any reason why the signature wouldn't be compatible between the two languages/apps? Is there a parameter that I'm missing, maybe? I made them both RSA with SHA512, but perhaps other aspects of the signature need to be accounted for as well?

In the XML representation on the C# side, the data are stored as unsigned big endian (and Base64 encoded).
However, the Java BigInteger(byte[] val)-constructor expects the data as signed (two's complement) big endian.
Therefore the BigInteger(int signum, byte[] magnitude)-constructor must be used, which expects the sign in the first parameter and the data in the second parameter as unsigned big endian, i.e. the following change is necessary:
RSAPublicKeySpec spec = new RSAPublicKeySpec(new BigInteger(1, modulus), new BigInteger(1, exponent));

Related

Bouncy Castle Curve25519 private key from Scrypt output

I am trying to achieve ECIES encryption, for which below code is working.
X9ECParameters ecP = CustomNamedCurves.getByName("curve25519");
ECParameterSpec ecSpec = EC5Util.convertToSpec(ecP);
BigInteger d = new BigInteger("145642755521911534651321230007534120304391871461646461466464667494947990");
ECPrivateKeySpec priKeySpec = new ECPrivateKeySpec(
d, // d
ecSpec);
ECPoint Q = new FixedPointCombMultiplier().multiply(params.getG(), d.multiply(BigInteger.valueOf(-1)));
Q = Q.normalize();
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(
new ECPoint(Q.getAffineXCoord().toBigInteger(), Q.getAffineYCoord().toBigInteger()), // Q
ecSpec);
KeyFactory factTrial = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
BCECPrivateKey sKey = (BCECPrivateKey) factTrial.generatePrivate(priKeySpec);
PublicKey vKey = factTrial.generatePublic(pubKeySpec);
Cipher c = Cipher.getInstance("ECIESwithAES-CBC",BouncyCastleProvider.PROVIDER_NAME);
byte[] encodeBytes = c.doFinal(data.getBytes());
String encrypt = Base64.getEncoder().encodeToString(encodeBytes);
Cipher c2 = Cipher.getInstance("ECIESwithAES-CBC",BouncyCastleProvider.PROVIDER_NAME);
c2.init(Cipher.DECRYPT_MODE,sKey, c.getParameters());
byte[] decodeBytes = c2.doFinal(encodeBytes);
String deCrypt = new String(decodeBytes,"UTF-8");
Issue is the private key element 'd'. If I try to replace it with output of scrypt hash, private key fails to be converted in PrivateKey instance.
I have gone through net resources https://github.com/bcgit/bc-java/issues/251, https://crypto.stackexchange.com/questions/51703/how-to-convert-from-curve25519-33-byte-to-32-byte-representation, https://crypto.stackexchange.com/questions/72134/raw-curve25519-public-key-points.
Above resources suggest the way Bouncy Castle for Curve25519 interprets private key is different from the ways some internet resource suggest. In post https://crypto.stackexchange.com/questions/51703/how-to-convert-from-curve25519-33-byte-to-32-byte-representation there is mention as follows.
According to the curve25519 paper a x25519 public key can be represented in 32 bytes.
The x25519 library I'm using (bouncycastle) however gives me a 33 byte representation according to this standard.
I am very new to ECC, these resources are confusing me, the difference between lengths, the style of encoding big vs. little.
I have tried libSodium 'crypto_box_easy' and 'crypto_box_open_easy'
via its Java binding and it works all fine. The 32 byte scrypt output
is used by 'crypto_box_seed_keypair' to generate key pair which is
used for encryption process.
As I see some maths is involved here which I lack at present or I am failing to see the conversion.
I have to go this route Scrypt output -> key pair -> use for encryption
Using directly KeyGenerator from BC is working, but that utilises SecureRandom, but I need the output of Scrypt to behave as private key.
Questions:
I'll really appreciate someone helps me understand the difference between libSodium and Bouncy Castle approach. libSodium mentions it uses X25519. When I try to create X25519 key from 32 bytes, but BC Cipher(ECIESwithAES-CBC) then complaints it is not a EC Point, from this resource 'https://github.com/bcgit/bc-java/issues/251' it seems there are differences in that too (Curve25519 vs X25519).
The private key 'd', how to interpret it. I have seen these random values in Bouncy Castle documentation and test cases, is this simply a number in the prescribed range for valid keys? This number is treated (little vs. big endian) before creating BigInteger instance. I mean the raw value of 'd' in the my code example was converted from some other number?
The struggle between understanding different mechanism of Curve25519 and BC API itself, I am really confused.
Some pointers to further my research would be of great help.

OCB mode in Bouncy Castle Lightweight API

I've got a Java application that does AES-256-OCB. For this, the BouncyCastle crypto library is used. As-is, it uses the standard JCA interface, but this requires a special policy file to be installed to permit key sizes greater than 128 bits.
This is unsuitable in our environment, and it seems to me that we may be able to dodge this by using BouncyCastle's own lightweight API. I'm a bit confused by this API, however, and I was curious how I actually go about instantiating a cipher as AES/OCB/NoPadding.
I'm normally pretty good about reading documentation, but BouncyCastle's rather extensive options have me a bit confused.
How can I instantiate a BlockCipher object for 256-bit OCB mode with no padding, using the BouncyCastle lightweight API, and use this to encrypt and decrypt data? I've already got the key, IV and data as byte[]s.
Here's what I came up with reading through BouncyCastle's test code. It appears to function, although I've not compared the results with any test vectors.
Call with encrypt=true for encryption, encrypt=false for decryption. Set tagLen to the desired length of the AEAD tag in bits (eg. tagLen=128). Optionally set ad to associated data for validation, or leave null to skip. Returns a properly-sized byte array of resulting ciphertext or plaintext.
protected static byte[] processCipher(boolean encrypt, int tagLen, byte[] keyBytes, byte[] iv, byte[] in, byte[] ad) throws IllegalStateException, InvalidCipherTextException {
KeyParameter key = new KeyParameter(keyBytes);
AEADParameters params = new AEADParameters(key, tagLen, iv);
AEADBlockCipher cipher = new OCBBlockCipher(new AESEngine(), new AESEngine());
cipher.init(encrypt, params);
byte[] out = new byte[cipher.getOutputSize(in.length)];
if(ad != null) cipher.processAADBytes(ad, 0, ad.length);
int offset = cipher.processBytes(in, 0, in.length, out, 0);
offset += cipher.doFinal(out, offset);
return out;
}

Authenticating signature created on c# by public key on java

I am new to verification and certificates etc ..
I am facing an issue , that I need to sign a message on c# then verify the signature on java , the issue I ma facing that I am unable to load the public key on java on a (PublicKey) object using the Base64 string generated on c# , I used the following code to generate the private and public key on c# side
CspParameters cspParams = new CspParameters { ProviderType = 1 };
cspParams.KeyContainerName = "MyKeyContainer";
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(1024);
string publicKey = Convert.ToBase64String(rsaProvider.ExportCspBlob(false));
string privateKey = Convert.ToBase64String(rsaProvider.ExportCspBlob(true));
System.Diagnostics.Debug.WriteLine("pub:" + publicKey);
System.Diagnostics.Debug.WriteLine("pri:" + privateKey);
Console.WriteLine("Key added to container: \n {0}", rsaProvider.ToXmlString(true));
then I used the following code to create a public key on Java side :
X509EncodedKeySpec specc = new X509EncodedKeySpec(org.apache.commons.codec.binary.Base64.decodeBase64("BgIAAACkAABSU0ExAAQAAA......"));
KeyFactory xx = KeyFactory .getInstance("RSA");
PublicKey ssx= xx.generatePublic(specc);
note that I copied the base64 public key string from the c# console .
When I try to run the code on java side the I get the following exception :
java.security.spec.InvalidKeySpecException: Inappropriate key specification: invalid key format
at sun.security.provider.DSAKeyFactory.engineGeneratePublic(Unknown Source)
at java.security.KeyFactory.generatePublic(Unknown Source)
I need to find a way to generate private and public key on c# (and generate a .cer file for the public key) to load it on java side , or find a way to load the base64 public key string into a (Publickey) object on java side . please help !
Option 1: Same data, different format.
The easiest way to transmit a public RSA key from .NET is to check that the public exponent value is { 01 00 01 } and then send the modulus value. On the receiver side you accept the modulus and assert the public exponent.
RSAParameters keyParams = rsa.ExportParameters(false);
if (!keyParams.Exponent.SequenceEqual(new byte[] { 0x01, 0x00, 0x01 }))
throw new InvalidOperationException();
Send(keyParams.Modulus);
Then Creating RSA keys from known parameters in Java says you can straightforwardly recover it on the Java side.
Option 2: Same format, different parser.
The next option you have is to keep using the CSP blob, but writing a parser in Java. The data is the result of calling CryptExportKey with PUBLICKEYBLOB, making your data layout as described at https://msdn.microsoft.com/en-us/library/ee442238.aspx and https://msdn.microsoft.com/en-us/library/windows/desktop/aa375601(v=vs.85).aspx#pub_BLOB.
In summary:
A header (which you could decide to skip, or just test it for equal to the fixed value(s) that you expect):
A byte, value 0x06 (PUBLICKEYBLOB)
A byte, value 0x02 (blob v2)
A short, value 0x0000 (reserved)
An integer (stored as little-endian) identifying the key as RSA (0x0000A400 or 0x00002400)
An integer (stored as little-endian) identifying the next segment as an RSA public key (a bit redundant, but technically a different structure now): 0x31415352
After all that comes the relevant data:
The bit-length of the modulus stored as a little-endian unsigned integer. For your 1024-bit example this will be 1024, aka 0x00000400, aka { 00 04 00 00 }(LE).
The public exponent, stored as a little-endian unsigned integer. This is almost always 0x00010001 (aka { 01 00 01 00 }), but since it's there you should respect it.
The next bitLen/8 bytes represent the modulus value. Since this is the public key that should be "the rest of the bytes in this array".
Option 3: Build a certificate
.NET Framework doesn't have this capability built-in (as of the current version, 4.7). You can P/Invoke to CertCreateSelfSignCertificate, but that would involve quite a lot of change (since RSACryptoServiceProvider won't let you get at the key handle, so you'll have to P/Invoke all of that, too).
You could "bit bang" out the DER-encoded certificate yourself. While fun, this is hard to get right, and probably not a viable path.
If you can move to .NET Core, the ability to create certificates has been added to .NET Core 2.0 via the CertificateRequest class. For a very simple certificate from your key:
var certReq = new CertificateRequest(
"CN=SubjectCN",
rsaProvider,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
// add any extensions you want. I'm not adding any because I said "simple".
DateTimeOffset now = DateTimeOffset.UtcNow;
X509Certificate2 cert = certReq.CreateSelfSigned(now, now.AddMinutes(90));
byte[] xfer = cert.RawData;

RSA encryption in Java, decrypt in PHP

Assume I have the following Java code to generate a Public-private keypair:
KeyPairGenerator generator = KeyPairGenerator.getInstance ("RSA");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
generator.initialize (1024, random);
KeyPair pair = generator.generateKeyPair();
RSAPrivateKey priv = (RSAPrivateKey)pair.getPrivate();
RSAPublicKey pub = (RSAPublicKey)pair.getPublic();
// Sign a message
Signature dsa = Signature.getInstance("SHA1withRSA");
dsa.initSign (priv);
dsa.update ("Hello, World".getBytes(), 0, "Hello, World".length());
byte[] out = dsa.sign();
/* save the signature in a file */
FileOutputStream sigfos = new FileOutputStream("sig");
sigfos.write(out);
sigfos.close();
How would one go about and decrypt the file "sig" in PHP? I've read the post: https://stackoverflow.com/a/1662887/414414 which supplies a function to convert a DER file to PEM (Assume I also save the public key from Java).
I have tried something like:
$key = openssl_pkey_get_public ("file://pub_key.pem");
$data = null;
openssl_public_decrypt ( file_get_contents ("sig"), $data, $key);
echo $data, "\n";
It successfully decrypts the message, but it is many weird characters.
Our scenario is a Java client that is sending messages to a PHP server, but encrypts the data with a private key. PHP knows about the public key, which it should use to decrypt and validate the message.
I've read a lot of posts regarding this issue here on SO, but I've come to realize that this is a bit specific issue, especially if there's different algorithms in use, etc. So sorry if this may be a duplicate.
Any feedbacks are greatly appreciated!
an "RSA signature" is usually more than just "encrypt with private key, decrypt with public key", since Public key protocols like PKCS#1 also specify padding schemes, and all signature schemes will encrypt a digest of the message, instead of the full message. I cannot find any documentation if java's signature scheme uses the signature padding scheme specified in PKCS#1, but my suspicion is that it is.
If it is, you will instead want to use the openssl_verify method in PHP, documented here. This will return a 0 or 1 if the signature is invalid or valid, respectively.
In the event that Java does not use a padding scheme, your issue is that the data encrypted in the signature is a hash of the message, instead of the message itself (you can see in the Java code that it uses the SHA-1 hash algorithm). So on the PHP side, you will need to take the sha1 hash of your message using the sha1 method with $raw_output set to true, and compare those strings to ensure your message is valid.
From the snippet
$key = openssl_pkey_get_public ("file://pub_key.pem");
It looks like you're referencing the public key, which would be the wrong one to decrypt. Double check ?

How to verify Hash of a plainText instead of plainText itself

I'm trying to verify a SHA1 With RSA signature in Java. My code is actually this :
String plainText = "myString";
String signature_b64_encoded = "bOBrJmPGJ6eqDpk7sZMJZ0QujErTVz/boe44ANTLbeHd1Pm56UBH3uUCdH4sDZcDczIW5E6Q0o2dq/4lpjTAOzu3FjaLMAZ1sSTn8Ds8XxQnOnmhapbrd/6sNSr9fC57c7BlgAy7GZbCZGUQC/b/UETb8cIR0B1Dqk41RDorm+NdwDEVCTl4q9I7D70/Kau/aGpUvJ3ULgzM3/JnxHpaWmG7xAsXH9MyxVSxi/ZpJ0WuyuaAiSmiDN946O/ElIxRqnaEtFuSIDinUYz4yAWtoLdssDO5SqMFb8EskUdfExs1IcGtYRaIbz3ASTFdVQoou7HI4pxVn1Sh4Y4mrxa7Ag==";
Signature instance = Signature.getInstance("SHA1WithRSA");
PublicKey pkey = cert.getPublicKey(); // gets my Public Key
instance.initVerify(pkey);
instance.update(plainText.getBytes());
Boolean blnResult = instance.verify(Base64.decode(strSignature.getBytes("UTF-8"), Base64.DEFAULT));
The problem is that the signature verified is false only in some cases and in other cases it is true. I don't manage to understand the cause of the randomness of this result.
The string i'm trying to verify is signed in .NET environment
The verification of the signature done in .NET is made on the hash of the plainText and not on the plainText.
Question 1
Is there a means to verify the hash of the plainText instead of plainText itself in Java (as it is possible in .NET) ? This would allow to be sure to have the same input at the beginning of the verification process.
Question 2
Is there another way to verify a SHA1 with RSA signature in Java ?

Categories

Resources