I have a need to create a HMACSHA1 in C# to work with a Java web service. I believe that the Java implementation may be flawed because I've tested my result extensively using some online services. I don't know Java very well and I need to make my C# code produce the same has as the Java code, even if it is not technically correct.
Java Version:
public static byte[] computeSignature(String algorithm, byte[] data, byte[] sharedSecret) {
try {
SecretKey secretKey = new SecretKeySpec(Base64.decode(sharedSecret), algorithm);
Mac mac = Mac.getInstance(algorithm);
mac.init(secretKey);
mac.update(data);
return Base64.encode(mac.doFinal());
} catch (NoSuchAlgorithmException e) {
throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR);
} catch (InvalidKeyException e) {
throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
C# Version:
private string GetHash(string message, string key)
{
message = message.Replace("\r", "");
Encoding encoding = new UTF8Encoding();
var keyByte = encoding.GetBytes(key);
var hmacsha1 = new HMACSHA1(keyByte);
hmacsha1.Initialize();
byte[] messageBytes = encoding.GetBytes(message);
var hashmessage = hmacsha1.ComputeHash(messageBytes);
var sigB64 = Convert.ToBase64String(hashmessage);
return sigB64;
}
Using the following message and secret key:
Message: The quick brown fox jumps over the lazy dog
Secret: key
I get the following results:
C#: 3nybhbi3iqa8ino29wqQcBydtNk=
Java: K6f3B61fGHxBLeMQZYPDER1mjeg=
I'm fairly certain the Base64 encoding is where the difference is happening, but I'm not sure.
So, my question is -- How can I make my C# code produce the same output as the Java example?
P.S. The java code has the following import:
com.sun.jersey.core.util.Base64;
Edit:
Here is how the java function gets called:
String signature = new String(HmacUtils.computeSignature("HmacSHA1", canonicalRepresentation.getBytes(), sharedSecret.getBytes()));
String authorization = "HMAC " + publicKey + " " + signature;
clientRequest.getHeaders().put(HttpHeaders.AUTHORIZATION, Lists.<Object>newArrayList(authorization));
Related
I have to call a government API, session key needs to be encrypted using public key provided by them. Following code to encrypt the session key is working fine on windows server using .NET framework, but I need to host the API on aws lambda using .NET core. There is gives following error
System.InvalidCastException: Unable to cast object of type
'System.Security.Cryptography.RSAOpenSsl' to type
'System.Security.Cryptography.RSACryptoServiceProvider
private static string EncryptRsa(byte[] input)
{
string output = string.Empty;
System.Security.Cryptography.X509Certificates.X509Certificate2 cert = new X509Certificate2(#"Cert/server_pub.cer");
using (RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PublicKey.Key)
{
byte[] bytesData = input;
byte[] bytesEncrypted = csp.Encrypt(bytesData, true);
output = Convert.ToBase64String(bytesEncrypted);
}
return output;
}
I changed the code to following, it runs successfully but when I call the API it give an error saying session key decryption error, please encrypt the session key using correct public key.
How to I get similar encryption to RSACryptoServiceProvider in .net core
private static string EncryptRsa(byte[] input)
{
string output = string.Empty;
System.Security.Cryptography.X509Certificates.X509Certificate2 cert = new X509Certificate2(#"Cert/server_pub.cer");
using (RSA csp = (RSA)cert.PublicKey.Key)
{
byte[] bytesData = input;
byte[] bytesEncrypted = csp.Encrypt(bytesData, RSAEncryptionPadding.Pkcs1);
output = Convert.ToBase64String(bytesEncrypted);
}
return output;
}
Code in answer of other question is not working on .net core, it uses .net framework
Casting private key to RSACryptoServiceProvider not working
It worked, in the updated code I was trying, I changed the padding to OaepSHA1, its working now. Thank you
Below is the working code (Tested on AWS Lambda):
private static string EncryptRsa(byte[] input)
{
string output = string.Empty;
System.Security.Cryptography.X509Certificates.X509Certificate2 cert = new X509Certificate2(#"Cert/server_pub.cer");
using (RSA csp = (RSA)cert.PublicKey.Key)
{
byte[] bytesData = input;
byte[] bytesEncrypted = csp.Encrypt(bytesData, RSAEncryptionPadding.OaepSHA1);
output = Convert.ToBase64String(bytesEncrypted);
}
return output;
}
I am encrypting the message in .NET with RSACryptoServiceProvider with private key. (PKCS#1 v1.5)
When I try to decrypt in .NET with the following code that uses public key everything works fine:
private static string Decrypt(string key, string content)
{
byte[] rgb = Convert.FromBase64String(content);
var cryptoServiceProvider = new RSACryptoServiceProvider(new CspParameters()
{
ProviderType = 1
});
cryptoServiceProvider.ImportCspBlob(Convert.FromBase64String(key));
return Convert.ToBase64String(cryptoServiceProvider.Decrypt(rgb, false));
}
When on the other hand I try to find an algorithm to make the same decrypt method in Android, I am failing to decrypt it properly with public key. I exported the modulus and exponent from public key in .NET in order to load it properly on Android.
The method in Android is here:
public String Decrypt(String input) {
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
String modulusString = "mmGn1IXB+/NEm1ecLiUzgz7g2L6L5EE5DUcptppTNwZSqxeYKn0AuAccupL0iyX3LMPw6Dl9pjPXDjk93TQwYwyGgZaXOSRDQd/W2Y93g8erpGBRm/Olt7QN2GYhxP8Vn+cWUbNuikdD4yMfYX9NeD9UNt5WJGFf+jRkLk0zRK0A7ZIS+q0NvGJ/CgaRuoe3x4Mh1qYP9ZWNRw8rsDbZ6N2zyUa3Hk/WJkptRa6jrzc937r3QYF3eDTurVJZHwC7c3TJ474/8up3YNREnpK1p7hqwQ78fn35Tw4ZyTNxCevVJfYtc7pKHHiwfk36OxtOIesfKlMnHMs4vMWJm79ctixqAe3i9aFbbRj710dKAfZZ0FnwSnTpsoKO5g7N8mKY8nVpZej7tcLdTL44JqWEqnQkocRqgO/p3R8V/6To/OjQGf0r6ut9y/LnlM5qalnKJ1gFg1D7gCzZJ150TX4AO5kGSAFRyjkwGxnR0WLKf+BDZ8T/syOrFOrzg6b05OxiECwCvLWk0AaQiJkdu2uHbsFUj3J2BcwDYm/kZiD0Ri886xHqZMNExZshlIqiecqCskQhaMVC1+aCm+IFf16Qg/+eMYCd+3jm/deezT4rcMBOV/M+muownGYQ9WOdjEK53h9oVheahD3LqCW8MizABFimvXR3wAgkIUvhocVhSN0=";
String exponentString = "AQAB";
byte[] modulusBytes = Base64.decode(modulusString.getBytes("UTF-8"), Base64.DEFAULT);
byte[] dBytes = Base64.decode(exponentString.getBytes("UTF-8"), Base64.DEFAULT);
BigInteger modulus = new BigInteger(1, modulusBytes);
BigInteger d = new BigInteger(1, dBytes);
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, d);
PublicKey key = keyFactory.generatePublic(keySpec);
//at one point I read somewhere that .net reverses the byte array so that it needs to be reversed for java, but who knows any more
/*byte[] inputArrayReversed = Base64.decode(input.getBytes("UTF-8"), Base64.DEFAULT);
for (int i = 0; i < inputArrayReversed.length / 2; i++) {
byte temp = inputArrayReversed[i];
inputArrayReversed[i] = inputArrayReversed[inputArrayReversed.length - 1];
inputArrayReversed[inputArrayReversed.length - 1] = temp;
}*/
byte[] decryptedText = null;
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, key);
decryptedText = cipher.doFinal(Base64.decode(input.getBytes("UTF-8"), Base64.DEFAULT));
return Base64.encodeToString(decryptedText, Base64.NO_WRAP);
//return new String(decryptedText, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
Actually I tried also with different algorithms specified in Cypher class, also tried many other combinations, tried using SpongyCastle instead of built in Android RSA providers, but nothing worked. If anybody has any clue to point me in right direction, I would be absolutely grateful.
First hint is that decrypted string from .NET comes as around 25 characters long, and when I get Android to return decrypted string without exceptions it is usually much longer, around 500 bytes.
Second hint deleted
Third hint I also tried spongycastle, but it didn't help that much
Anyways, thank you in advance for any help!!!
UPDATE 1
Second hint is deleted because was wrong, disregard it. Now I have one question if the following can prove that the public key is loaded correctly, just to rule that problem out.
BigInteger modulus and exponent in the upper Android code and the following BigIntegers in .NET show equal integer values.
var parameters = csp.ExportParameters(false);
var modulusInteger = new BigInteger(parameters.Modulus.Reverse().Concat(new byte[] { 0 }).ToArray());
var exponentInteger = new BigInteger(parameters.Exponent.Reverse().Concat(new byte[] { 0 }).ToArray());
UPDATE 2
This and This SO answers provide some interesting clues
Heeh, the mistake was one of the basics, we had an architecture where we were doing encryption with public key and decryption with private key. The problem was in the architecture itself because as we initially set it up, we were sending private keys to all our client apps, which is big security flaw.
My mistake was that I assumed that on the client we have public key and actually from private key all the time I was trying to load the public key and then do decrypt.
If I knew the PKI in depth and communicated a bit better with my colleague, I could have noticed few things:
Decrypt can be done with private key only, while one the other hand verify can be done with public key, so when I saw Decrypt being used on client in .NET, I should have assumed that on the client we have private key (which is a security flaw in the end in the way we want to use PKI)
Few things that I already knew or learnt and want to share with others:
Private key should be kept secret, whether you want to have it on server or preferably only on one client because public key can easily be guessed from private key and then someone can easily repeat your whole encryption process easily and breach your security
PKI works for two scenarios:
First scenario is when you want to Encrypt something and that only specific person/computer can Decrypt it. In first scenario as you see, many stakeholders can have someone's Public key and send messages to him and that only he can read them with his Private key. Second scenario is when you want to be sure that the message that came to you was not altered and was sent by specific person/computer. In that case you Sign data with Private key and Verify it on the other end with Public key. The only process that is suitable for us is Sign <-> Verify because we send plain text license with signature in it, and thus on the client we want to be sure that nobody tampered with the plain text license and that it came from us.
In your code, if Decrypt or Verify functions throw exceptions in 50% of the time it is because of loading the incorrect key or incorrectly loading the correct key and in the other 50% it is because you are using the incorrect algorithm or because algorithm parameters are incorrectly set or because the algorithm implementations between platforms are incompatible (the last one is very rare)
.NET server code
public string Sign(string privateKey, string data)
{
_rsaProvider.ImportCspBlob(Convert.FromBase64String(privateKey));
//// Write the message to a byte array using UTF8 as the encoding.
var encoder = new UTF8Encoding();
byte[] byteData = encoder.GetBytes(data);
//// Sign the data, using SHA512 as the hashing algorithm
byte[] encryptedBytes = _rsaProvider.SignData(byteData, new SHA1CryptoServiceProvider());
return Convert.ToBase64String(encryptedBytes);
}
.NET client code (Win Mobile)
private bool Verify(string key, string signature, string data)
{
CspParameters cspParams = new CspParameters { ProviderType = 1 };
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(cspParams);
rsaProvider.ImportCspBlob(Convert.FromBase64String(key));
byte[] signatureBytes = Convert.FromBase64String(signature);
var encoder = new UTF8Encoding();
byte[] dataBytes = encoder.GetBytes(data);
return rsaProvider.VerifyData(dataBytes, new SHA1CryptoServiceProvider(), signatureBytes);
}
Android client code:
public boolean Verify(RSAPublicKey key, String signature, String data)
{
try
{
Signature sign = Signature.getInstance("SHA1withRSA");
sign.initVerify(key);
sign.update(data.getBytes("UTF-8"));
return sign.verify(Base64.decode(signature.getBytes("UTF-8"), Base64.NO_WRAP));
}
catch (Exception e)
{
e.printStackTrace();
}
return false;
}
in .NET public key is exported in xml format with following code:
public string ExportPublicToXML(string publicKey)
{
RSACryptoServiceProvider csp = new RSACryptoServiceProvider(new CspParameters()
{
ProviderType = 1
});
csp.ImportCspBlob(Convert.FromBase64String(publicKey));
return csp.ToXmlString(false);
}
and then modulus and exponent are used in Android to load public key:
private RSAPublicKey GetPublicKey(String keyXmlString) throws InvalidKeySpecException, UnsupportedEncodingException, NoSuchAlgorithmException
{
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
String modulusString = keyXmlString.substring(keyXmlString.indexOf("<Modulus>"), keyXmlString.indexOf("</Modulus>")).replace("<Modulus>", "");
String exponentString = keyXmlString.substring(keyXmlString.indexOf("<Exponent>"), keyXmlString.indexOf("</Exponent>")).replace("<Exponent>", "");
byte[] modulusBytes = Base64.decode(modulusString.getBytes("UTF-8"), Base64.DEFAULT);
byte[] dBytes = Base64.decode(exponentString.getBytes("UTF-8"), Base64.DEFAULT);
BigInteger modulus = new BigInteger(1, modulusBytes);
BigInteger d = new BigInteger(1, dBytes);
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, d);
return (RSAPublicKey) keyFactory.generatePublic(keySpec);
}
I am trying to hash a value (SHA1) in both C# and Java, and then return a base64 representation. I get 2 different results.
I know this is because Java uses signed bytes while C# doesn't.
C# version :
static public string toSHA1(string toEncrypt)
{
return toSHA1(toEncrypt, new UTF8Encoding());
}
static public string toSHA1(string toEncrypt, Encoding encoding)
{
String salt = "fE4wd#u*d9b9kdKszgè02ep5à4qZa!éi6";
SHA256Managed sha256hasher = new SHA256Managed();
byte[] hashedDataBytes = sha256hasher.ComputeHash(encoding.GetBytes(toEncrypt + salt));
return Convert.ToBase64String(hashedDataBytes);
}
Java version :
public static String toSHA1(String toEncrypt) {
return toSHA1(toEncrypt, "UTF-8");
}
public static String toSHA1(String toEncrypt, String encoding) {
String salt = "fE4wd#u*d9b9kdKszgè02ep5à4qZa!éi6";
String res = null;
toEncrypt = toEncrypt + salt;
try {
byte[] dataBytes = toEncrypt.getBytes(encoding);
MessageDigest md = MessageDigest.getInstance("SHA-1");
res = Base64.encodeBytes(md.digest(dataBytes));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return res;
}
I can't manage to find a solution to get the correct base64 result using Java.
Converting signed values to unsigned ones forces the use of int data type, but as soon as I put it in a byte data type, I get my signed bytes back...
Base64.encodeBytes is waiting for a byte array, so is there any way I can pass an unsigned byte array to this method ? What can I do with that int array ? :
int[] dataInt = new int[dataBytes.length];
// signed to unsigned
for (int i=0; i<dataBytes.length; i++)
{
dataInt[i] = (dataBytes[i] & 0xFF);
}
I can't modify the C# version, I have to adapt the Java version to give the same results.
The problem is very simple... From your C# code:
SHA256Managed sha256hasher = new SHA256Managed()
SHA-256 != SHA-1. Use the SHA1 class instead in C#, or use SHA-256 in Java as well. As you apparently can't change the C# code, you should change the Java instead:
MessageDigest md = MessageDigest.getInstance("SHA-256");
Once you've done that, the base64-encoded data should be the same in both platforms. Even though bytes are signed in Java, base64 encoders treat them as unsigned... they're only interested in the bits, basically.
I'd also strongly suggest that you represent your salt in ASCII in the source code, using \uxxxx escaping for any non-ASCII characters. This will prevent problems due to compiling using the wrong encoding.
We have a Windows Phone 8 app that needs to communicate with a web service that uses Chilkat to encrypt some data. As far as I know, Chilkat does not support the Windows Phone platform. I have the key and other info about how the data is encrypted (such as the encryption algorithm name, key-length etc.), but will I be able to encrypt/decrypt on Windows Phone without having this library? (We already have android/ios apps that use the same service and they use the chilkat library to crypt the data)
class Program
{
static readonly string keyString = "MyKey";
static readonly string iv = "MyIV";
static Encoding TheEncoding = Encoding.UTF8;
static void Main(string[] args)
{
//I got Chilkat and BouncyCastle via NuGet
//https://www.nuget.org/packages/WinRTBouncyCastle/0.1.1.1
//chilcat-win32
var original = "clear text";
var chilkatCrypt = GetChilkat3Des();
//this is equalent to an encrypted text I get from the service
var ckEncrypted = chilkatCrypt.EncryptStringENC(original);
var ckDecrypted = chilkatCrypt.DecryptStringENC(ckEncrypted);
if (!string.Equals(original, ckDecrypted)) throw new ArgumentException("chilkat encrypt/decrypt failure...");
//now comes the challenge, to decrypt the Chilkat encryption with BouncyCastle (or what ever crypto lib that runs on WP8)
//this is where i need help :)
byte[] chilkatEncBytes = System.Text.Encoding.UTF8.GetBytes(ckEncrypted);
var bouncyDecrypted = BouncyCastleDecrypt(chilkatEncBytes);
}
public static Chilkat.Crypt2 GetChilkat3Des()
{
var crypt = new Chilkat.Crypt2();
if (!crypt.UnlockComponent("Start my 30-day Trial"))
{
throw new Exception("Unlock Chilkat failed");
}
crypt.CryptAlgorithm = "3des";
crypt.CipherMode = "cbc";
crypt.KeyLength = 192;
crypt.PaddingScheme = 0;
// It may be "hex", "url", "base64", or "quoted-printable".
crypt.EncodingMode = "hex";
crypt.SetEncodedIV(iv, crypt.EncodingMode);
crypt.SetEncodedKey(keyString, crypt.EncodingMode);
return crypt;
}
//this code is more or less copied from here:
//http://nicksnettravels.builttoroam.com/post/2012/03/27/TripleDes-Encryption-with-Key-and-IV-for-Windows-Phone.aspx
public static byte[] RunBouncyCastleTripleDes(byte[] input, bool encrypt)
{
byte[] byteKey = new byte[24];
Buffer.BlockCopy(TheEncoding.GetBytes(keyString), 0, byteKey, 0, TheEncoding.GetBytes(keyString).Length);
var IV = new byte[8];
Buffer.BlockCopy(TheEncoding.GetBytes(iv), 0, IV, 0, TheEncoding.GetBytes(iv).Length);
var keyParam = new Org.BouncyCastle.Crypto.Parameters.DesEdeParameters(byteKey);
var ivParam = new Org.BouncyCastle.Crypto.Parameters.ParametersWithIV(keyParam, IV);
var engine = Org.BouncyCastle.Security.CipherUtilities.GetCipher("DESede/CBC/PKCS5Padding");
engine.Init(encrypt, ivParam);
var output = engine.DoFinal(input);
return output;
}
public static byte[] BouncyCastleEncrypt(byte[] input)
{
return RunBouncyCastleTripleDes(input, true);
}
public static byte[] BouncyCastleDecrypt(byte[] input)
{
return RunBouncyCastleTripleDes(input, false);
}
}
I have the key and other info about how the data is encrypted (such as the encryption algorithm name, key-length etc.), but will I be able to encrypt/decrypt on Windows Phone without having this library?
It depends, buts the answer is probably yes.
If they have a home-grown implementation of well known algorithms, then they might have a bug and the answer could be NO.
If they are using well-known algorithms form well vetted libraries and have fully specified the algorithms and parameters, the the answer is likely YES.
I was hoping I might get some help here so that I might finally solve this frustrating problem.
On the java side of things they sign with the following code:
public static void main(String[] args) throws Exception {
if (args.length < 2)
printInfoAndExit();
String cmd = args[0];
Security.addProvider(new BouncyCastleProvider());
Signature signature = Signature.getInstance("SHA1withRSA", "BC");
if ("sign".equalsIgnoreCase(cmd)) {
String pemFileName = args[1];
String dataFileName = args[2];
byte[] data = readFile(dataFileName);
FileReader fr = new FileReader(new File(pemFileName));
PEMReader pemReader = new PEMReader(fr);
KeyPair keyPair = (KeyPair) pemReader.readObject();
fr.close();
signature.initSign(keyPair.getPrivate());
signature.update(data);
byte[] signatureBytes = signature.sign();
writeFile(signatureBytes, dataFileName + ".signed");
String encoded = Base64.encode(signatureBytes);
writeFile(encoded.getBytes(), dataFileName + ".signed.base64");
} else {
printInfoAndExit();
}
}
When I receive the data I have their public key and try to verify with the following C# code:
public static bool Verify(String msg, String signature, String publicKey)
{
RsaKeyParameters remotepubkey = GetRsaPublicKey(publicKey);
ISigner signer = SignerUtilities.GetSigner("SHA1withRSA");
signer.Init(false, remotepubkey);
byte[] sigBytes = Convert.FromBase64String(signature);
byte[] msgBytes = Encoding.Default.GetBytes(msg);
signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
return signer.VerifySignature(sigBytes);
}
This is not working!! I can however verify the data with openssl:
openssl dgst -sha1 -verify public_key.pem -signature data.txt.signed data.txt
The question is, what am I missing to make this work?
NOTE: I don't have a problem with the keys, that is working correctly but somehow there is a difference between how java and .net works with RSA?
**Edit 1 : **In this particular scenario all I had to do was change the GetSigner to
ISigner signer = SignerUtilities.GetSigner("RSA");
Could someone tell me the difference between SHA1withRSA and RSA?
The problem was actually solved on the Java side. They had some issues with their side of things.
You could have an encoding problem with your message data. You've converted the original file data into a unicode string, and are trying to convert it back to raw bytes. Depending on the encoding of the file, and if it's even text at all, your msgBytes could be different from the actual file contents.
Read the raw bytes from the file instead of a string. You don't show the code for actually reading the file data, but I assume you're reading it as text.